diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index db47a1bf9..01b7bba09 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -155,8 +155,14 @@ jobs: yarn config set yarn-offline-mirror ~/.npm-packages-offline-cache yarn install --frozen-lockfile --prefer-offline --ignore-engines - name: Build - id: yarn-pack-dir run: yarn build + # Seeing some flakes on windows, skip for now + # https://github.com/angular/angularfire/runs/3593478229 + # not just windows + # https://github.com/angular/angularfire/runs/3593535123 + # - name: Test + # if: runner.os != 'windows' + # run: yarn test headless: runs-on: ubuntu-latest diff --git a/package.json b/package.json index ba97634d0..cc23f3e65 100644 --- a/package.json +++ b/package.json @@ -52,22 +52,25 @@ "@angular/platform-browser": "^12.0.0", "@angular/platform-browser-dynamic": "^12.0.0", "@angular/router": "^12.0.0", + "@schematics/angular": "^12.0.0", "firebase": "^9.0.0", - "firebase-admin": "^8.10.0", + "firebase-admin": "^9.11.1", "firebase-functions": "^3.6.0", "firebase-tools": "^9.0.0", "fs-extra": "^8.0.1", "fuzzy": "^0.1.3", "husky": "^4.2.5", - "inquirer": "^6.2.2", "inquirer-autocomplete-prompt": "^1.0.1", "jsonc-parser": "^3.0.0", - "open": "^7.0.3", + "open": "^7.0.3 || ^8.0.0", + "ora": "^5.3.0", "rxfire": "^6.0.0", "rxjs": "~6.6.0", "semver": "^7.1.3", + "triple-beam": "^1.3.0", "tslib": "^2.1.0", "webpack": "^5.35.0", + "winston": "^3.0.0", "zone.js": "~0.11.4" }, "devDependencies": { @@ -84,6 +87,8 @@ "@types/node": "^12.6.2 < 12.12.42", "@types/request": "0.0.30", "@types/semver": "^7.1.0", + "@types/triple-beam": "^1.3.0", + "@types/winston": "^2.4.4", "codelyzer": "^6.0.0", "concurrently": "^2.2.0", "conventional-changelog-cli": "^1.2.0", diff --git a/samples/advanced/.firebaserc b/samples/advanced/.firebaserc index b4341eb73..0ce29fcbd 100644 --- a/samples/advanced/.firebaserc +++ b/samples/advanced/.firebaserc @@ -7,5 +7,8 @@ ] } } + }, + "projects": { + "default": "aftest-94085" } } \ No newline at end of file diff --git a/samples/advanced/angular.json b/samples/advanced/angular.json index 4c9024825..3b7e87886 100644 --- a/samples/advanced/angular.json +++ b/samples/advanced/angular.json @@ -122,8 +122,17 @@ "deploy": { "builder": "@angular/fire:deploy", "options": { - "ssr": true, - "functionsNodeVersion": 16 + "ssr": "cloud-functions", + "prerender": true, + "firebaseProject": "aftest-94085", + "firebaseHostingSite": "aftest-94085", + "functionName": "ssr_sample", + "functionsNodeVersion": "16", + "region": "us-central1", + "browserTarget": "sample:build:production", + "serverTarget": "sample:server:production", + "prerenderTarget": "sample:prerender:production", + "outputPath": "dist/sample/functions" } }, "server": { @@ -135,8 +144,7 @@ "optimization": false, "sourceMap": true, "extractLicenses": false, - "externalDependencies": [ - ] + "externalDependencies": [] }, "configurations": { "production": { diff --git a/samples/advanced/firebase.json b/samples/advanced/firebase.json index b8c396b34..4677ea2f0 100644 --- a/samples/advanced/firebase.json +++ b/samples/advanced/firebase.json @@ -2,7 +2,7 @@ "hosting": [ { "target": "sample", - "public": "dist/sample/dist/sample/browser", + "public": "dist/sample/browser", "ignore": [ "**/.*" ], @@ -20,12 +20,12 @@ "rewrites": [ { "source": "**", - "function": "ssr" + "function": "ssr_sample" } ] } ], "functions": { - "source": "dist/sample" + "source": "dist/sample/functions" } } \ No newline at end of file diff --git a/samples/advanced/package.json b/samples/advanced/package.json index 22d173fb5..41bae0797 100644 --- a/samples/advanced/package.json +++ b/samples/advanced/package.json @@ -52,20 +52,14 @@ "firebase-admin": "^9.11.1", "firebase-functions": "^3.6.0", "firebase-functions-test": "^0.2.2", - "firebase-tools": "^9.0.0", - "fuzzy": "^0.1.3", - "inquirer": "^6.2.2", - "inquirer-autocomplete-prompt": "^1.0.1", "jasmine-core": "~3.7.0", "jasmine-spec-reporter": "~7.0.0", - "jsonc-parser": "^3.0.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.0.3", "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "^1.5.0", "ng-packagr": "^12.0.0", - "open": "^7.0.3", "ts-node": "~9.1.1", "tslint": "~6.1.3", "typescript": "~4.2.3", @@ -74,4 +68,4 @@ "resolutions": { "webpack": "^5.35.0" } -} +} \ No newline at end of file diff --git a/samples/advanced/yarn.lock b/samples/advanced/yarn.lock index 5b3646823..3932af65a 100644 --- a/samples/advanced/yarn.lock +++ b/samples/advanced/yarn.lock @@ -245,8 +245,13 @@ tslib "^2.2.0" "@angular/fire@../../dist/packages-dist": - version "7.0.4" + version "7.1.0" dependencies: + firebase-tools "^9.0.0" + fuzzy "^0.1.3" + inquirer-autocomplete-prompt "^1.0.1" + jsonc-parser "^3.0.0" + open "^7.0.3 || ^8.0.0" tslib "^2.0.0" "@angular/forms@^12.0.0": @@ -6497,25 +6502,6 @@ inquirer@8.1.2: strip-ansi "^6.0.0" through "^2.3.6" -inquirer@^6.2.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" - integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== - dependencies: - ansi-escapes "^3.2.0" - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^2.0.0" - lodash "^4.17.12" - mute-stream "0.0.7" - run-async "^2.2.0" - rxjs "^6.4.0" - string-width "^2.1.0" - strip-ansi "^5.1.0" - through "^2.3.6" - inquirer@~6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.3.1.tgz#7a413b5e7950811013a3db491c61d1f3b776e8e7" @@ -6906,7 +6892,7 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= -is-wsl@^2.1.1, is-wsl@^2.2.0: +is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== @@ -7744,7 +7730,7 @@ lodash.values@^2.4.1: dependencies: lodash.keys "~2.4.1" -lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.7.0: +lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -8718,7 +8704,7 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" -open@8.2.1: +open@8.2.1, "open@^7.0.3 || ^8.0.0": version "8.2.1" resolved "https://registry.yarnpkg.com/open/-/open-8.2.1.tgz#82de42da0ccbf429bc12d099dad2e0975e14e8af" integrity sha512-rXILpcQlkF/QuFez2BJDf3GsqpjGKbkUUToAIGo9A0Q6ZkoSGogZJulrUdwRkrAsoQvoZsrjCYt8+zblOk7JQQ== @@ -8734,14 +8720,6 @@ open@^6.3.0: dependencies: is-wsl "^1.1.0" -open@^7.0.3: - version "7.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" - integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== - dependencies: - is-docker "^2.0.0" - is-wsl "^2.1.1" - openapi3-ts@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/openapi3-ts/-/openapi3-ts-2.0.1.tgz#b270aecea09e924f1886bc02a72608fca5a98d85" diff --git a/samples/modular/yarn.lock b/samples/modular/yarn.lock index 5b3646823..61accf228 100644 --- a/samples/modular/yarn.lock +++ b/samples/modular/yarn.lock @@ -245,9 +245,16 @@ tslib "^2.2.0" "@angular/fire@../../dist/packages-dist": - version "7.0.4" + version "7.1.0" dependencies: + fuzzy "^0.1.3" + inquirer-autocomplete-prompt "^1.0.1" + jsonc-parser "^3.0.0" + open "^8.0.0" + ora "^5.3.0" + triple-beam "^1.3.0" tslib "^2.0.0" + winston "^3.0.0" "@angular/forms@^12.0.0": version "12.2.5" @@ -8718,7 +8725,7 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" -open@8.2.1: +open@8.2.1, open@^8.0.0: version "8.2.1" resolved "https://registry.yarnpkg.com/open/-/open-8.2.1.tgz#82de42da0ccbf429bc12d099dad2e0975e14e8af" integrity sha512-rXILpcQlkF/QuFez2BJDf3GsqpjGKbkUUToAIGo9A0Q6ZkoSGogZJulrUdwRkrAsoQvoZsrjCYt8+zblOk7JQQ== diff --git a/src/package.json b/src/package.json index 1a673eb7f..801bfdf7c 100644 --- a/src/package.json +++ b/src/package.json @@ -32,7 +32,14 @@ "rxjs": "~6.6.0" }, "dependencies": { - "tslib": "^2.0.0" + "tslib": "^2.0.0", + "fuzzy": "^0.1.3", + "inquirer-autocomplete-prompt": "^1.0.1", + "open": "^8.0.0", + "jsonc-parser": "^3.0.0", + "ora": "^5.3.0", + "winston": "^3.0.0", + "triple-beam": "^1.3.0" }, "ngPackage": { "lib": { @@ -45,7 +52,12 @@ }, "entryFile": "public_api.ts" }, - "dest": "../dist/packages-dist" + "dest": "../dist/packages-dist", + "allowedNonPeerDependencies": [ + "fuzzy", "inquirer-autocomplete-prompt", + "open", "jsonc-parser", "ora", "winston", + "triple-beam" + ] }, "ng-update": { "migrations": "./schematics/migration.json" diff --git a/src/schematics/add/index.ts b/src/schematics/add/index.ts new file mode 100644 index 000000000..8a9032181 --- /dev/null +++ b/src/schematics/add/index.ts @@ -0,0 +1,34 @@ +import { chain, Rule, SchematicContext, TaskId, Tree } from '@angular-devkit/schematics'; +import { DeployOptions } from '../interfaces'; +import { addDependencies } from '../common'; +import { peerDependencies } from '../versions.json'; +import { NodePackageInstallTask, RunSchematicTask } from '@angular-devkit/schematics/tasks'; + +const addFirebaseHostingDependencies = () => (tree: Tree, context: SchematicContext) => { + addDependencies( + tree, + peerDependencies, + context + ); + return tree; +}; + +let npmInstallTaskId: TaskId; + +const npmInstall = () => (tree: Tree, context: SchematicContext) => { + npmInstallTaskId = context.addTask(new NodePackageInstallTask()); + return tree; +}; + +const runSetup = (options: DeployOptions) => (tree: Tree, context: SchematicContext) => { + context.addTask(new RunSchematicTask('ng-add-setup-project', options), [npmInstallTaskId]); + return tree; +}; + +export const ngAdd = (options: DeployOptions): Rule => { + return chain([ + addFirebaseHostingDependencies(), + npmInstall(), + runSetup(options), + ]); +}; diff --git a/src/schematics/add/schema.json b/src/schematics/add/schema.json new file mode 100644 index 000000000..5b1ce2c4f --- /dev/null +++ b/src/schematics/add/schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "angular-fire-ng-add", + "title": "AngularFire ng-add", + "type": "object", + "properties": { + "project": { + "type": "string", + "description": "The name of the project.", + "$default": { + "$source": "projectName" + } + } + }, + "required": [] + } diff --git a/src/schematics/collection.json b/src/schematics/collection.json index 863eb83b3..be353c37d 100644 --- a/src/schematics/collection.json +++ b/src/schematics/collection.json @@ -3,11 +3,13 @@ "schematics": { "ng-add": { "description": "Add firebase deploy schematic", - "factory": "./public_api#ngAdd" + "factory": "./add#ngAdd", + "schema": "./add/schema.json" }, "ng-add-setup-project": { "description": "Setup ng deploy", - "factory": "./public_api#ngAddSetupProject" + "factory": "./setup#ngAddSetupProject", + "schema": "./setup/schema.json" } } } diff --git a/src/schematics/ng-add-common.ts b/src/schematics/common.ts similarity index 62% rename from src/schematics/ng-add-common.ts rename to src/schematics/common.ts index 813d146a1..c5d3f8eef 100644 --- a/src/schematics/ng-add-common.ts +++ b/src/schematics/common.ts @@ -1,20 +1,7 @@ import { SchematicsException, Tree, SchematicContext } from '@angular-devkit/schematics'; -import { FirebaseRc } from './interfaces'; +import { FirebaseHostingSite, FirebaseRc } from './interfaces'; import * as semver from 'semver'; - -export interface NgAddOptions { - firebaseProject: string; - project?: string; -} - -export interface NgAddNormalizedOptions { - firebaseProject: string; - project: string; -} - -export interface DeployOptions { - project: string; -} +import { shortSiteName } from './utils'; export const stringifyFormatted = (obj: any) => JSON.stringify(obj, null, 2); @@ -36,12 +23,11 @@ function emptyFirebaseRc() { }; } -function generateFirebaseRcTarget(firebaseProject: string, project: string) { +function generateFirebaseRcTarget(firebaseProject: string, firebaseHostingSite: FirebaseHostingSite|undefined, project: string) { return { hosting: { [project]: [ - // TODO(kirjs): Generally site name is consistent with the project name, but there are edge cases. - firebaseProject + shortSiteName(firebaseHostingSite) ?? firebaseProject ] } }; @@ -51,6 +37,7 @@ export function generateFirebaseRc( tree: Tree, path: string, firebaseProject: string, + firebaseHostingSite: FirebaseHostingSite|undefined, project: string ) { const firebaseRc: FirebaseRc = tree.exists(path) @@ -58,18 +45,12 @@ export function generateFirebaseRc( : emptyFirebaseRc(); firebaseRc.targets = firebaseRc.targets || {}; - - /* TODO do we want to prompt? - if (firebaseProject in firebaseRc.targets) { - throw new SchematicsException( - `Firebase project ${firebaseProject} already defined in .firebaserc` - ); - }*/ - firebaseRc.targets[firebaseProject] = generateFirebaseRcTarget( firebaseProject, + firebaseHostingSite, project ); + firebaseRc.projects = { default: firebaseProject }; overwriteIfExists(tree, path, stringifyFormatted(firebaseRc)); } @@ -95,28 +76,27 @@ export const addDependencies = ( throw new SchematicsException('Could not locate package.json'); } + packageJson.devDependencies ??= {}; + packageJson.dependencies ??= {}; + Object.keys(deps).forEach(depName => { const dep = deps[depName]; - if (dep.dev) { - const existingVersion = packageJson.devDependencies[depName]; - if (existingVersion) { + const existingDeps = dep.dev ? packageJson.devDependencies : packageJson.dependencies; + const existingVersion = existingDeps[depName]; + if (existingVersion) { + try { if (!semver.intersects(existingVersion, dep.version)) { context.logger.warn(`⚠️ The ${depName} devDependency specified in your package.json (${existingVersion}) does not fulfill AngularFire's dependency (${dep.version})`); // TODO offer to fix } - } else { - packageJson.devDependencies[depName] = dep.version; - } - } else { - const existingVersion = packageJson.dependencies[depName]; - if (existingVersion) { - if (!semver.intersects(existingVersion, dep.version)) { - context.logger.warn(`⚠️ The ${depName} dependency specified in your package.json (${existingVersion}) does not fulfill AngularFire's dependency (${dep.version})`); + } catch (e) { + if (existingVersion !== dep.version) { + context.logger.warn(`⚠️ The ${depName} devDependency specified in your package.json (${existingVersion}) does not fulfill AngularFire's dependency (${dep.version})`); // TODO offer to fix } - } else { - packageJson.dependencies[depName] = dep.version; } + } else { + existingDeps[depName] = dep.version; } }); diff --git a/src/schematics/deploy/actions.jasmine.ts b/src/schematics/deploy/actions.jasmine.ts index d4202736b..fc4d8146e 100644 --- a/src/schematics/deploy/actions.jasmine.ts +++ b/src/schematics/deploy/actions.jasmine.ts @@ -1,7 +1,7 @@ import { JsonObject, logging } from '@angular-devkit/core'; import { BuilderContext, BuilderRun, ScheduleOptions, Target } from '@angular-devkit/architect'; import { BuildTarget, FirebaseDeployConfig, FirebaseTools, FSHost } from '../interfaces'; -import deploy, { deployToFunction } from './actions'; +import deploy, { deployToFunction } from '@angular/fire/schematics/deploy/actions'; import { join } from 'path'; import 'jasmine'; @@ -28,13 +28,29 @@ const initMocks = () => { renameSync(_: string, __: string) { }, writeFileSync(_: string, __: string) { + }, + copySync(_: string, __: string) { + }, + removeSync(_: string) { } }; firebaseMock = { login: () => Promise.resolve(), projects: { - list: () => Promise.resolve([]) + list: () => Promise.resolve([]), + create: () => Promise.reject(), + }, + apps: { + list: () => Promise.resolve([]), + create: () => Promise.reject(), + sdkconfig: () => Promise.resolve({ fileName: '_', fileContents: '', sdkConfig: {}, }), + }, + hosting: { + sites: { + list: () => Promise.resolve({sites: []}), + create: () => Promise.reject(), + } }, deploy: (_: FirebaseDeployConfig) => Promise.resolve(), use: () => Promise.resolve(), @@ -86,19 +102,22 @@ describe('Deploy Angular apps', () => { it('should call login', async () => { const spy = spyOn(firebaseMock, 'login'); - await deploy(firebaseMock, context, STATIC_BUILD_TARGET, undefined, FIREBASE_PROJECT, { preview: false }); + await deploy( + firebaseMock, context, STATIC_BUILD_TARGET, undefined, + undefined, undefined, { projectId: FIREBASE_PROJECT, preview: false } + ); expect(spy).toHaveBeenCalled(); }); it('should not call login', async () => { const spy = spyOn(firebaseMock, 'login'); - await deploy(firebaseMock, context, STATIC_BUILD_TARGET, undefined, FIREBASE_PROJECT, { preview: false }, FIREBASE_TOKEN); + await deploy(firebaseMock, context, STATIC_BUILD_TARGET, undefined, undefined, undefined, { preview: false }, FIREBASE_TOKEN); expect(spy).not.toHaveBeenCalled(); }); it('should invoke the builder', async () => { const spy = spyOn(context, 'scheduleTarget').and.callThrough(); - await deploy(firebaseMock, context, STATIC_BUILD_TARGET, undefined, FIREBASE_PROJECT, { preview: false }); + await deploy(firebaseMock, context, STATIC_BUILD_TARGET, undefined, undefined, undefined, { preview: false }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith({ target: 'build', @@ -113,26 +132,27 @@ describe('Deploy Angular apps', () => { options: {} }; const spy = spyOn(context, 'scheduleTarget').and.callThrough(); - await deploy(firebaseMock, context, buildTarget, undefined, FIREBASE_PROJECT, { preview: false }); + await deploy(firebaseMock, context, buildTarget, undefined, undefined, undefined, { preview: false }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith({ target: 'prerender', project: PROJECT }, {}); }); it('should invoke firebase.deploy', async () => { const spy = spyOn(firebaseMock, 'deploy').and.callThrough(); - await deploy(firebaseMock, context, STATIC_BUILD_TARGET, undefined, FIREBASE_PROJECT, { preview: false }, FIREBASE_TOKEN); + await deploy(firebaseMock, context, STATIC_BUILD_TARGET, undefined, undefined, undefined, { preview: false }, FIREBASE_TOKEN); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith({ cwd: 'cwd', only: 'hosting:' + PROJECT, - token: FIREBASE_TOKEN + token: FIREBASE_TOKEN, + nonInteractive: true, }); }); describe('error handling', () => { it('throws if there is no firebase project', async () => { try { - await deploy(firebaseMock, context, STATIC_BUILD_TARGET, undefined, undefined, { preview: false }); + await deploy(firebaseMock, context, STATIC_BUILD_TARGET, undefined, undefined, undefined, { preview: false }); } catch (e) { expect(e.message).toMatch(/Cannot find firebase project/); } @@ -141,7 +161,7 @@ describe('Deploy Angular apps', () => { it('throws if there is no target project', async () => { context.target = undefined; try { - await deploy(firebaseMock, context, STATIC_BUILD_TARGET, undefined, FIREBASE_PROJECT, { preview: false }); + await deploy(firebaseMock, context, STATIC_BUILD_TARGET, undefined, undefined, undefined, { preview: false }); } catch (e) { expect(e.message).toMatch(/Cannot execute the build target/); } @@ -174,6 +194,28 @@ describe('universal deployment', () => { expect(functionArgs[0]).toBe(join('dist', 'index.js')); }); + it('should create a firebase function (new)', async () => { + const spy = spyOn(fsHost, 'writeFileSync'); + await deployToFunction( + firebaseMock, + context, + '/home/user', + STATIC_BUILD_TARGET, + SERVER_BUILD_TARGET, + { preview: false, outputPath: join('dist', 'functions') }, + undefined, + fsHost + ); + + expect(spy).toHaveBeenCalledTimes(2); + + const packageArgs = spy.calls.argsFor(0); + const functionArgs = spy.calls.argsFor(1); + + expect(packageArgs[0]).toBe(join('dist', 'functions', 'package.json')); + expect(functionArgs[0]).toBe(join('dist', 'functions', 'index.js')); + }); + it('should rename the index.html file in the nested dist', async () => { const spy = spyOn(fsHost, 'renameSync'); await deployToFunction( @@ -197,6 +239,29 @@ describe('universal deployment', () => { ]); }); + it('should rename the index.html file in the nested dist (new)', async () => { + const spy = spyOn(fsHost, 'renameSync'); + await deployToFunction( + firebaseMock, + context, + '/home/user', + STATIC_BUILD_TARGET, + SERVER_BUILD_TARGET, + { preview: false, outputPath: join('dist', 'functions') }, + undefined, + fsHost + ); + + expect(spy).toHaveBeenCalledTimes(1); + + const packageArgs = spy.calls.argsFor(0); + + expect(packageArgs).toEqual([ + join('dist', 'functions', 'dist', 'browser', 'index.html'), + join('dist', 'functions', 'dist', 'browser', 'index.original.html') + ]); + }); + it('should invoke firebase.deploy', async () => { const spy = spyOn(firebaseMock, 'deploy'); await deployToFunction( diff --git a/src/schematics/deploy/actions.ts b/src/schematics/deploy/actions.ts index 070647000..b28111199 100644 --- a/src/schematics/deploy/actions.ts +++ b/src/schematics/deploy/actions.ts @@ -1,14 +1,60 @@ import { BuilderContext, targetFromTargetString } from '@angular-devkit/architect'; -import { BuildTarget, DeployBuilderSchema, FirebaseTools, FSHost } from '../interfaces'; +import { BuildTarget, CloudRunOptions, DeployBuilderSchema, FirebaseTools, FSHost } from '../interfaces'; import { existsSync, readFileSync, renameSync, writeFileSync } from 'fs'; import { copySync, removeSync } from 'fs-extra'; import { dirname, join } from 'path'; -import { execSync } from 'child_process'; -import { defaultFunction, defaultPackage } from './functions-templates'; +import { execSync, spawn, SpawnOptionsWithoutStdio } from 'child_process'; +import { defaultFunction, defaultPackage, DEFAULT_FUNCTION_NAME, dockerfile } from './functions-templates'; import { satisfies } from 'semver'; -import * as open from 'open'; +import open from 'open'; +import { SchematicsException } from '@angular-devkit/schematics'; +import { firebaseFunctionsDependencies } from '../versions.json'; +import * as winston from 'winston'; +import tripleBeam from 'triple-beam'; +import * as inquirer from 'inquirer'; + +const DEFAULT_EMULATOR_PORT = 5000; +const DEFAULT_EMULATOR_HOST = 'localhost'; + +const DEFAULT_CLOUD_RUN_OPTIONS: Partial = { + memory: '1Gi', + timeout: 60, + maxInstances: 'default', + maxConcurrency: 'default', // TODO tune concurrency for cloud run + angular + minInstances: 'default', + cpus: 1, +}; + +const spawnAsync = async ( + command: string, + options?: SpawnOptionsWithoutStdio +) => + new Promise((resolve, reject) => { + const [spawnCommand, ...args] = command.split(/\s+/); + const spawnProcess = spawn(spawnCommand, args, options); + const chunks: Buffer[] = []; + const errorChunks: Buffer[] = []; + spawnProcess.stdout.on('data', (data) => { + process.stdout.write(data.toString()); + chunks.push(data); + }); + spawnProcess.stderr.on('data', (data) => { + process.stderr.write(data.toString()); + errorChunks.push(data); + }); + spawnProcess.on('error', (error) => { + reject(error); + }); + spawnProcess.on('close', (code) => { + if (code === 1) { + reject(Buffer.concat(errorChunks).toString()); + return; + } + resolve(Buffer.concat(chunks)); + }); + }); -export type DeployBuilderOptions = DeployBuilderSchema | Record; +export type DeployBuilderOptions = DeployBuilderSchema & Record; const escapeRegExp = (str: string) => str.replace(/[\-\[\]\/{}()*+?.\\^$|]/g, '\\$&'); @@ -17,7 +63,7 @@ const moveSync = (src: string, dest: string) => { removeSync(src); }; -const deployToHosting = ( +const deployToHosting = async ( firebaseTools: FirebaseTools, context: BuilderContext, workspaceRoot: string, @@ -26,93 +72,79 @@ const deployToHosting = ( ) => { if (options.preview) { - const port = 5000; // TODO make this configurable - - setTimeout(() => { - open(`http://localhost:${port}`); - }, 1500); - - return firebaseTools.serve({ port, targets: ['hosting'], host: 'localhost' }).then(() => - require('inquirer').prompt({ - type: 'confirm', - name: 'deployProject', - message: 'Would you like to deploy your application to Firebase Hosting?' - }) - ).then(({ deployProject }: { deployProject: boolean }) => { - if (deployProject) { - return firebaseTools.deploy({ - // tslint:disable-next-line:no-non-null-assertion - only: 'hosting:' + context.target!.project, - cwd: workspaceRoot, - token: firebaseToken, - }); - } else { - return Promise.resolve(); - } - }); - - } else { - return firebaseTools.deploy({ + await firebaseTools.serve({ + port: DEFAULT_EMULATOR_PORT, + host: DEFAULT_EMULATOR_HOST, // tslint:disable-next-line:no-non-null-assertion - only: 'hosting:' + context.target!.project, - cwd: workspaceRoot, - token: firebaseToken, + targets: [`hosting:${context.target!.project}`], + nonInteractive: true + }); + + const { deployProject } = await inquirer.prompt({ + type: 'confirm', + name: 'deployProject', + message: 'Would you like to deploy your application to Firebase Hosting?' }); + if (!deployProject) { return; } + } + + return await firebaseTools.deploy({ + // tslint:disable-next-line:no-non-null-assertion + only: 'hosting:' + context.target!.project, + cwd: workspaceRoot, + token: firebaseToken, + nonInteractive: true, + }); + }; const defaultFsHost: FSHost = { moveSync, writeFileSync, - renameSync + renameSync, + copySync, + removeSync, }; -const getVersionRange = (v: number) => `^${v}.0.0`; - const findPackageVersion = (name: string) => { const match = execSync(`npm list ${name}`).toString().match(` ${escapeRegExp(name)}@.+\\w`); return match ? match[0].split(`${name}@`)[1].split(/\s/)[0] : null; }; -const getPackageJson = (context: BuilderContext, workspaceRoot: string, options: DeployBuilderOptions) => { - const dependencies = { - 'firebase-admin': 'latest', - 'firebase-functions': 'latest' - }; - const devDependencies = { - 'firebase-functions-test': 'latest' - }; - Object.keys(dependencies).forEach((dependency: string) => { - const packageVersion = findPackageVersion(dependency); - if (packageVersion) { dependencies[dependency] = packageVersion; } - }); - Object.keys(devDependencies).forEach((devDependency: string) => { - const packageVersion = findPackageVersion(devDependency); - if (packageVersion) { devDependencies[devDependency] = packageVersion; } - }); +const getPackageJson = (context: BuilderContext, workspaceRoot: string, options: DeployBuilderOptions, main?: string) => { + const dependencies: Record = {}; + const devDependencies: Record = {}; + if (options.ssr !== 'cloud-run') { + Object.keys(firebaseFunctionsDependencies).forEach(name => { + const { version, dev } = firebaseFunctionsDependencies[name]; + (dev ? devDependencies : dependencies)[name] = version; + }); + } if (existsSync(join(workspaceRoot, 'angular.json'))) { const angularJson = JSON.parse(readFileSync(join(workspaceRoot, 'angular.json')).toString()); // tslint:disable-next-line:no-non-null-assertion const server = angularJson.projects[context.target!.project].architect.server; const externalDependencies = server?.options?.externalDependencies || []; const bundleDependencies = server?.options?.bundleDependencies ?? true; - if (bundleDependencies !== true) { + if (bundleDependencies) { + externalDependencies.forEach(externalDependency => { + const packageVersion = findPackageVersion(externalDependency); + if (packageVersion) { dependencies[externalDependency] = packageVersion; } + }); + } else { if (existsSync(join(workspaceRoot, 'package.json'))) { const packageJson = JSON.parse(readFileSync(join(workspaceRoot, 'package.json')).toString()); Object.keys(packageJson.dependencies).forEach((dependency: string) => { dependencies[dependency] = packageJson.dependencies[dependency]; }); } // TODO should we throw? - } else { - externalDependencies.forEach(externalDependency => { - const packageVersion = findPackageVersion(externalDependency); - if (packageVersion) { dependencies[externalDependency] = packageVersion; } - }); } - } // TODO should we throw? - return defaultPackage(dependencies, devDependencies, options); + } + // TODO should we throw? + return defaultPackage(dependencies, devDependencies, options, main); }; export const deployToFunction = async ( @@ -142,74 +174,185 @@ export const deployToFunction = async ( const staticOut = staticBuildOptions.outputPath; const serverOut = serverBuildOptions.outputPath; - const newClientPath = join(dirname(staticOut), staticOut); - const newServerPath = join(dirname(serverOut), serverOut); - // This is needed because in the server output there's a hardcoded dependency on $cwd/dist/browser, - // This assumes that we've deployed our application dist directory and we're running the server - // in the parent directory. To have this precondition, we move dist/browser to dist/dist/browser - // since the firebase function runs the server from dist. - fsHost.moveSync(staticOut, newClientPath); - fsHost.moveSync(serverOut, newServerPath); + const functionsOut = options.outputPath || dirname(serverOut); + const functionName = options.functionName || DEFAULT_FUNCTION_NAME; + + const newStaticOut = join(functionsOut, staticOut); + const newServerOut = join(functionsOut, serverOut); + + // New behavior vs. old + if (options.outputPath) { + fsHost.removeSync(functionsOut); + fsHost.copySync(staticOut, newStaticOut); + fsHost.copySync(serverOut, newServerOut); + } else { + fsHost.moveSync(staticOut, newStaticOut); + fsHost.moveSync(serverOut, newServerOut); + } const packageJson = getPackageJson(context, workspaceRoot, options); - const nodeVersion = JSON.parse(packageJson).engines.node; + const nodeVersion = packageJson.engines.node; - if (!satisfies(process.versions.node, getVersionRange(nodeVersion))) { + if (!satisfies(process.versions.node, nodeVersion.toString())) { context.logger.warn( `⚠️ Your Node.js version (${process.versions.node}) does not match the Firebase Functions runtime (${nodeVersion}).` ); } fsHost.writeFileSync( - join(dirname(serverOut), 'package.json'), - packageJson + join(functionsOut, 'package.json'), + JSON.stringify(packageJson, null, 2) ); fsHost.writeFileSync( - join(dirname(serverOut), 'index.js'), - defaultFunction(serverOut, options) + join(functionsOut, 'index.js'), + defaultFunction(serverOut, options, functionName) ); - try { - fsHost.renameSync( - join(newClientPath, 'index.html'), - join(newClientPath, 'index.original.html') - ); - } catch (e) { } + if (!options.prerender) { + try { + fsHost.renameSync( + join(newStaticOut, 'index.html'), + join(newStaticOut, 'index.original.html') + ); + } catch (e) { } + } + + // tslint:disable-next-line:no-non-null-assertion + const project = context.target!.project; if (options.preview) { - const port = 5000; // TODO make this configurable - - setTimeout(() => { - open(`http://localhost:${port}`); - }, 1500); - - return firebaseTools.serve({ port, targets: ['hosting', 'functions'], host: 'localhost'}).then(() => - require('inquirer').prompt({ - type: 'confirm', - name: 'deployProject', - message: 'Would you like to deploy your application to Firebase Hosting & Cloud Functions?' - }) - ).then(({ deployProject }: { deployProject: boolean }) => { - if (deployProject) { - return firebaseTools.deploy({ - // tslint:disable-next-line:no-non-null-assertion - only: `hosting:${context.target!.project},functions:ssr`, - cwd: workspaceRoot - }); - } else { - return Promise.resolve(); - } + + await firebaseTools.serve({ + port: DEFAULT_EMULATOR_PORT, + host: DEFAULT_EMULATOR_HOST, + targets: [`hosting:${project}`, `functions:${functionName}`], + nonInteractive: true }); - } else { - return firebaseTools.deploy({ - // tslint:disable-next-line:no-non-null-assertion - only: `hosting:${context.target!.project},functions:ssr`, - cwd: workspaceRoot, - token: firebaseToken, + + const { deployProject} = await inquirer.prompt({ + type: 'confirm', + name: 'deployProject', + message: 'Would you like to deploy your application to Firebase Hosting & Cloud Functions?' }); + + if (!deployProject) { return; } + } + + return await firebaseTools.deploy({ + only: `hosting:${project},functions:${functionName}`, + cwd: workspaceRoot, + token: firebaseToken, + nonInteractive: true, + }); + +}; + + +export const deployToCloudRun = async ( + firebaseTools: FirebaseTools, + context: BuilderContext, + workspaceRoot: string, + staticBuildTarget: BuildTarget, + serverBuildTarget: BuildTarget, + options: DeployBuilderOptions, + firebaseToken?: string, + fsHost: FSHost = defaultFsHost +) => { + + const staticBuildOptions = await context.getTargetOptions(targetFromTargetString(staticBuildTarget.name)); + if (!staticBuildOptions.outputPath || typeof staticBuildOptions.outputPath !== 'string') { + throw new Error( + `Cannot read the output path option of the Angular project '${staticBuildTarget.name}' in angular.json` + ); } + + const serverBuildOptions = await context.getTargetOptions(targetFromTargetString(serverBuildTarget.name)); + if (!serverBuildOptions.outputPath || typeof serverBuildOptions.outputPath !== 'string') { + throw new Error( + `Cannot read the output path option of the Angular project '${serverBuildTarget.name}' in angular.json` + ); + } + + const staticOut = staticBuildOptions.outputPath; + const serverOut = serverBuildOptions.outputPath; + + // TODO pull these from firebase config + const cloudRunOut = options.outputPath || staticBuildOptions.outputPath.replace('/browser', '/run'); + const serviceId = options.functionName || DEFAULT_FUNCTION_NAME; + + const newStaticOut = join(cloudRunOut, staticOut); + const newServerOut = join(cloudRunOut, serverOut); + + // This is needed because in the server output there's a hardcoded dependency on $cwd/dist/browser, + // This assumes that we've deployed our application dist directory and we're running the server + // in the parent directory. To have this precondition, we move dist/browser to dist/dist/browser + // since the firebase function runs the server from dist. + fsHost.removeSync(cloudRunOut); + fsHost.copySync(staticOut, newStaticOut); + fsHost.copySync(serverOut, newServerOut); + + const packageJson = getPackageJson(context, workspaceRoot, options, join(serverOut, 'main.js')); + const nodeVersion = packageJson.engines.node; + + if (!satisfies(process.versions.node, nodeVersion.toString())) { + context.logger.warn( + `⚠️ Your Node.js version (${process.versions.node}) does not match the Cloud Run runtime (${nodeVersion}).` + ); + } + + fsHost.writeFileSync( + join(cloudRunOut, 'package.json'), + JSON.stringify(packageJson, null, 2), + ); + + fsHost.writeFileSync( + join(cloudRunOut, 'Dockerfile'), + dockerfile(options) + ); + + if (!options.prerender) { + try { + fsHost.renameSync( + join(newStaticOut, 'index.html'), + join(newStaticOut, 'index.original.html') + ); + } catch (e) { } + } + + if (options.preview) { + throw new SchematicsException('Cloud Run preview not supported.'); + } + + const deployArguments: Array = []; + const cloudRunOptions = options.cloudRunOptions || {}; + Object.entries(DEFAULT_CLOUD_RUN_OPTIONS).forEach(([k, v]) => { + cloudRunOptions[k] ||= v; + }); + // lean on the schema for validation (rather than sanitize) + if (cloudRunOptions.cpus) { deployArguments.push('--cpu', cloudRunOptions.cpus); } + if (cloudRunOptions.maxConcurrency) { deployArguments.push('--concurrency', cloudRunOptions.maxConcurrency); } + if (cloudRunOptions.maxInstances) { deployArguments.push('--max-instances', cloudRunOptions.maxInstances); } + if (cloudRunOptions.memory) { deployArguments.push('--memory', cloudRunOptions.memory); } + if (cloudRunOptions.minInstances) { deployArguments.push('--min-instances', cloudRunOptions.minInstances); } + if (cloudRunOptions.timeout) { deployArguments.push('--timeout', cloudRunOptions.timeout); } + if (cloudRunOptions.vpcConnector) { deployArguments.push('--vpc-connector', cloudRunOptions.vpcConnector); } + + // TODO validate serviceId, firebaseProject, and vpcConnector both to limit errors and opp for injection + + context.logger.info(`📦 Deploying to Cloud Run`); + await spawnAsync(`gcloud builds submit ${cloudRunOut} --tag gcr.io/${options.firebaseProject}/${serviceId} --project ${options.firebaseProject} --quiet`); + await spawnAsync(`gcloud run deploy ${serviceId} --image gcr.io/${options.firebaseProject}/${serviceId} --project ${options.firebaseProject} ${deployArguments.join(' ')} --platform managed --allow-unauthenticated --region=${options.region} --quiet`); + + // TODO deploy cloud run + return await firebaseTools.deploy({ + // tslint:disable-next-line:no-non-null-assertion + only: `hosting:${context.target!.project}`, + cwd: workspaceRoot, + token: firebaseToken, + nonInteractive: true, + }); }; export default async function deploy( @@ -217,6 +360,7 @@ export default async function deploy( context: BuilderContext, staticBuildTarget: BuildTarget, serverBuildTarget: BuildTarget | undefined, + prerenderBuildTarget: BuildTarget | undefined, firebaseProject: string, options: DeployBuilderOptions, firebaseToken?: string, @@ -225,24 +369,37 @@ export default async function deploy( await firebaseTools.login(); } - if (!context.target) { - throw new Error('Cannot execute the build target'); - } - - context.logger.info(`📦 Building "${context.target.project}"`); - - const run = await context.scheduleTarget( - targetFromTargetString(staticBuildTarget.name), - staticBuildTarget.options - ); - await run.result; + if (prerenderBuildTarget) { - if (serverBuildTarget) { const run = await context.scheduleTarget( - targetFromTargetString(serverBuildTarget.name), - serverBuildTarget.options + targetFromTargetString(prerenderBuildTarget.name), + prerenderBuildTarget.options ); await run.result; + + } else { + + if (!context.target) { + throw new Error('Cannot execute the build target'); + } + + context.logger.info(`📦 Building "${context.target.project}"`); + + const builders = [ + context.scheduleTarget( + targetFromTargetString(staticBuildTarget.name), + staticBuildTarget.options + ).then(run => run.result) + ]; + + if (serverBuildTarget) { + builders.push(context.scheduleTarget( + targetFromTargetString(serverBuildTarget.name), + serverBuildTarget.options + ).then(run => run.result)); + } + + await Promise.all(builders); } try { @@ -251,23 +408,30 @@ export default async function deploy( throw new Error(`Cannot select firebase project '${firebaseProject}'`); } - try { - const winston = require('winston'); - const tripleBeam = require('triple-beam'); - - const logger = new winston.transports.Console({ - level: 'info', - format: winston.format.printf((info) => - [info.message, ...(info[tripleBeam.SPLAT] || [])] - .filter((chunk) => typeof chunk === 'string') - .join(' ') - ) - }); + options.firebaseProject = firebaseProject; + + const logger = new winston.transports.Console({ + level: 'info', + format: winston.format.printf((info) => { + const emulator = info[tripleBeam.SPLAT as any]?.[1]?.metadata?.emulator; + const text = info[tripleBeam.SPLAT as any]?.[0]; + if (text?.replace) { + const plainText = text.replace(/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]/g, ''); + if (emulator?.name === 'hosting' && plainText.startsWith('Local server: ')) { + open(plainText.split(': ')[1]); + } + } + return [info.message, ...(info[tripleBeam.SPLAT as any] || [])] + .filter((chunk) => typeof chunk === 'string') + .join(' '); + }) + }); - firebaseTools.logger.logger.add(logger); + firebaseTools.logger.logger.add(logger); - if (serverBuildTarget) { - await deployToFunction( + if (serverBuildTarget) { + if (options.ssr === 'cloud-run') { + await deployToCloudRun( firebaseTools, context, context.workspaceRoot, @@ -277,16 +441,24 @@ export default async function deploy( firebaseToken, ); } else { - await deployToHosting( + await deployToFunction( firebaseTools, context, context.workspaceRoot, + staticBuildTarget, + serverBuildTarget, options, firebaseToken, ); } - - } catch (e) { - context.logger.error(e.message || e); + } else { + await deployToHosting( + firebaseTools, + context, + context.workspaceRoot, + options, + firebaseToken, + ); } + } diff --git a/src/schematics/deploy/builder.ts b/src/schematics/deploy/builder.ts index a188d6225..42ffc7b59 100644 --- a/src/schematics/deploy/builder.ts +++ b/src/schematics/deploy/builder.ts @@ -1,7 +1,8 @@ import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; import deploy, { DeployBuilderOptions } from './actions'; import { BuildTarget } from '../interfaces'; -import { getFirebaseProjectName } from '../utils'; +import { getFirebaseProjectNameFromFs } from '../utils'; +import { getFirebaseTools } from '../firebaseTools'; // Call the createBuilder() function to create a builder. This mirrors // createJobHandler() but add typings specific to Architect Builders. @@ -11,31 +12,51 @@ export default createBuilder( throw new Error('Cannot deploy the application without a target'); } - const firebaseProject = options.firebaseProject || getFirebaseProjectName( + const [defaultFirebaseProject, defulatFirebaseHostingSite] = getFirebaseProjectNameFromFs( context.workspaceRoot, context.target.project ); + const firebaseProject = options.firebaseProject || defaultFirebaseProject; if (!firebaseProject) { - throw new Error('Cannot find firebase project for your app in .firebaserc'); + throw new Error('Cannot detirmine the Firebase Project from your angular.json or .firebaserc'); + } + if (firebaseProject !== defaultFirebaseProject) { + throw new Error('The Firebase Project specified by your angular.json or .firebaserc is in conflict'); + } + + const firebaseHostingSite = options.firebaseHostingSite || defulatFirebaseHostingSite; + if (!firebaseHostingSite) { + throw new Error(`Cannot detirmine the Firebase Hosting Site from your angular.json or .firebaserc`); + } + if (firebaseHostingSite !== defulatFirebaseHostingSite) { + throw new Error('The Firebase Hosting Site specified by your angular.json or .firebaserc is in conflict'); } - const staticBuildTarget = { name: options.buildTarget || `${context.target.project}:build:production` }; + const staticBuildTarget = { name: options.browserTarget || options.buildTarget || `${context.target.project}:build:production` }; + + let prerenderBuildTarget: BuildTarget | undefined; + if (options.prerender) { + prerenderBuildTarget = { + name: options.prerenderTarget || `${context.target.project}:prerender:production` + }; + } let serverBuildTarget: BuildTarget | undefined; if (options.ssr) { serverBuildTarget = { - name: options.universalBuildTarget || `${context.target.project}:server:production` + name: options.serverTarget || options.universalBuildTarget || `${context.target.project}:server:production` }; } try { process.env.FIREBASE_DEPLOY_AGENT = 'angularfire'; await deploy( - require('firebase-tools'), + (await getFirebaseTools()), context, staticBuildTarget, serverBuildTarget, + prerenderBuildTarget, firebaseProject, options, process.env.FIREBASE_TOKEN, diff --git a/src/schematics/deploy/functions-templates.ts b/src/schematics/deploy/functions-templates.ts index d28342232..207212b43 100644 --- a/src/schematics/deploy/functions-templates.ts +++ b/src/schematics/deploy/functions-templates.ts @@ -1,9 +1,10 @@ import { DeployBuilderOptions } from './actions'; -// TODO allow these to be configured -const DEFAULT_NODE_VERSION = 10; -const DEFAULT_FUNCTION_NAME = 'ssr'; +export const DEFAULT_NODE_VERSION = 14; +export const DEFAULT_FUNCTION_NAME = 'ssr'; + const DEFAULT_FUNCTION_REGION = 'us-central1'; + const DEFAULT_RUNTIME_OPTIONS = { timeoutSeconds: 60, memory: '1GB' @@ -13,29 +14,26 @@ export const defaultPackage = ( dependencies: {[key: string]: string}, devDependencies: {[key: string]: string}, options: DeployBuilderOptions, -) => `{ - "name": "functions", - "description": "Angular Universal Application", - "scripts": { - "lint": "", - "serve": "firebase serve --only functions", - "shell": "firebase functions:shell", - "start": "npm run shell", - "deploy": "firebase deploy --only functions", - "logs": "firebase functions:log" + main?: string, +) => ({ + name: 'functions', + description: 'Angular Universal Application', + main: main ?? 'index.js', + scripts: { + start: main ? `node ${main}` : 'firebase functions:shell', }, - "engines": { - "node": "${options.functionsNodeVersion || DEFAULT_NODE_VERSION}" + engines: { + node: (options.functionsNodeVersion || DEFAULT_NODE_VERSION).toString() }, - "dependencies": ${JSON.stringify(dependencies, null, 4)}, - "devDependencies": ${JSON.stringify(devDependencies, null, 4)}, - "private": true -} -`; + dependencies, + devDependencies, + private: true +}); export const defaultFunction = ( path: string, options: DeployBuilderOptions, + functionName: string|undefined, ) => `const functions = require('firebase-functions'); // Increase readability in Cloud Logging @@ -43,9 +41,19 @@ require("firebase-functions/lib/logger/compat"); const expressApp = require('./${path}/main').app(); -exports.${DEFAULT_FUNCTION_NAME} = functions - .region('${DEFAULT_FUNCTION_REGION}') +exports.${functionName || DEFAULT_FUNCTION_NAME} = functions + .region('${options.region || DEFAULT_FUNCTION_REGION}') .runWith(${JSON.stringify(options.functionsRuntimeOptions || DEFAULT_RUNTIME_OPTIONS)}) .https .onRequest(expressApp); `; + +export const dockerfile = ( + options: DeployBuilderOptions, +) => `FROM node:${options.functionsNodeVersion || DEFAULT_NODE_VERSION}-slim +WORKDIR /usr/src/app +COPY package*.json ./ +RUN npm install --only=production +COPY . ./ +CMD [ "npm", "start" ] +`; diff --git a/src/schematics/deploy/schema.json b/src/schematics/deploy/schema.json index 5f426e6c6..94c13ccf6 100644 --- a/src/schematics/deploy/schema.json +++ b/src/schematics/deploy/schema.json @@ -9,21 +9,103 @@ "description": "Target to build.", "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$" }, + "browserTarget": { + "type": "string", + "description": "Target to build.", + "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$" + }, + "prerenderTarget": { + "type": "string", + "description": "Target to build.", + "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$" + }, + "serverTarget": { + "type": "string", + "description": "Target to build.", + "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$" + }, + "universalBuildTarget": { + "type": "string", + "description": "Target to build.", + "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$" + }, + "ssr": { + "type": ["boolean", "string"], + "description": "Should we attempt to deploy the function to Cloud Functions (true or 'cloud-functions') / Cloud Run ('cloud-run') or just Hosting (false)" + }, + "prerender": { + "type": "boolean", + "description": "Prerender before deploy?" + }, "firebaseProject": { "type": "string", "description": "The Firebase project name or project alias to use when deploying" }, + "firebaseHostingSite": { + "type": "string", + "description": "The Firebase Hosting site to deploy to" + }, + "functionName": { + "type": "string", + "description": "The name of the Cloud Function or Cloud Run serviceId to deploy SSR to" + }, "functionsNodeVersion": { - "type": "number", - "description": "Version of Node.js to run Cloud Functions on" + "type": ["number", "string"], + "description": "Version of Node.js to run Cloud Functions / Run on" + }, + "region": { + "type": "string", + "description": "The region to deploy Cloud Functions or Cloud Run to" + }, + "outputPath": { + "type": "string", + "description": "Where to output the deploy artifacts" }, "functionsRuntimeOptions": { "type": "object", - "description": "Runtime options for Cloud Functions" + "description": "Runtime options for Cloud Functions, if deploying to Cloud Functions" }, "preview": { "type": "boolean", "description": "Do not deploy the application, just set up the Firebase Function in the project output directory. Can be used for testing the Firebase Function with `firebase serve`." + }, + "cloudRunOptions": { + "type": "object", + "description": "Options passed to Cloud Run, if deploying to Cloud Run.", + "properties": { + "cpus": { + "type": "number", + "description": "Set a CPU limit in Kubernetes cpu units." + }, + "maxConcurrency": { + "type": ["number", "string"], + "pattern": "^(\\d+|default)$", + "description": "Set the maximum number of concurrent requests allowed per container instance. If concurrency is unspecified, any number of concurrent requests are allowed. To unset this field, provide the special value default." + }, + "maxInstances": { + "type": ["number", "string"], + "pattern": "^(\\d+|default)$", + "description": "The maximum number of container instances of the Service to run. Use 'default' to unset the limit and use the platform default." + }, + "memory": { + "type": "string", + "pattern": "^\\d+(G|M)i$", + "description": "Set a memory limit. Ex: 1Gi, 512Mi." + }, + "minInstances": { + "type": ["number", "string"], + "pattern": "^(\\d+|default)$", + "description": "The minimum number of container instances of the Service to run or 'default' to remove any minimum." + }, + "timeout": { + "type": "number", + "description": "Set the maximum request execution time (timeout) in seconds." + }, + "vpcConnector": { + "type": "string", + "description": "Set a VPC connector for this resource." + } + } } } } diff --git a/src/schematics/firebaseTools.ts b/src/schematics/firebaseTools.ts new file mode 100644 index 000000000..5ed2606a6 --- /dev/null +++ b/src/schematics/firebaseTools.ts @@ -0,0 +1,49 @@ +import { FirebaseTools } from './interfaces'; +import { spawn, execSync } from 'child_process'; +import ora from 'ora'; + +declare global { + var firebaseTools: FirebaseTools|undefined; +} + +export const getFirebaseTools = () => globalThis.firebaseTools ? + Promise.resolve(globalThis.firebaseTools) : + new Promise((resolve, reject) => { + try { + resolve(require('firebase-tools')); + } catch (e) { + try { + const root = execSync('npm root -g').toString().trim(); + resolve(require(`${root}/firebase-tools`)); + } catch (e) { + const spinner = ora({ + text: `Installing firebase-tools...`, + // Workaround for https://github.com/sindresorhus/ora/issues/136. + discardStdin: process.platform !== 'win32', + }).start(); + spawn('npm', ['i', '-g', 'firebase-tools'], { + stdio: 'pipe', + shell: true, + }).on('close', (code) => { + if (code === 0) { + spinner.succeed('firebase-tools installed globally.'); + spinner.stop(); + const root = execSync('npm root -g').toString().trim(); + resolve(require(`${root}/firebase-tools`)); + } else { + spinner.fail('Package install failed.'); + reject(); + } + }); + } + } + }).then(firebaseTools => { + globalThis.firebaseTools = firebaseTools; + const version = firebaseTools.cli.version(); + console.log(`Using firebase-tools version ${version}`); + if (parseInt(version, 10) !== 9) { + console.error('firebase-tools version 9 is required'); + return Promise.reject(); + } + return firebaseTools; + }); diff --git a/src/schematics/interfaces.ts b/src/schematics/interfaces.ts index 7cfbc60c1..22012e6a7 100644 --- a/src/schematics/interfaces.ts +++ b/src/schematics/interfaces.ts @@ -1,22 +1,108 @@ import { RuntimeOptions } from 'firebase-functions'; -export interface Project { +export const enum FEATURES { + Hosting, + Authentication, + Analytics, + Database, + Functions, + Messaging, + Performance, + Firestore, + Storage, + RemoteConfig, +} + +export const featureOptions = [ + { name: 'ng deploy -- hosting', value: FEATURES.Hosting }, + { name: 'Authentication', value: FEATURES.Authentication }, + { name: 'Firestore', value: FEATURES.Firestore }, + { name: 'Realtime Database', value: FEATURES.Database }, + { name: 'Analytics', value: FEATURES.Analytics }, + { name: 'Cloud Functions (callable)', value: FEATURES.Functions }, + { name: 'Cloud Messaging', value: FEATURES.Messaging }, + { name: 'Performance Monitoring', value: FEATURES.Performance }, + { name: 'Cloud Storage', value: FEATURES.Storage }, + { name: 'Remote Config', value: FEATURES.RemoteConfig }, +]; + +export const enum PROJECT_TYPE { Static, CloudFunctions, CloudRun } + +export interface NgAddOptions { + firebaseProject: string; + project?: string; +} + +export interface NgAddNormalizedOptions { + project: string; + firebaseProject: FirebaseProject; + firebaseApp: FirebaseApp|undefined; + firebaseHostingSite: FirebaseHostingSite|undefined; + sdkConfig: Record|undefined; + prerender: boolean; + browserTarget: string|undefined; + serverTarget: string|undefined; + prerenderTarget: string|undefined; +} + +export interface DeployOptions { + project: string; +} + +export interface FirebaseProject { projectId: string; projectNumber: string; displayName: string; name: string; resources: { [key: string]: string }; + state: string; } export interface FirebaseDeployConfig { cwd: string; only?: string; token?: string; + [key: string]: any; +} + +export interface FirebaseApp { + name: string; + displayName: string; + platform: string; + appId: string; + namespace: string; +} + +export interface FirebaseHostingSite { + name: string; + defaultUrl: string; + type: string; + appId: string|undefined; +} + +export interface FirebaseSDKConfig { + fileName: string; + fileContents: string; + sdkConfig: { [key: string]: string }; } export interface FirebaseTools { projects: { - list(): Promise; + list(options: any): Promise; + create(projectId: string|undefined, options: any): Promise; + }; + + apps: { + list(platform: string|undefined, options: any): Promise; + create(platform: string, displayName: string|undefined, options: any): Promise; + sdkconfig(type: string, projectId: string, options: any): Promise; + }; + + hosting: { + sites: { + list(options: any): Promise<{ sites: FirebaseHostingSite[]}>; + create(siteId: string, options: any): Promise; + } }; logger: { @@ -67,16 +153,36 @@ export interface FirebaseRcTarget { export interface FirebaseRc { targets?: Record; + projects?: Record; } export interface DeployBuilderSchema { buildTarget?: string; + browserTarget?: string; firebaseProject?: string; + firebaseHostingSite?: string; preview?: boolean; universalBuildTarget?: string; - ssr?: boolean; - functionsNodeVersion?: number; + serverTarget?: string; + prerenderTarget?: string; + ssr?: boolean | string; + region?: string; + prerender?: boolean; + functionName?: string; + functionsNodeVersion?: number|string; functionsRuntimeOptions?: RuntimeOptions; + cloudRunOptions?: Partial; + outputPath?: string; +} + +export interface CloudRunOptions { + cpus: number; + maxConcurrency: number | 'default'; + maxInstances: number | 'default'; + memory: string; + minInstances: number | 'default'; + timeout: number; + vpcConnector: string; } export interface BuildTarget { @@ -88,11 +194,20 @@ export interface FSHost { moveSync(src: string, dest: string): void; writeFileSync(src: string, data: string): void; renameSync(src: string, dest: string): void; + copySync(src: string, dest: string): void; + removeSync(src: string): void; } export interface WorkspaceProject { + root: string; + sourceRoot?: string; projectType?: string; - architect?: Record }>; + architect?: Record, + configurations?: Record>, + defaultConfiguration?: string, + }>; } export interface Workspace { diff --git a/src/schematics/migration.json b/src/schematics/migration.json index 459179314..453d5a9e3 100644 --- a/src/schematics/migration.json +++ b/src/schematics/migration.json @@ -4,11 +4,11 @@ "migration-v7": { "version": "7.0.0", "description": "Update @angular/fire to v7", - "factory": "./public_api#ngUpdateV7" + "factory": "./update/v7#ngUpdate" }, "ng-post-upgate": { "description": "Print out results after ng-update", - "factory": "./public_api#ngPostUpdate", + "factory": "./update#ngPostUpdate", "private": true } } diff --git a/src/schematics/ng-add-ssr.ts b/src/schematics/ng-add-ssr.ts deleted file mode 100644 index 5b9005e79..000000000 --- a/src/schematics/ng-add-ssr.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { SchematicsException, Tree, SchematicContext, noop } from '@angular-devkit/schematics'; -import { - addDependencies, - generateFirebaseRc, - NgAddNormalizedOptions, - overwriteIfExists, - safeReadJSON, - stringifyFormatted -} from './ng-add-common'; -import { FirebaseJSON, Workspace, WorkspaceProject } from './interfaces'; -import { firebaseFunctions as firebaseFunctionsDependencies } from './versions.json'; -import { dirname, join } from 'path'; -import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; - -// We consider a project to be a universal project if it has a `server` architect -// target. If it does, it knows how to build the application's server. -export const isUniversalApp = ( - project: WorkspaceProject -) => project.architect && project.architect.server; - -function emptyFirebaseJson(source: string) { - return { - hosting: [], - functions: { - source - } - }; -} - -function generateHostingConfig(project: string, dist: string) { - return { - target: project, - public: join(dirname(dist), dist), - ignore: ['**/.*'], - headers: [{ - source: '*.[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].+(css|js)', - headers: [{ - key: 'Cache-Control', - value: 'public,max-age=31536000,immutable' - }] - }], - rewrites: [ - { - source: '**', - function: 'ssr' - } - ] - }; -} - -function generateFunctionsConfig(dist: string) { - return { - source: dirname(dist) - }; -} - -export function generateFirebaseJson( - tree: Tree, - path: string, - project: string, - dist: string, - serverOutput: string -) { - const firebaseJson: FirebaseJSON = tree.exists(path) - ? safeReadJSON(path, tree) - : emptyFirebaseJson(dirname(serverOutput)); - - /* TODO do we want to prompt for override? - if ( - firebaseJson.hosting && - ((Array.isArray(firebaseJson.hosting) && - firebaseJson.hosting.find(config => config.target === project)) || - (firebaseJson.hosting).target === project) - ) { - throw new SchematicsException( - `Target ${project} already exists in firebase.json` - ); - }*/ - - const newConfig = generateHostingConfig(project, dist); - if (firebaseJson.hosting === undefined) { - firebaseJson.hosting = newConfig; - } else if (Array.isArray(firebaseJson.hosting)) { - const existingConfigIndex = firebaseJson.hosting.findIndex(config => config.target === newConfig.target); - if (existingConfigIndex > -1) { - firebaseJson.hosting.splice(existingConfigIndex, 1, newConfig); - } else { - firebaseJson.hosting.push(newConfig); - } - } else { - firebaseJson.hosting = [firebaseJson.hosting, newConfig]; - } - - firebaseJson.functions = generateFunctionsConfig(dist); - - overwriteIfExists(tree, path, stringifyFormatted(firebaseJson)); -} - -export const addFirebaseFunctionsDependencies = (tree: Tree, context: SchematicContext) => { - addDependencies( - tree, - firebaseFunctionsDependencies, - context - ); - context.addTask(new NodePackageInstallTask()); - return tree; -}; - -export const setupUniversalDeployment = (config: { - project: WorkspaceProject; - options: NgAddNormalizedOptions; - workspacePath: string; - workspace: Workspace; - tree: Tree; -}) => { - const { tree, workspacePath, workspace, options } = config; - const project = workspace.projects[options.project]; - - if ( - !project.architect || - !project.architect.build || - !project.architect.build.options || - !project.architect.build.options.outputPath - ) { - throw new SchematicsException( - `Cannot read the output path (architect.build.options.outputPath) of the Angular project "${options.project}" in angular.json` - ); - } - - if ( - !project.architect || - !project.architect.server || - !project.architect.server.options || - !project.architect.server.options.outputPath - ) { - throw new SchematicsException( - `Cannot read the output path (architect.server.options.outputPath) of the Angular project "${options.project}" in angular.json` - ); - } - - const staticOutput = project.architect.build.options.outputPath; - const serverOutput = project.architect.server.options.outputPath; - - // Add firebase libraries to externalDependencies. For older versions of @firebase/firestore grpc native would cause issues when - // bundled. While, it's using grpc-js now and doesn't have issues, ngcc tends to bundle the esm version of the libraries; which - // is problematic for SSR (references to Window, etc.) Let's just mark all of them as external so we know the CJS is used. - const externalDependencies: string[] = project.architect.server.options.externalDependencies || []; - [ - 'firebase', - '@firebase/app', - '@firebase/analytics', - '@firebase/app', - '@firebase/auth', - '@firebase/component', - '@firebase/database', - '@firebase/firestore', - '@firebase/functions', - '@firebase/installations', - '@firebase/messaging', - '@firebase/storage', - '@firebase/performance', - '@firebase/remote-config', - '@firebase/util' - ].forEach(dep => { - if (!externalDependencies.includes(dep)) { externalDependencies.push(dep); } - }); - - project.architect.server.options.externalDependencies = externalDependencies; - - project.architect.deploy = { - builder: '@angular/fire:deploy', - options: { - ssr: true - } - }; - - tree.overwrite(workspacePath, JSON.stringify(workspace, null, 2)); - - generateFirebaseJson(tree, 'firebase.json', options.project, staticOutput, serverOutput); - generateFirebaseRc( - tree, - '.firebaserc', - options.firebaseProject, - options.project - ); - - return tree; -}; diff --git a/src/schematics/ng-add.jasmine.ts b/src/schematics/ng-add.jasmine.ts index 4259272a3..98f919566 100644 --- a/src/schematics/ng-add.jasmine.ts +++ b/src/schematics/ng-add.jasmine.ts @@ -1,8 +1,8 @@ -import { Tree } from '@angular-devkit/schematics'; -import { setupProject } from './ng-add'; +import { SchematicsException, Tree } from '@angular-devkit/schematics'; +import { setupProject } from '@angular/fire/schematics/setup'; import 'jasmine'; -import { join } from '@angular-devkit/core'; import { join as pathJoin } from 'path'; +import { FEATURES, PROJECT_TYPE } from './interfaces'; const PROJECT_NAME = 'pie-ka-chu'; const PROJECT_ROOT = 'pirojok'; @@ -40,6 +40,13 @@ function generateAngularJson() { }; } +function generatePackageJson() { + return { + name: 'foo', + private: true, + }; +} + function generateAngularJsonWithServer() { return { defaultProject: PROJECT_NAME, @@ -118,6 +125,9 @@ const initialFirebaserc = `{ ] } } + }, + \"projects\": { + \"default\": \"pirojok-111e3\" } }`; @@ -135,7 +145,11 @@ const initialAngularJson = `{ }, \"deploy\": { \"builder\": \"@angular/fire:deploy\", - \"options\": {} + \"options\": { + \"prerender\": false, + \"ssr\": false, + \"firebaseProject\": \"pirojok-111e3\" + } } } }, @@ -191,6 +205,9 @@ const overwriteFirebaserc = `{ ] } } + }, + \"projects\": { + \"default\": \"pirojok-111e3\" } }`; @@ -208,7 +225,11 @@ const overwriteAngularJson = `{ }, \"deploy\": { \"builder\": \"@angular/fire:deploy\", - \"options\": {} + \"options\": { + \"prerender\": false, + \"ssr\": false, + \"firebaseProject\": \"pirojok-111e3\" + } } } }, @@ -295,6 +316,9 @@ const projectFirebaserc = `{ ] } } + }, + \"projects\": { + \"default\": \"bi-catch-you-77e7e\" } }`; @@ -312,7 +336,11 @@ const projectAngularJson = `{ }, \"deploy\": { \"builder\": \"@angular/fire:deploy\", - \"options\": {} + \"options\": { + \"prerender": false, + \"ssr\": false, + \"firebaseProject\": \"pirojok-111e3\" + } } } }, @@ -327,7 +355,11 @@ const projectAngularJson = `{ }, \"deploy\": { \"builder\": \"@angular/fire:deploy\", - \"options\": {} + \"options\": { + \"prerender\": false, + \"ssr\": false, + \"firebaseProject\": \"bi-catch-you-77e7e\" + } } } } @@ -337,7 +369,7 @@ const projectAngularJson = `{ const universalFirebaseJson = { hosting: [{ target: 'pie-ka-chu', - public: pathJoin('dist', 'dist', 'ikachu'), + public: 'dist/ikachu', ignore: [ '**/.*' ], @@ -351,12 +383,12 @@ const universalFirebaseJson = { rewrites: [ { source: '**', - function: 'ssr' + function: 'ssr_pie-ka-chu' } ] }], functions: { - source: 'dist' + source: 'dist/pie-ka-chu/functions' } }; @@ -367,13 +399,15 @@ describe('ng-add', () => { beforeEach(() => { tree = Tree.empty(); tree.create('angular.json', JSON.stringify(generateAngularJson())); + tree.create('package.json', JSON.stringify(generatePackageJson())); }); it('generates new files if starting from scratch', async () => { - const result = await setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - universalProject: false, - project: PROJECT_NAME + const result = await setupProject(tree, {} as any, [FEATURES.Hosting], { + firebaseProject: { projectId: FIREBASE_PROJECT } as any, + projectType: PROJECT_TYPE.Static, + project: PROJECT_NAME, + prerender: false, }); expect(result.read('firebase.json').toString()).toEqual(initialFirebaseJson); expect(result.read('.firebaserc').toString()).toEqual(initialFirebaserc); @@ -381,10 +415,11 @@ describe('ng-add', () => { }); it('uses default project', async () => { - const result = await setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - universalProject: false, - project: undefined + const result = await setupProject(tree, {} as any, [FEATURES.Hosting], { + firebaseProject: { projectId: FIREBASE_PROJECT } as any, + projectType: PROJECT_TYPE.Static, + project: undefined, + prerender: false, }); expect(result.read('firebase.json').toString()).toEqual(overwriteFirebaseJson); expect(result.read('.firebaserc').toString()).toEqual(overwriteFirebaserc); @@ -392,14 +427,17 @@ describe('ng-add', () => { }); it('overrides existing files', async () => { - const tempTree = await setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - universalProject: false, project: PROJECT_NAME + const tempTree = await setupProject(tree, {} as any, [FEATURES.Hosting], { + firebaseProject: { projectId: FIREBASE_PROJECT } as any, + projectType: PROJECT_TYPE.Static, + project: PROJECT_NAME, + prerender: false, }); - const result = await setupProject(tempTree, { - firebaseProject: OTHER_FIREBASE_PROJECT_NAME, + const result = await setupProject(tree, {} as any, [FEATURES.Hosting], { + firebaseProject: { projectId: OTHER_FIREBASE_PROJECT_NAME } as any, + projectType: PROJECT_TYPE.Static, project: OTHER_PROJECT_NAME, - universalProject: false + prerender: false, }); expect(result.read('firebase.json').toString()).toEqual(projectFirebaseJson); expect(result.read('.firebaserc').toString()).toEqual(projectFirebaserc); @@ -408,54 +446,53 @@ describe('ng-add', () => { }); describe('error handling', () => { - it('fails if project not defined', () => { + it('fails if project not defined', async () => { const tree = Tree.empty(); const angularJSON = generateAngularJson(); delete angularJSON.defaultProject; tree.create('angular.json', JSON.stringify(angularJSON)); - expect(() => - setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - universalProject: false, - project: undefined - }) - ).toThrowError( - /No Angular project selected and no default project in the workspace/ + tree.create('package.json', JSON.stringify(generatePackageJson())); + await expectAsync(setupProject(tree, {} as any, [FEATURES.Hosting], { + firebaseProject: { projectId: FIREBASE_PROJECT } as any, + projectType: PROJECT_TYPE.Static, + project: undefined, + prerender: false, + })).toBeRejectedWith( + new SchematicsException('No Angular project selected and no default project in the workspace') ); }); it('Should throw if angular.json not found', async () => { - expect(() => - setupProject(Tree.empty(), { - firebaseProject: FIREBASE_PROJECT, - universalProject: false, - project: PROJECT_NAME - }) - ).toThrowError(/Could not find angular.json/); + await expectAsync(setupProject(Tree.empty(), {} as any, [FEATURES.Hosting], { + firebaseProject: { projectId: FIREBASE_PROJECT } as any, + projectType: PROJECT_TYPE.Static, + project: PROJECT_NAME, + prerender: false, + })).toBeRejectedWith(new SchematicsException('Could not find angular.json')); }); it('Should throw if angular.json can not be parsed', async () => { const tree = Tree.empty(); tree.create('angular.json', 'hi'); - expect(() => - setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - universalProject: false, - project: PROJECT_NAME - }) - ).toThrowError(/Could not parse angular.json/); + tree.create('package.json', JSON.stringify(generatePackageJson())); + await expectAsync(setupProject(tree, {} as any, [FEATURES.Hosting], { + firebaseProject: { projectId: FIREBASE_PROJECT } as any, + projectType: PROJECT_TYPE.Static, + project: PROJECT_NAME, + prerender: false, + })).toBeRejectedWith(new SchematicsException('Could not parse angular.json')); }); it('Should throw if specified project does not exist ', async () => { const tree = Tree.empty(); tree.create('angular.json', JSON.stringify({ projects: {} })); - expect(() => - setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - universalProject: false, - project: PROJECT_NAME - }) - ).toThrowError(/The specified Angular project is not defined in this workspace/); + tree.create('package.json', JSON.stringify(generatePackageJson())); + await expectAsync(setupProject(tree, {} as any, [FEATURES.Hosting], { + firebaseProject: { projectId: FIREBASE_PROJECT } as any, + projectType: PROJECT_TYPE.Static, + project: PROJECT_NAME, + prerender: false, + })).toBeRejectedWith(new SchematicsException('The specified Angular project is not defined in this workspace')); }); it('Should throw if specified project is not application', async () => { @@ -466,13 +503,13 @@ describe('ng-add', () => { projects: { [PROJECT_NAME]: { projectType: 'pokemon' } } }) ); - expect(() => - setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - universalProject: false, - project: PROJECT_NAME - }) - ).toThrowError(/Deploy requires an Angular project type of "application" in angular.json/); + tree.create('package.json', JSON.stringify(generatePackageJson())); + await expectAsync(setupProject(tree, {} as any, [FEATURES.Hosting], { + firebaseProject: { projectId: FIREBASE_PROJECT } as any, + projectType: PROJECT_TYPE.Static, + project: PROJECT_NAME, + prerender: false, + })).toBeRejectedWith(new SchematicsException('Deploy requires an Angular project type of "application" in angular.json')); }); it('Should throw if app does not have architect configured', async () => { @@ -483,13 +520,15 @@ describe('ng-add', () => { projects: { [PROJECT_NAME]: { projectType: 'application' } } }) ); - expect(() => - setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - universalProject: false, - project: PROJECT_NAME - }) - ).toThrowError(/Cannot read the output path/); + tree.create('package.json', JSON.stringify(generatePackageJson())); + await expectAsync(setupProject(tree, {} as any, [FEATURES.Hosting], { + firebaseProject: { projectId: FIREBASE_PROJECT } as any, + projectType: PROJECT_TYPE.Static, + project: PROJECT_NAME, + prerender: false, + })).toBeRejectedWith( + new SchematicsException('Cannot read the output path (architect.build.options.outputPath) of the Angular project "pie-ka-chu" in angular.json') + ); }); /* TODO do something other than throw @@ -527,14 +566,16 @@ describe('ng-add', () => { it('Should throw if .firebaserc is broken', async () => { const tree = Tree.empty(); tree.create('angular.json', JSON.stringify(generateAngularJson())); + tree.create('package.json', JSON.stringify(generatePackageJson())); tree.create('.firebaserc', `I'm broken 😔`); - expect(() => - setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - universalProject: false, - project: PROJECT_NAME - }) - ).toThrowError(/.firebaserc: Unexpected token/); + await expectAsync(setupProject(tree, {} as any, [FEATURES.Hosting], { + firebaseProject: { projectId: FIREBASE_PROJECT } as any, + projectType: PROJECT_TYPE.Static, + project: PROJECT_NAME, + prerender: false, + })).toBeRejectedWith( + new SchematicsException('Error when parsing .firebaserc: Unexpected token I in JSON at position 0') + ); }); /* TODO do something else @@ -580,36 +621,46 @@ describe('ng-add', () => { it('should fail without a server project', async () => { const tree = Tree.empty(); tree.create('angular.json', JSON.stringify(generateAngularJson())); - - expect(() => setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - universalProject: true, - project: PROJECT_NAME - })).toThrowError(/\(architect.server.options.outputPath\) of the Angular project "pie-ka-chu" in angular.json/); + tree.create('package.json', JSON.stringify(generatePackageJson())); + + await expectAsync(setupProject(tree, {} as any, [FEATURES.Hosting], { + firebaseProject: { projectId: FIREBASE_PROJECT } as any, + projectType: PROJECT_TYPE.CloudFunctions, + project: PROJECT_NAME, + prerender: false, + })).toBeRejectedWith( + new SchematicsException('Cannot read the output path (architect.server.options.outputPath) of the Angular project "pie-ka-chu" in angular.json') + ); }); it('should add a @angular/fire builder', async () => { const tree = Tree.empty(); tree.create('angular.json', JSON.stringify(generateAngularJsonWithServer())); - - const result = await setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - universalProject: true, - project: PROJECT_NAME + tree.create('package.json', JSON.stringify(generatePackageJson())); + + // TODO mock addTask + const result = await setupProject(tree, {addTask: () => {}} as any, [FEATURES.Hosting], { + firebaseProject: { projectId: FIREBASE_PROJECT } as any, + projectType: PROJECT_TYPE.CloudFunctions, + project: PROJECT_NAME, + prerender: false, }); const workspace = JSON.parse((await result.read('angular.json')).toString()); - expect(workspace.projects['pie-ka-chu'].architect.deploy.options.ssr).toBeTrue(); + expect(workspace.projects['pie-ka-chu'].architect.deploy.options.ssr).toEqual('cloud-functions'); }); it('should configure firebase.json', async () => { const tree = Tree.empty(); tree.create('angular.json', JSON.stringify(generateAngularJsonWithServer())); - - const result = await setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - universalProject: true, - project: PROJECT_NAME + tree.create('package.json', JSON.stringify(generatePackageJson())); + + // TODO mock addTask + const result = await setupProject(tree, {addTask: () => {}} as any, [FEATURES.Hosting], { + firebaseProject: { projectId: FIREBASE_PROJECT } as any, + projectType: PROJECT_TYPE.CloudFunctions, + project: PROJECT_NAME, + prerender: false, }); const firebaseJson = JSON.parse((await result.read('firebase.json')).toString()); diff --git a/src/schematics/ng-add.ts b/src/schematics/ng-add.ts deleted file mode 100644 index f10ee17ba..000000000 --- a/src/schematics/ng-add.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { SchematicContext, Tree } from '@angular-devkit/schematics'; -import { listProjects, projectPrompt, getWorkspace, getProject, projectTypePrompt } from './utils'; -import { DeployOptions, NgAddNormalizedOptions } from './ng-add-common'; -import { addFirebaseFunctionsDependencies, setupUniversalDeployment } from './ng-add-ssr'; -import { addFirebaseHostingDependencies, setupStaticDeployment } from './ng-add-static'; - -export const setupProject = - (host: Tree, options: DeployOptions & { firebaseProject: string, universalProject: boolean }) => { - const { path: workspacePath, workspace } = getWorkspace(host); - - const {project, projectName} = getProject(options, host); - - const config: NgAddNormalizedOptions = { - project: projectName, - firebaseProject: options.firebaseProject - }; - - if (options.universalProject) { - return setupUniversalDeployment({ - workspace, - workspacePath, - options: config, - tree: host, - project - }); - } else { - return setupStaticDeployment({ - workspace, - workspacePath, - options: config, - tree: host, - project - }); - } -}; - -export const ngAddSetupProject = ( - options: DeployOptions -) => async (host: Tree, context: SchematicContext) => { - - // I'm not able to resolve dependencies.... this is definately some sort of race condition. - // Failing on bluebird but there are a lot of things that aren't right. Error for now. - try { - require('firebase-tools'); - } catch (e) { - throw new Error('The NodePackageInstallTask does not appear to have completed successfully or we ran into a race condition. Please run the `ng add @angular/fire` command again.'); - } - - const projects = await listProjects(); - const { firebaseProject } = await projectPrompt(projects); - const { project } = getProject(options, host); - const { universalProject } = await projectTypePrompt(project); - if (universalProject) { host = addFirebaseFunctionsDependencies(host, context); } - return setupProject(host, {...options, firebaseProject, universalProject }); -}; - -export const ngAdd = addFirebaseHostingDependencies; diff --git a/src/schematics/ngcc-config.ts b/src/schematics/ngcc-config.ts deleted file mode 100644 index 9b6792e41..000000000 --- a/src/schematics/ngcc-config.ts +++ /dev/null @@ -1,70 +0,0 @@ -export const packages = { - firebase: { - entryPoints: { - '.': { override: { main: undefined, browser: undefined } }, - './analytics': { override: { main: undefined, browser: undefined } }, - './app': {override: { main: undefined, browser: undefined } }, - './auth': {override: { main: undefined, browser: undefined } }, - './database': { override: { main: undefined, browser: undefined } }, - './firestore': { override: { main: undefined, browser: undefined } }, - './functions': { override: { main: undefined, browser: undefined } }, - './installations': { override: { main: undefined, browser: undefined } }, - './storage': { override: { main: undefined, browser: undefined } }, - './performance': { override: { main: undefined, browser: undefined } }, - './remote-config': { override: { main: undefined, browser: undefined } }, - }, - generateDeepReexports: true - }, - '@firebase/analytics': { - entryPoints: { '.': { override : { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/app': { - entryPoints: { '.': { override : { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/auth': { - entryPoints: { '.': { override : { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/component': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/database': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/firestore': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/functions': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/installations': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/messaging': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/storage': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/performance': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/remote-config': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/util': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - } -}; diff --git a/src/schematics/public_api.ts b/src/schematics/public_api.ts deleted file mode 100644 index 8d3d1e43d..000000000 --- a/src/schematics/public_api.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './ng-add'; -export * from './deploy/actions'; -export * from './deploy/builder'; -export * from './ng-update'; -export { ngUpdate as ngUpdateV7 } from './update/v7'; diff --git a/src/schematics/setup/index.ts b/src/schematics/setup/index.ts new file mode 100644 index 000000000..92b0852b2 --- /dev/null +++ b/src/schematics/setup/index.ts @@ -0,0 +1,150 @@ +import { SchematicContext, SchematicsException, Tree } from '@angular-devkit/schematics'; +import { getWorkspace, getProject, getFirebaseProjectNameFromHost, addEnvironmentEntry, addToNgModule, addIgnoreFiles } from '../utils'; +import { projectTypePrompt, appPrompt, sitePrompt, projectPrompt, featuresPrompt } from './prompts'; +import { setupUniversalDeployment } from './ssr'; +import { setupStaticDeployment } from './static'; +import { + FirebaseApp, FirebaseHostingSite, FirebaseProject, DeployOptions, NgAddNormalizedOptions, + FEATURES, PROJECT_TYPE +} from '../interfaces'; +import { getFirebaseTools } from '../firebaseTools'; + +export const setupProject = + async (tree: Tree, context: SchematicContext, features: FEATURES[], config: DeployOptions & { + firebaseProject: FirebaseProject, + firebaseApp?: FirebaseApp, + firebaseHostingSite?: FirebaseHostingSite, + sdkConfig?: Record, + projectType: PROJECT_TYPE, + prerender: boolean, + nodeVersion?: string, + browserTarget?: string, + serverTarget?: string, + prerenderTarget?: string, + project: string, + }) => { + const { path: workspacePath, workspace } = getWorkspace(tree); + + const { project, projectName } = getProject(config, tree); + + const sourcePath = [project.root, project.sourceRoot].filter(it => !!it).join('/'); + + addIgnoreFiles(tree); + + const featuresToImport = features.filter(it => it !== FEATURES.Hosting); + if (featuresToImport.length > 0) { + addToNgModule(tree, { features: featuresToImport, sourcePath }); + } + + if (config.sdkConfig) { + const source = ` + firebase: { +${Object.entries(config.sdkConfig).reduce( + (c, [k, v]) => c.concat(` ${k}: '${v}'`), + [] as string[] +).join(',\n')}, + }`; + + const environmentPath = `${sourcePath}/environments/environment.ts`; + addEnvironmentEntry(tree, `/${environmentPath}`, source); + + // Iterate over the replacements for the environment file and add the config + Object.values(project.architect || {}).forEach(builder => { + Object.values(builder.configurations || {}).forEach(configuration => { + (configuration.fileReplacements || []).forEach((replacement: any) => { + if (replacement.replace === environmentPath) { + addEnvironmentEntry(tree, `/${replacement.with}`, source); + } + }); + }); + }); + } + + const options: NgAddNormalizedOptions = { + project: projectName, + firebaseProject: config.firebaseProject, + firebaseApp: config.firebaseApp, + firebaseHostingSite: config.firebaseHostingSite, + sdkConfig: config.sdkConfig, + prerender: config.prerender, + browserTarget: config.browserTarget, + serverTarget: config.serverTarget, + prerenderTarget: config.prerenderTarget, + }; + + if (features.includes(FEATURES.Hosting)) { + // TODO dry up by always doing the static work + switch (config.projectType) { + case PROJECT_TYPE.CloudFunctions: + case PROJECT_TYPE.CloudRun: + return setupUniversalDeployment({ + workspace, + workspacePath, + options, + tree, + context, + project, + projectType: config.projectType, + // tslint:disable-next-line:no-non-null-assertion + nodeVersion: config.nodeVersion!, + }); + case PROJECT_TYPE.Static: + return setupStaticDeployment({ + workspace, + workspacePath, + options, + tree, + context, + project + }); + default: throw(new SchematicsException(`Unimplemented PROJECT_TYPE ${config.projectType}`)); + } + } +}; + +export const ngAddSetupProject = ( + options: DeployOptions +) => async (host: Tree, context: SchematicContext) => { + const features = await featuresPrompt(); + + if (features.length > 0) { + + const firebaseTools = await getFirebaseTools(); + + await firebaseTools.login(); + + const { project: ngProject, projectName: ngProjectName } = getProject(options, host); + + const [ defaultProjectName ] = getFirebaseProjectNameFromHost(host, ngProjectName); + + const firebaseProject = await projectPrompt(defaultProjectName); + + let hosting = { projectType: PROJECT_TYPE.Static, prerender: false }; + let firebaseHostingSite: FirebaseHostingSite|undefined; + + if (features.includes(FEATURES.Hosting)) { + // TODO read existing settings from angular.json, if available + const results = await projectTypePrompt(ngProject, ngProjectName); + hosting = { ...hosting, ...results }; + firebaseHostingSite = await sitePrompt(firebaseProject); + } + + let firebaseApp: FirebaseApp|undefined; + let sdkConfig: Record|undefined; + + if (features.find(it => it !== FEATURES.Hosting)) { + + const defaultAppId = firebaseHostingSite?.appId; + firebaseApp = await appPrompt(firebaseProject, defaultAppId); + + const result = await firebaseTools.apps.sdkconfig('web', firebaseApp.appId, { nonInteractive: true }); + sdkConfig = result.sdkConfig; + + } + + await setupProject(host, context, features, { + ...options, ...hosting, firebaseProject, firebaseApp, firebaseHostingSite, sdkConfig, + }); + + } +}; diff --git a/src/schematics/setup/prompts.ts b/src/schematics/setup/prompts.ts new file mode 100644 index 000000000..b93e54623 --- /dev/null +++ b/src/schematics/setup/prompts.ts @@ -0,0 +1,273 @@ +import * as fuzzy from 'fuzzy'; +import * as inquirer from 'inquirer'; +import { featureOptions, FEATURES, FirebaseApp, FirebaseHostingSite, FirebaseProject, PROJECT_TYPE, WorkspaceProject } from '../interfaces'; +import { hasPrerenderOption, isUniversalApp, shortAppId, shortSiteName } from '../utils'; +import { getFirebaseTools } from '../firebaseTools'; + +inquirer.registerPrompt('autocomplete', require('inquirer-autocomplete-prompt')); + +const NEW_OPTION = '~~angularfire-new~~'; +const DEFAULT_SITE_TYPE = 'DEFAULT_SITE'; + +// `fuzzy` passes either the original list of projects or an internal object +// which contains the project as a property. +const isProject = (elem: FirebaseProject | fuzzy.FilterResult): elem is FirebaseProject => { + return (elem as { original: FirebaseProject }).original === undefined; +}; + +const isApp = (elem: FirebaseApp | fuzzy.FilterResult): elem is FirebaseApp => { + return (elem as { original: FirebaseApp }).original === undefined; +}; + +const isSite = (elem: FirebaseHostingSite | fuzzy.FilterResult): elem is FirebaseHostingSite => { + return (elem as { original: FirebaseHostingSite }).original === undefined; +}; + +export const searchProjects = (promise: Promise) => + (_: any, input: string) => promise.then(projects => { + projects.unshift({ + projectId: NEW_OPTION, + displayName: '[CREATE NEW PROJECT]' + } as any); + return fuzzy.filter(input, projects, { + extract(el) { + return `${el.projectId} ${el.displayName}`; + } + }).map((result) => { + let original: FirebaseProject; + if (isProject(result)) { + original = result; + } else { + original = result.original; + } + return { + name: original.displayName, + title: original.displayName, + value: original.projectId + }; + }); + }); + +export const searchApps = (promise: Promise) => + (_: any, input: string) => promise.then(apps => { + apps.unshift({ + appId: NEW_OPTION, + displayName: '[CREATE NEW APP]', + } as any); + return fuzzy.filter(input, apps, { + extract(el: FirebaseApp) { + return el.displayName; + } + }).map((result) => { + let original: FirebaseApp; + if (isApp(result)) { + original = result; + } else { + original = result.original; + } + return { + name: original.displayName, + title: original.displayName, + value: shortAppId(original), + }; + }); + }); + +export const searchSites = (promise: Promise) => + (_: any, input: string) => promise.then(sites => { + sites.unshift({ + name: NEW_OPTION, + defaultUrl: '[CREATE NEW SITE]', + } as any); + return fuzzy.filter(input, sites, { + extract(el) { + return el.defaultUrl; + } + }).map((result) => { + let original: FirebaseHostingSite; + if (isSite(result)) { + original = result; + } else { + original = result.original; + } + return { + name: original.defaultUrl, + title: original.defaultUrl, + value: shortSiteName(original), + }; + }); + }); + + +type Prompt = (questions: { name: K, source: (...args) => + Promise<{ value: U }[]>, default?: U | ((o: U[]) => U | Promise), [key: string]: any }) => + Promise<{[T in K]: U }>; + +const autocomplete: Prompt = (questions) => inquirer.prompt(questions); + + +export const featuresPrompt = async (): Promise => { + const { features } = await inquirer.prompt({ + type: 'checkbox', + name: 'features', + choices: featureOptions, + message: 'What features would you like to setup?', + default: [FEATURES.Hosting], + }); + return features; +}; + +export const projectPrompt = async (defaultProject?: string) => { + const firebaseTools = await getFirebaseTools(); + const projects = firebaseTools.projects.list({}); + const { projectId } = await autocomplete({ + type: 'autocomplete', + name: 'projectId', + source: searchProjects(projects), + message: 'Please select a project:', + default: defaultProject, + }); + if (projectId === NEW_OPTION) { + const { projectId } = await inquirer.prompt({ + type: 'input', + name: 'projectId', + message: `Please specify a unique project id (cannot be modified afterward) [6-30 characters]:`, + }); + const { displayName } = await inquirer.prompt({ + type: 'input', + name: 'displayName', + message: 'What would you like to call your project?', + default: projectId, + }); + return await firebaseTools.projects.create(projectId, { displayName, nonInteractive: true }); + } + // tslint:disable-next-line:no-non-null-assertion + return (await projects).find(it => it.projectId === projectId)!; +}; + +export const appPrompt = async ({ projectId: project }: FirebaseProject, defaultAppId: string|undefined) => { + const firebaseTools = await getFirebaseTools(); + const apps = firebaseTools.apps.list('web', { project }); + const { appId } = await autocomplete({ + type: 'autocomplete', + name: 'appId', + source: searchApps(apps), + message: 'Please select an app:', + default: defaultAppId, + }); + if (appId === NEW_OPTION) { + const { displayName } = await inquirer.prompt({ + type: 'input', + name: 'displayName', + message: 'What would you like to call your app?', + }); + return await firebaseTools.apps.create('web', displayName, { nonInteractive: true, project }); + } + // tslint:disable-next-line:no-non-null-assertion + return (await apps).find(it => shortAppId(it) === appId)!; +}; + +export const sitePrompt = async ({ projectId: project }: FirebaseProject) => { + const firebaseTools = await getFirebaseTools(); + if (!firebaseTools.hosting.sites) { + return undefined; + } + const sites = firebaseTools.hosting.sites.list({ project }).then(it => { + if (it.sites.length === 0) { + // newly created projects don't return their default site, stub one + return [{ + name: project, + defaultUrl: `https://${project}.web.app`, + type: DEFAULT_SITE_TYPE, + appId: undefined, + } as FirebaseHostingSite]; + } else { + return it.sites; + } + }); + const { siteName } = await autocomplete({ + type: 'autocomplete', + name: 'siteName', + source: searchSites(sites), + message: 'Please select a hosting site:', + default: _ => sites.then(it => shortSiteName(it.find(it => it.type === DEFAULT_SITE_TYPE))), + }); + if (siteName === NEW_OPTION) { + const { subdomain } = await inquirer.prompt({ + type: 'input', + name: 'subdomain', + message: 'Please provide an unique, URL-friendly id for the site (.web.app):', + }); + return await firebaseTools.hosting.sites.create(subdomain, { nonInteractive: true, project }); + } + // tslint:disable-next-line:no-non-null-assertion + return (await sites).find(it => shortSiteName(it) === siteName)!; +}; + +export const prerenderPrompt = (project: WorkspaceProject, prerender: boolean): Promise<{ projectType: PROJECT_TYPE }> => { + if (isUniversalApp(project)) { + return inquirer.prompt({ + type: 'prompt', + name: 'prerender', + message: 'We detected an Angular Universal project. How would you like to render server-side content?', + default: true + }); + } + return Promise.resolve({ projectType: PROJECT_TYPE.Static }); +}; + +export const projectTypePrompt = async (project: WorkspaceProject, name: string) => { + let prerender = false; + let nodeVersion: string|undefined; + let serverTarget: string|undefined; + let browserTarget = `${name}:build:${project.architect?.build?.defaultConfiguration || 'production'}`; + let prerenderTarget: string|undefined; + if (isUniversalApp(project)) { + serverTarget = `${name}:server:${project.architect?.server?.defaultConfiguration || 'production'}`; + browserTarget = `${name}:build:${project.architect?.build?.defaultConfiguration || 'production'}`; + if (hasPrerenderOption(project)) { + prerenderTarget = `${name}:prerender:${project.architect?.prerender?.defaultConfiguration || 'production'}`; + const { shouldPrerender } = await inquirer.prompt({ + type: 'confirm', + name: 'shouldPrerender', + message: 'Should we prerender before deployment?', + default: true + }); + prerender = shouldPrerender; + } + const choices = [ + { name: prerender ? 'Pre-render only' : 'Don\'t render universal content', value: PROJECT_TYPE.Static }, + { name: 'Cloud Functions', value: PROJECT_TYPE.CloudFunctions }, + { name: 'Cloud Run', value: PROJECT_TYPE.CloudRun }, + ]; + const { projectType } = await inquirer.prompt({ + type: 'list', + name: 'projectType', + choices, + message: 'How would you like to render server-side content?', + default: PROJECT_TYPE.CloudFunctions, + }); + if (projectType === PROJECT_TYPE.CloudFunctions) { + const { newNodeVersion } = await inquirer.prompt({ + type: 'list', + name: 'newNodeVersion', + choices: ['12', '14', '16'], + message: 'What version of Node.js would you like to use?', + default: parseInt(process.versions.node, 10).toString(), + }); + nodeVersion = newNodeVersion; + } else if (projectType === PROJECT_TYPE.CloudRun) { + const fetch = require('node-fetch'); + const { newNodeVersion } = await inquirer.prompt({ + type: 'input', + name: 'newNodeVersion', + message: 'What version of Node.js would you like to use?', + validate: it => fetch(`https://hub.docker.com/v2/repositories/library/node/tags/${it}-slim`).then(it => it.status === 200 || `Can't find node:${it}-slim docker image.`), + default: parseFloat(process.versions.node).toString(), + }); + nodeVersion = newNodeVersion; + } + return { prerender, projectType, nodeVersion, browserTarget, serverTarget, prerenderTarget }; + } + return { projectType: PROJECT_TYPE.Static, prerender, nodeVersion, browserTarget, serverTarget, prerenderTarget }; +}; diff --git a/src/schematics/setup/schema.json b/src/schematics/setup/schema.json new file mode 100644 index 000000000..5b1ce2c4f --- /dev/null +++ b/src/schematics/setup/schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "angular-fire-ng-add", + "title": "AngularFire ng-add", + "type": "object", + "properties": { + "project": { + "type": "string", + "description": "The name of the project.", + "$default": { + "$source": "projectName" + } + } + }, + "required": [] + } diff --git a/src/schematics/setup/ssr.ts b/src/schematics/setup/ssr.ts new file mode 100644 index 000000000..7452463a6 --- /dev/null +++ b/src/schematics/setup/ssr.ts @@ -0,0 +1,150 @@ +import { SchematicsException, Tree, SchematicContext } from '@angular-devkit/schematics'; +import { + addDependencies, + generateFirebaseRc, + overwriteIfExists, + safeReadJSON, + stringifyFormatted +} from '../common'; +import { FirebaseJSON, Workspace, WorkspaceProject, NgAddNormalizedOptions, PROJECT_TYPE } from '../interfaces'; +import { firebaseFunctionsDependencies } from '../versions.json'; +import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; +import { shortSiteName } from '../utils'; + +function generateHostingConfig(project: string, dist: string, functionName: string, projectType: PROJECT_TYPE) { + return { + target: project, + public: dist, + ignore: ['**/.*'], + headers: [{ + // TODO check the hash style in the angular.json + source: '*.[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].+(css|js)', + headers: [{ + key: 'Cache-Control', + value: 'public,max-age=31536000,immutable' + }] + }], + rewrites: [ + projectType === PROJECT_TYPE.CloudFunctions ? { + source: '**', + function: functionName + } : { + source: '**', + run: { serviceId: functionName } + } + ] + }; +} + +function generateFunctionsConfig(source: string) { + return { + source + }; +} + +export function generateFirebaseJson( + tree: Tree, + path: string, + project: string, + dist: string, + functionsOutput: string, + functionName: string, + projectType: PROJECT_TYPE, +) { + const firebaseJson: FirebaseJSON = tree.exists(path) + ? safeReadJSON(path, tree) + : {}; + + const newConfig = generateHostingConfig(project, dist, functionName, projectType); + if (firebaseJson.hosting === undefined) { + firebaseJson.hosting = [newConfig]; + } else if (Array.isArray(firebaseJson.hosting)) { + const existingConfigIndex = firebaseJson.hosting.findIndex(config => config.target === newConfig.target); + if (existingConfigIndex > -1) { + firebaseJson.hosting.splice(existingConfigIndex, 1, newConfig); + } else { + firebaseJson.hosting.push(newConfig); + } + } else { + firebaseJson.hosting = [firebaseJson.hosting, newConfig]; + } + + if (projectType === PROJECT_TYPE.CloudFunctions) { + firebaseJson.functions = generateFunctionsConfig(functionsOutput); + } + + overwriteIfExists(tree, path, stringifyFormatted(firebaseJson)); +} + +export const setupUniversalDeployment = (config: { + project: WorkspaceProject; + options: NgAddNormalizedOptions; + workspacePath: string; + workspace: Workspace; + tree: Tree; + context: SchematicContext; + projectType: PROJECT_TYPE; + nodeVersion: string; +}) => { + const { tree, workspacePath, workspace, options } = config; + const project = workspace.projects[options.project]; + + if (!project.architect?.build?.options?.outputPath) { + throw new SchematicsException( + `Cannot read the output path (architect.build.options.outputPath) of the Angular project "${options.project}" in angular.json` + ); + } + + if (!project.architect?.server?.options?.outputPath) { + throw new SchematicsException( + `Cannot read the output path (architect.server.options.outputPath) of the Angular project "${options.project}" in angular.json` + ); + } + + const ssrDirectory = config.projectType === PROJECT_TYPE.CloudFunctions ? 'functions' : 'run'; + const staticOutput = project.architect.build.options.outputPath; + const functionsOutput = `dist/${options.project}/${ssrDirectory}`; + + // TODO clean this up a bit + const functionName = config.projectType === PROJECT_TYPE.CloudRun ? + `ssr-${options.project.replace('_', '-')}` : + `ssr_${options.project}`; + + project.architect.deploy = { + builder: '@angular/fire:deploy', + options: { + ssr: config.projectType === PROJECT_TYPE.CloudRun ? 'cloud-run' : 'cloud-functions', + prerender: options.prerender, + firebaseProject: options.firebaseProject.projectId, + firebaseHostingSite: shortSiteName(options.firebaseHostingSite), + functionName, + functionsNodeVersion: config.nodeVersion, + region: 'us-central1', + browserTarget: options.browserTarget, + ...(options.serverTarget ? {serverTarget: options.serverTarget} : {}), + ...(options.prerenderTarget ? {prerenderTarget: options.prerenderTarget} : {}), + outputPath: functionsOutput, + } + }; + + tree.overwrite(workspacePath, JSON.stringify(workspace, null, 2)); + + addDependencies( + tree, + firebaseFunctionsDependencies, + config.context + ); + + config.context.addTask(new NodePackageInstallTask()); + + generateFirebaseJson(tree, 'firebase.json', options.project, staticOutput, functionsOutput, functionName, config.projectType); + generateFirebaseRc( + tree, + '.firebaserc', + options.firebaseProject.projectId, + options.firebaseHostingSite, + options.project + ); + + return tree; +}; diff --git a/src/schematics/ng-add-static.ts b/src/schematics/setup/static.ts similarity index 63% rename from src/schematics/ng-add-static.ts rename to src/schematics/setup/static.ts index 7bf4538c3..dca13e944 100644 --- a/src/schematics/ng-add-static.ts +++ b/src/schematics/setup/static.ts @@ -1,17 +1,13 @@ import { SchematicsException, Tree, SchematicContext } from '@angular-devkit/schematics'; import { - addDependencies, - DeployOptions, generateFirebaseRc, - NgAddNormalizedOptions, overwriteIfExists, safeReadJSON, stringifyFormatted -} from './ng-add-common'; -import { FirebaseJSON, Workspace, WorkspaceProject } from './interfaces'; +} from '../common'; +import { NgAddNormalizedOptions, FirebaseJSON, Workspace, WorkspaceProject } from '../interfaces'; -import { default as defaultDependencies } from './versions.json'; -import { NodePackageInstallTask, RunSchematicTask } from '@angular-devkit/schematics/tasks'; +import { shortSiteName } from '../utils'; function emptyFirebaseJson() { return { @@ -50,23 +46,16 @@ export function generateFirebaseJson( ? safeReadJSON(path, tree) : emptyFirebaseJson(); - /* TODO do we want to prompt for override? - if ( - firebaseJson.hosting && - ((Array.isArray(firebaseJson.hosting) && - firebaseJson.hosting.find(config => config.target === project)) || - (firebaseJson.hosting as FirebaseHostingConfig).target === project) - ) { - throw new SchematicsException( - `Target ${project} already exists in firebase.json` - ); - }*/ - const newConfig = generateHostingConfig(project, dist); if (firebaseJson.hosting === undefined) { firebaseJson.hosting = newConfig; } else if (Array.isArray(firebaseJson.hosting)) { - firebaseJson.hosting.push(newConfig); + const targetIndex = firebaseJson.hosting.findIndex(it => it.target === newConfig.target); + if (targetIndex > -1) { + firebaseJson.hosting[targetIndex] = newConfig; + } else { + firebaseJson.hosting.push(newConfig); + } } else { firebaseJson.hosting = [firebaseJson.hosting, newConfig]; } @@ -74,34 +63,18 @@ export function generateFirebaseJson( overwriteIfExists(tree, path, stringifyFormatted(firebaseJson)); } -export const addFirebaseHostingDependencies = (options: DeployOptions) => (tree: Tree, context: SchematicContext) => { - addDependencies( - tree, - defaultDependencies, - context - ); - context.addTask(new RunSchematicTask('ng-add-setup-project', options), [ - context.addTask(new NodePackageInstallTask()) - ]); - return tree; -}; - export const setupStaticDeployment = (config: { project: WorkspaceProject; options: NgAddNormalizedOptions; workspacePath: string; workspace: Workspace; tree: Tree; + context: SchematicContext; }) => { const { tree, workspacePath, workspace, options } = config; const project = workspace.projects[options.project]; - if ( - !project.architect || - !project.architect.build || - !project.architect.build.options || - !project.architect.build.options.outputPath - ) { + if (!project.architect?.build?.options?.outputPath) { throw new SchematicsException( `Cannot read the output path (architect.build.options.outputPath) of the Angular project "${options.project}" in angular.json` ); @@ -111,7 +84,15 @@ export const setupStaticDeployment = (config: { project.architect.deploy = { builder: '@angular/fire:deploy', - options: {} + options: { + prerender: options.prerender, + ssr: false, + browserTarget: options.browserTarget, + firebaseProject: options.firebaseProject.projectId, + firebaseHostingSite: shortSiteName(options.firebaseHostingSite), + ...(options.serverTarget ? {serverTarget: options.serverTarget} : {}), + ...(options.prerenderTarget ? {prerenderTarget: options.prerenderTarget} : {}), + } }; tree.overwrite(workspacePath, JSON.stringify(workspace, null, 2)); @@ -119,7 +100,8 @@ export const setupStaticDeployment = (config: { generateFirebaseRc( tree, '.firebaserc', - options.firebaseProject, + options.firebaseProject.projectId, + options.firebaseHostingSite, options.project ); diff --git a/src/schematics/tsconfig.json b/src/schematics/tsconfig.json index 10bc3028e..9a9cc515b 100644 --- a/src/schematics/tsconfig.json +++ b/src/schematics/tsconfig.json @@ -7,6 +7,7 @@ "removeComments": true, "strictNullChecks": true, "resolveJsonModule": true, + "esModuleInterop": true, "lib": [ "es2015", "dom", @@ -20,5 +21,12 @@ "module": "commonjs", "outDir": "../../dist/packages-dist/schematics" }, - "files": ["public_api.ts"] + "files": [ + "update/index.ts", + "deploy/actions.ts", + "deploy/builder.ts", + "add/index.ts", + "setup/index.ts", + "update/v7/index.ts", + ] } \ No newline at end of file diff --git a/src/schematics/ng-update.ts b/src/schematics/update/index.ts similarity index 100% rename from src/schematics/ng-update.ts rename to src/schematics/update/index.ts diff --git a/src/schematics/update/v7/index.ts b/src/schematics/update/v7/index.ts index 99e76e824..4fc5a45fb 100644 --- a/src/schematics/update/v7/index.ts +++ b/src/schematics/update/v7/index.ts @@ -1,7 +1,7 @@ import { Rule, SchematicContext, SchematicsException, Tree } from '@angular-devkit/schematics'; import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; -import { overwriteIfExists, safeReadJSON, stringifyFormatted } from '../../ng-add-common'; -import { default as defaultDependencies, firebaseFunctions } from '../../versions.json'; +import { overwriteIfExists, safeReadJSON, stringifyFormatted } from '../../common'; +import { peerDependencies, firebaseFunctionsDependencies } from '../../versions.json'; import { join } from 'path'; const IMPORT_REGEX = /(?import|export)\s+(?:(?[\w,{}\s\*]+)\s+from)?\s*(?:(?["'])?(?[@\w\s\\\/.-]+)\3?)\s*(?[;\n])/g; @@ -23,18 +23,16 @@ export const ngUpdate = (): Rule => ( throw new SchematicsException('Could not locate package.json'); } - Object.keys(defaultDependencies).forEach(depName => { - const dep = defaultDependencies[depName]; - if (dep.dev) { - packageJson.devDependencies[depName] = dep.version; - } else { - packageJson.dependencies[depName] = dep.version; + Object.keys(peerDependencies).forEach(depName => { + const dep = peerDependencies[depName]; + if (dep) { + packageJson[dep.dev ? 'devDependencies' : 'dependencies'][depName] = dep.version; } }); // TODO test if it's a SSR project in the JSON - Object.keys(firebaseFunctions).forEach(depName => { - const dep = firebaseFunctions[depName]; + Object.keys(firebaseFunctionsDependencies).forEach(depName => { + const dep = firebaseFunctionsDependencies[depName]; if (dep.dev && packageJson.devDependencies[depName]) { packageJson.devDependencies[depName] = dep.version; } else if (packageJson.dependencies[depName]) { diff --git a/src/schematics/utils.ts b/src/schematics/utils.ts index 5a6e716ba..9228ee4b0 100644 --- a/src/schematics/utils.ts +++ b/src/schematics/utils.ts @@ -1,64 +1,38 @@ import { readFileSync } from 'fs'; -import { FirebaseRc, Project, Workspace, WorkspaceProject } from './interfaces'; +import { FirebaseRc, Workspace, WorkspaceProject, FirebaseApp, FirebaseHostingSite, DeployOptions, FEATURES } from './interfaces'; import { join } from 'path'; -import { isUniversalApp } from './ng-add-ssr'; import { SchematicsException, Tree } from '@angular-devkit/schematics'; -import { DeployOptions } from './ng-add-common'; +import ts from '@schematics/angular/third_party/github.com/Microsoft/TypeScript/lib/typescript'; +import { findNode, addImportToModule, insertImport } from '@schematics/angular/utility/ast-utils'; +import { InsertChange, ReplaceChange, applyToUpdateRecorder, Change } from '@schematics/angular/utility/change'; +import { findModuleFromOptions, buildRelativePath } from '@schematics/angular/utility/find-module'; +import { overwriteIfExists } from './common'; -export async function listProjects() { - const firebase = require('firebase-tools'); - await firebase.login(); - return firebase.projects.list(); -} +// We consider a project to be a universal project if it has a `server` architect +// target. If it does, it knows how to build the application's server. +export const isUniversalApp = ( + project: WorkspaceProject +) => project.architect?.server; -// `fuzzy` passes either the original list of projects or an internal object -// which contains the project as a property. -const isProject = (elem: Project | { original: Project }): elem is Project => { - return (elem as { original: Project }).original === undefined; -}; +export const hasPrerenderOption = ( + project: WorkspaceProject +) => project.architect?.prerender; -const searchProjects = (projects: Project[]) => { - return (_: any, input: string) => { - return Promise.resolve( - require('fuzzy') - .filter(input, projects, { - extract(el: Project) { - return `${el.projectId} ${el.displayName}`; - } - }) - .map((result: Project | { original: Project }) => { - let original: Project; - if (isProject(result)) { - original = result; - } else { - original = result.original; - } - return { - name: `${original.displayName} (${original.projectId})`, - title: original.displayName, - value: original.projectId - }; - }) - ); - }; -}; +export const shortAppId = (app?: FirebaseApp) => app?.appId && app.appId.split('/').pop(); +export const shortSiteName = (site?: FirebaseHostingSite) => site?.name && site.name.split('/').pop(); export function getWorkspace( host: Tree ): { path: string; workspace: Workspace } { - const possibleFiles = ['/angular.json', '/.angular.json']; - const path = possibleFiles.filter(p => host.exists(p))[0]; + const path = '/angular.json'; - const configBuffer = host.read(path); - if (configBuffer === null) { + const configBuffer = path && host.read(path); + if (!configBuffer) { throw new SchematicsException(`Could not find angular.json`); } - // We can not depend on this library to have be included in older (or newer) Angular versions. - // Require here, since the schematic will add it to the package.json and install it before - // continuing. - const { parse }: typeof import('jsonc-parser') = require('jsonc-parser'); + const { parse } = require('jsonc-parser'); const workspace = parse(configBuffer.toString()) as Workspace|undefined; if (!workspace) { @@ -97,41 +71,251 @@ export const getProject = (options: DeployOptions, host: Tree) => { return {project, projectName}; }; -export const projectPrompt = (projects: Project[]) => { - const inquirer = require('inquirer'); - inquirer.registerPrompt( - 'autocomplete', - require('inquirer-autocomplete-prompt') +export function getFirebaseProjectNameFromHost( + host: Tree, + target: string +): [string|undefined, string|undefined] { + const buffer = host.read('/.firebaserc'); + if (!buffer) { + return [undefined, undefined]; + } + const rc: FirebaseRc = JSON.parse(buffer.toString()); + return projectFromRc(rc, target); +} + +export function getFirebaseProjectNameFromFs( + root: string, + target: string +): [string|undefined, string|undefined] { + const path = join(root, '.firebaserc'); + try { + const buffer = readFileSync(path); + const rc: FirebaseRc = JSON.parse(buffer.toString()); + return projectFromRc(rc, target); + } catch (e) { + return [undefined, undefined]; + } +} + +const projectFromRc = (rc: FirebaseRc, target: string): [string|undefined, string|undefined] => { + const defaultProject = rc.projects?.default; + const project = Object.keys(rc.targets || {}).find( + project => !!rc.targets?.[project]?.hosting?.[target] ); - return inquirer.prompt({ - type: 'autocomplete', - name: 'firebaseProject', - source: searchProjects(projects), - message: 'Please select a project:' - }); + const site = project && rc.targets?.[project]?.hosting?.[target]?.[0]; + return [project || defaultProject, site]; }; -export const projectTypePrompt = (project: WorkspaceProject): Promise<{ universalProject: boolean }> => { - if (isUniversalApp(project)) { - return require('inquirer').prompt({ - type: 'confirm', - name: 'universalProject', - message: 'We detected an Angular Universal project. Do you want to deploy as a Firebase Function?' - }); +/** + * Adds a package to the package.json + */ +export function addEnvironmentEntry( + host: Tree, + filePath: string, + data: string, +): Tree { + if (!host.exists(filePath)) { + throw new Error(`File ${filePath} does not exist`); } - return Promise.resolve({ universalProject: false }); -}; -export function getFirebaseProjectName( - workspaceRoot: string, - target: string -): string | undefined { - const rc: FirebaseRc = JSON.parse( - readFileSync(join(workspaceRoot, '.firebaserc'), 'UTF-8') + const buffer = host.read(filePath); + if (!buffer) { + throw new SchematicsException(`Cannot read ${filePath}`); + } + const sourceFile = ts.createSourceFile(filePath, buffer.toString('utf-8'), ts.ScriptTarget.Latest, true); + + const envIdentifier = findNode(sourceFile as any, ts.SyntaxKind.Identifier, 'environment'); + if (!envIdentifier || !envIdentifier.parent) { + throw new SchematicsException(`Cannot find 'environment' identifier in ${filePath}`); + } + + const envObjectLiteral = envIdentifier.parent.getChildren().find(({ kind }) => kind === ts.SyntaxKind.ObjectLiteralExpression); + if (!envObjectLiteral) { + throw new SchematicsException(`${filePath} is not in the expected format`); + } + const firebaseIdentifier = findNode(envObjectLiteral, ts.SyntaxKind.Identifier, 'firebase'); + + const recorder = host.beginUpdate(filePath); + if (firebaseIdentifier && firebaseIdentifier.parent) { + const change = new ReplaceChange(filePath, firebaseIdentifier.parent.pos, firebaseIdentifier.parent.getFullText(), data); + applyToUpdateRecorder(recorder, [change]); + } else { + const openBracketToken = envObjectLiteral.getChildren().find(({ kind }) => kind === ts.SyntaxKind.OpenBraceToken); + if (openBracketToken) { + const change = new InsertChange(filePath, openBracketToken.end, `${data},`); + applyToUpdateRecorder(recorder, [change]); + } else { + throw new SchematicsException(`${filePath} is not in the expected format`); + } + } + host.commitUpdate(recorder); + + return host; +} + +export function addToNgModule(host: Tree, options: { sourcePath: string, features: FEATURES[]}) { + + const modulePath = findModuleFromOptions(host, { + name: 'app', + path: options.sourcePath, + }); + + if (!modulePath) { + return host; + } + + if (!host.exists(modulePath)) { + throw new Error(`Specified module path ${modulePath} does not exist`); + } + + const text = host.read(modulePath); + if (text === null) { + throw new SchematicsException(`File ${modulePath} does not exist.`); + } + const sourceText = text.toString('utf-8'); + + const source = ts.createSourceFile( + modulePath, + sourceText, + ts.ScriptTarget.Latest, + true ); - const targets = rc.targets || {}; - const projects = Object.keys(targets || {}); - return projects.find( - project => !!Object.keys(targets[project].hosting).find(t => t === target) + + const environmentsPath = buildRelativePath( + modulePath, + `/${options.sourcePath}/environments/environment` ); + + const changes: Array = []; + + if (!findNode(source, ts.SyntaxKind.Identifier, 'provideFirebaseApp')) { + changes.push( + insertImport(source, modulePath, ['initializeApp', 'provideFirebaseApp'] as any, '@angular/fire/app'), + insertImport(source, modulePath, 'environment', environmentsPath), + ...addImportToModule(source, modulePath, `provideFirebaseApp(() => initializeApp(environment.firebase))`, null as any), + ); + } + + if ( + options.features.includes(FEATURES.Analytics) && + !findNode(source, ts.SyntaxKind.Identifier, 'provideAnalytics') + ) { + // TODO add user and screen tracking service + changes.push( + insertImport(source, modulePath, ['provideAnalytics', 'getAnalytics'] as any, '@angular/fire/analytics'), + ...addImportToModule(source, modulePath, `provideAnalytics(() => getAnalytics())`, null as any), + ); + } + + if ( + options.features.includes(FEATURES.Authentication) && + !findNode(source, ts.SyntaxKind.Identifier, 'provideAuth') + ) { + changes.push( + insertImport(source, modulePath, ['provideAuth', 'getAuth'] as any, '@angular/fire/auth'), + ...addImportToModule(source, modulePath, `provideAuth(() => getAuth())`, null as any), + ); + } + + if ( + options.features.includes(FEATURES.Database) && + !findNode(source, ts.SyntaxKind.Identifier, 'provideDatabase') + ) { + changes.push( + insertImport(source, modulePath, ['provideDatabase', 'getDatabase'] as any, '@angular/fire/database'), + ...addImportToModule(source, modulePath, `provideDatabase(() => getDatabase())`, null as any), + ); + } + + if ( + options.features.includes(FEATURES.Firestore) && + !findNode(source, ts.SyntaxKind.Identifier, 'provideFirestore') + ) { + changes.push( + insertImport(source, modulePath, ['provideFirestore', 'getFirestore'] as any, '@angular/fire/firestore'), + ...addImportToModule(source, modulePath, `provideFirestore(() => getFirestore())`, null as any), + ); + } + + if ( + options.features.includes(FEATURES.Functions) && + !findNode(source, ts.SyntaxKind.Identifier, 'provideFunctions') + ) { + changes.push( + insertImport(source, modulePath, ['provideFunctions', 'getFunctions'] as any, '@angular/fire/functions'), + ...addImportToModule(source, modulePath, `provideFunctions(() => getFunctions())`, null as any), + ); + } + + if ( + options.features.includes(FEATURES.Messaging) && + !findNode(source, ts.SyntaxKind.Identifier, 'provideMessaging') + ) { + // TODO add the service worker + changes.push( + insertImport(source, modulePath, ['provideMessaging', 'getMessaging'] as any, '@angular/fire/messaging'), + ...addImportToModule(source, modulePath, `provideMessaging(() => getMessaging())`, null as any), + ); + } + + if ( + options.features.includes(FEATURES.Performance) && + !findNode(source, ts.SyntaxKind.Identifier, 'providePerformance') + ) { + // TODO performance monitor service + changes.push( + insertImport(source, modulePath, ['providePerformance', 'getPerformance'] as any, '@angular/fire/performance'), + ...addImportToModule(source, modulePath, `providePerformance(() => getPerformance())`, null as any), + ); + } + + if ( + options.features.includes(FEATURES.RemoteConfig) && + !findNode(source, ts.SyntaxKind.Identifier, 'provideRemoteConfig') + ) { + changes.push( + insertImport(source, modulePath, ['provideRemoteConfig', 'getRemoteConfig'] as any, '@angular/fire/remote-config'), + ...addImportToModule(source, modulePath, `provideRemoteConfig(() => getRemoteConfig())`, null as any), + ); + } + + if ( + options.features.includes(FEATURES.Storage) && + !findNode(source, ts.SyntaxKind.Identifier, 'provideStorage') + ) { + changes.push( + insertImport(source, modulePath, ['provideStorage', 'getStorage'] as any, '@angular/fire/storage'), + ...addImportToModule(source, modulePath, `provideStorage(() => getStorage())`, null as any), + ); + } + + const recorder = host.beginUpdate(modulePath); + applyToUpdateRecorder(recorder, changes); + host.commitUpdate(recorder); + + return host; } + +export const addIgnoreFiles = (host: Tree) => { + const path = '/.gitignore'; + if (!host.exists(path)) { + return host; + } + + const buffer = host.read(path); + if (!buffer) { + return host; + } + + const content = buffer.toString(); + if (!content.includes('# Firebase')) { + overwriteIfExists(host, path, content.concat(` +# Firebase +.firebase +*-debug.log +.runtimeconfig.json +`)); + } + + return host; +}; diff --git a/src/schematics/versions.json b/src/schematics/versions.json index b8ae3e989..33465856c 100644 --- a/src/schematics/versions.json +++ b/src/schematics/versions.json @@ -1,18 +1,10 @@ { - "default": { - "firebase": { "version": "0.0.0" }, - "rxfire": { "version": "0.0.0" }, - "@angular-devkit/architect": { "dev": true, "version": "0.0.0" }, - "firebase-tools": { "dev": true, "version": "0.0.0" }, - "fuzzy": { "dev": true, "version": "0.0.0"}, - "inquirer": { "dev": true, "version": "0.0.0"}, - "inquirer-autocomplete-prompt": { "dev": true, "version": "0.0.0"}, - "open": { "dev": true, "version": "0.0.0"}, - "jsonc-parser": { "dev": true, "version": "0.0.0" } + "peerDependencies": { + "firebase": { "dev": false, "version": "0.0.0" }, + "rxfire": { "dev": false, "version": "0.0.0" } }, - "firebaseFunctions": { + "firebaseFunctionsDependencies": { "firebase-admin": { "dev": true, "version": "0.0.0" }, - "firebase-functions": { "dev": true, "version": "0.0.0" }, - "firebase-functions-test": { "dev": true, "version": "0.0.0" } + "firebase-functions": { "dev": true, "version": "0.0.0" } } } diff --git a/tools/build.ts b/tools/build.ts index 5ebf4e82a..9132bb6f1 100644 --- a/tools/build.ts +++ b/tools/build.ts @@ -161,11 +161,11 @@ async function replaceSchematicVersions() { const root = await rootPackage; const path = dest('schematics', 'versions.json'); const dependencies = await import(path); - Object.keys(dependencies.default).forEach(name => { - dependencies.default[name].version = root.dependencies[name] || root.devDependencies[name]; + Object.keys(dependencies.peerDependencies).forEach(name => { + dependencies.peerDependencies[name].version = root.dependencies[name] || root.devDependencies[name]; }); - Object.keys(dependencies.firebaseFunctions).forEach(name => { - dependencies.firebaseFunctions[name].version = root.dependencies[name] || root.devDependencies[name]; + Object.keys(dependencies.firebaseFunctionsDependencies).forEach(name => { + dependencies.firebaseFunctionsDependencies[name].version = root.dependencies[name] || root.devDependencies[name]; }); return writeFile(path, JSON.stringify(dependencies, null, 2)); } @@ -181,6 +181,8 @@ async function compileSchematics() { copy(src('schematics', 'collection.json'), dest('schematics', 'collection.json')), copy(src('schematics', 'migration.json'), dest('schematics', 'migration.json')), copy(src('schematics', 'deploy', 'schema.json'), dest('schematics', 'deploy', 'schema.json')), + copy(src('schematics', 'add', 'schema.json'), dest('schematics', 'add', 'schema.json')), + copy(src('schematics', 'setup', 'schema.json'), dest('schematics', 'setup', 'schema.json')), replaceSchematicVersions() ]); } diff --git a/yarn.lock b/yarn.lock index f2d3a29a7..b729837f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -134,6 +134,18 @@ rxjs "6.6.7" source-map "0.7.3" +"@angular-devkit/core@12.2.5": + version "12.2.5" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-12.2.5.tgz#928fc35b28e1ed84243b0c09db97be1a9c85acdb" + integrity sha512-UBo0Q9nVGPxC+C1PONSzaczPLv5++5Q7PC2orZepDbWmY0jUDwe9VVJrmp8EhLZbzVKFpyCIs1ZE8h0s0LP1zA== + dependencies: + ajv "8.6.2" + ajv-formats "2.1.0" + fast-json-stable-stringify "2.1.0" + magic-string "0.25.7" + rxjs "6.6.7" + source-map "0.7.3" + "@angular-devkit/core@8.3.29", "@angular-devkit/core@^8.3.8": version "8.3.29" resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-8.3.29.tgz#3477edd6458653f83e6d78684b100c1bef81382f" @@ -154,6 +166,15 @@ ora "5.4.1" rxjs "6.6.7" +"@angular-devkit/schematics@12.2.5": + version "12.2.5" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-12.2.5.tgz#2ca20be4bf5b411e1e29eceb479cb745bda50e16" + integrity sha512-8WAdZ39FZqbU1/ZQQrK+7PeRuj6QUGlxFUgoVXk5nzRbpZo/OSaKhPoC7sC1A0EU+7udLp5vT7R12sDz7Mr9vQ== + dependencies: + "@angular-devkit/core" "12.2.5" + ora "5.4.1" + rxjs "6.6.7" + "@angular-devkit/schematics@8.3.29", "@angular-devkit/schematics@^8.3.8": version "8.3.29" resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-8.3.29.tgz#b3ba658b90fb3226a80ff12977be7dd583e99c49" @@ -1480,10 +1501,10 @@ "@firebase/util" "1.3.0" tslib "^2.1.0" -"@firebase/app-types@0.6.1": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.6.1.tgz#dcbd23030a71c0c74fc95d4a3f75ba81653850e9" - integrity sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg== +"@firebase/app-types@0.6.3": + version "0.6.3" + resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.6.3.tgz#3f10514786aad846d74cd63cb693556309918f4b" + integrity sha512-/M13DPPati7FQHEQ9Minjk1HGLm/4K4gs9bR4rzLCWJg64yGtVC0zNg9gDpkw9yc2cvol/mNFxqTtd4geGrwdw== "@firebase/app-types@0.7.0": version "0.7.0" @@ -1513,11 +1534,6 @@ selenium-webdriver "^4.0.0-beta.2" tslib "^2.1.0" -"@firebase/auth-interop-types@0.1.5": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz#9fc9bd7c879f16b8d1bb08373a0f48c3a8b74557" - integrity sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw== - "@firebase/auth-interop-types@0.1.6": version "0.1.6" resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz#5ce13fc1c527ad36f1bb1322c4492680a6cf4964" @@ -1540,13 +1556,13 @@ selenium-webdriver "4.0.0-beta.1" tslib "^2.1.0" -"@firebase/component@0.1.19": - version "0.1.19" - resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.1.19.tgz#bd2ac601652c22576b574c08c40da245933dbac7" - integrity sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ== +"@firebase/component@0.5.5": + version "0.5.5" + resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.5.5.tgz#849ccf7cbf0398a43058f274ffcd43620ae9521f" + integrity sha512-L41SdS/4a164jx2iGfakJgaBUPPBI3DI+RrUlmh3oHSUljTeCwfj/Nhcv3S7e2lyXsGFJtAyepfPUx4IQ05crw== dependencies: - "@firebase/util" "0.3.2" - tslib "^1.11.1" + "@firebase/util" "1.2.0" + tslib "^2.1.0" "@firebase/component@0.5.6": version "0.5.6" @@ -1568,12 +1584,12 @@ "@firebase/util" "1.3.0" tslib "^2.1.0" -"@firebase/database-types@0.5.2": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-0.5.2.tgz#23bec8477f84f519727f165c687761e29958b63c" - integrity sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g== +"@firebase/database-types@0.7.3", "@firebase/database-types@^0.7.2": + version "0.7.3" + resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-0.7.3.tgz#819f16dd4c767c864b460004458620f265a3f735" + integrity sha512-dSOJmhKQ0nL8O4EQMRNGpSExWCXeHtH57gGg0BfNAdWcKhC8/4Y+qfKLfWXzyHvrSecpLmO0SmAi/iK2D5fp5A== dependencies: - "@firebase/app-types" "0.6.1" + "@firebase/app-types" "0.6.3" "@firebase/database-types@0.9.0": version "0.9.0" @@ -1595,18 +1611,18 @@ faye-websocket "0.11.3" tslib "^2.1.0" -"@firebase/database@^0.6.0": - version "0.6.13" - resolved "https://registry.yarnpkg.com/@firebase/database/-/database-0.6.13.tgz#b96fe0c53757dd6404ee085fdcb45c0f9f525c17" - integrity sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA== +"@firebase/database@^0.10.0": + version "0.10.9" + resolved "https://registry.yarnpkg.com/@firebase/database/-/database-0.10.9.tgz#79f7b03cbe8a127dddfb7ea7748a3e923990f046" + integrity sha512-Jxi9SiE4cNOftO9YKlG71ccyWFw4kSM9AG/xYu6vWXUGBr39Uw1TvYougANOcU21Q0TP4J08VPGnOnpXk/FGbQ== dependencies: - "@firebase/auth-interop-types" "0.1.5" - "@firebase/component" "0.1.19" - "@firebase/database-types" "0.5.2" + "@firebase/auth-interop-types" "0.1.6" + "@firebase/component" "0.5.5" + "@firebase/database-types" "0.7.3" "@firebase/logger" "0.2.6" - "@firebase/util" "0.3.2" + "@firebase/util" "1.2.0" faye-websocket "0.11.3" - tslib "^1.11.1" + tslib "^2.1.0" "@firebase/firestore-compat@0.1.2": version "0.1.2" @@ -1800,12 +1816,12 @@ node-fetch "2.6.1" tslib "^2.1.0" -"@firebase/util@0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@firebase/util/-/util-0.3.2.tgz#87de27f9cffc2324651cabf6ec133d0a9eb21b52" - integrity sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g== +"@firebase/util@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.2.0.tgz#4d4e419bf8c9bc1bc51308d1953dc2e4353c0770" + integrity sha512-8W9TTGImXr9cu+oyjBJ7yjoEd/IVAv0pBZA4c1uIuKrpGZi2ee38m+8xlZOBRmsAaOU/tR9DXz1WF/oeM6Fb7Q== dependencies: - tslib "^1.11.1" + tslib "^2.1.0" "@firebase/util@1.3.0": version "1.3.0" @@ -1824,39 +1840,30 @@ resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210" integrity sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw== -"@google-cloud/common@^2.1.1": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@google-cloud/common/-/common-2.4.0.tgz#2783b7de8435024a31453510f2dab5a6a91a4c82" - integrity sha512-zWFjBS35eI9leAHhjfeOYlK5Plcuj/77EzstnrJIZbKgF/nkqjcQuGiMCpzCwOfPyUbz8ZaEOYgbHa759AKbjg== +"@google-cloud/common@^3.7.0": + version "3.7.2" + resolved "https://registry.yarnpkg.com/@google-cloud/common/-/common-3.7.2.tgz#2f1feef9aaeb4f392d627ceab06e7eaa03329f58" + integrity sha512-5Q9f74IbZaY6xAwJSNFy5SrGwbm1j7mpv+6A/r+K2dymjsXBH5UauB0tziaMwWoVVaMq1IQnZF9lgtfqqvxcUg== dependencies: - "@google-cloud/projectify" "^1.0.0" - "@google-cloud/promisify" "^1.0.0" - arrify "^2.0.0" - duplexify "^3.6.0" + "@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 "^5.5.0" - retry-request "^4.0.0" - teeny-request "^6.0.0" + google-auth-library "^7.0.2" + retry-request "^4.2.2" + teeny-request "^7.0.0" -"@google-cloud/firestore@^3.0.0": - version "3.8.6" - resolved "https://registry.yarnpkg.com/@google-cloud/firestore/-/firestore-3.8.6.tgz#9e6dea57323a5824563430a759244825fb01d834" - integrity sha512-ox80NbrM1MLJgvAAUd1quFLx/ie/nSjrk1PtscSicpoYDlKb9e6j7pHrVpbopBMyliyfNl3tLJWaDh+x+uCXqw== +"@google-cloud/firestore@^4.5.0": + version "4.15.1" + resolved "https://registry.yarnpkg.com/@google-cloud/firestore/-/firestore-4.15.1.tgz#ed764fc76823ce120e68fe8c27ef1edd0650cd93" + integrity sha512-2PWsCkEF1W02QbghSeRsNdYKN1qavrHBP3m72gPDMHQSYrGULOaTi7fSJquQmAtc4iPVB2/x6h80rdLHTATQtA== dependencies: - deep-equal "^2.0.0" + fast-deep-equal "^3.1.1" functional-red-black-tree "^1.0.1" - google-gax "^1.15.3" - readable-stream "^3.4.0" - through2 "^3.0.0" - -"@google-cloud/paginator@^2.0.0": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@google-cloud/paginator/-/paginator-2.0.3.tgz#c7987ad05d1c3ebcef554381be80e9e8da4e4882" - integrity sha512-kp/pkb2p/p0d8/SKUu4mOq8+HGwF8NPzHWkj+VKrIPQPyMRw8deZtrO/OcSiy9C/7bpfU5Txah5ltUNfPkgEXg== - dependencies: - arrify "^2.0.0" - extend "^3.0.2" + google-gax "^2.24.1" + protobufjs "^6.8.6" "@google-cloud/paginator@^3.0.0": version "3.0.5" @@ -1867,25 +1874,15 @@ extend "^3.0.2" "@google-cloud/precise-date@^2.0.0": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@google-cloud/precise-date/-/precise-date-2.0.3.tgz#14f6f28ce35dabf3882e7aeab1c9d51bd473faed" - integrity sha512-+SDJ3ZvGkF7hzo6BGa8ZqeK3F6Z4+S+KviC9oOK+XCs3tfMyJCh/4j93XIWINgMMDIh9BgEvlw4306VxlXIlYA== - -"@google-cloud/projectify@^1.0.0": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@google-cloud/projectify/-/projectify-1.0.4.tgz#28daabebba6579ed998edcadf1a8f3be17f3b5f0" - integrity sha512-ZdzQUN02eRsmTKfBj9FDL0KNDIFNjBn/d6tHQmA/+FImH5DO6ZV8E7FzxMgAUiVAUq41RFAkb25p1oHOZ8psfg== + version "2.0.4" + resolved "https://registry.yarnpkg.com/@google-cloud/precise-date/-/precise-date-2.0.4.tgz#930b0cbf557ef3a4bfeeb121cfc6da341212a2cb" + integrity sha512-nOB+mZdevI/1Si0QAfxWfzzIqFdc7wrO+DYePFvgbOoMtvX+XfFTINNt7e9Zg66AbDbWCPRnikU+6f5LTm9Wyg== "@google-cloud/projectify@^2.0.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@google-cloud/projectify/-/projectify-2.1.0.tgz#3df145c932e244cdeb87a30d93adce615bc69e6d" integrity sha512-qbpidP/fOvQNz3nyabaVnZqcED1NNzf7qfeOlgtAZd9knTwY+KtsGRkYpiQzcATABy4gnGP2lousM3S0nuWVzA== -"@google-cloud/promisify@^1.0.0": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@google-cloud/promisify/-/promisify-1.0.4.tgz#ce86ffa94f9cfafa2e68f7b3e4a7fad194189723" - integrity sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ== - "@google-cloud/promisify@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@google-cloud/promisify/-/promisify-2.0.3.tgz#f934b5cdc939e3c7039ff62b9caaf59a9d89e3a8" @@ -1912,32 +1909,29 @@ lodash.snakecase "^4.1.1" p-defer "^3.0.0" -"@google-cloud/storage@^4.1.2": - version "4.7.0" - resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-4.7.0.tgz#a7466086a83911c7979cc238d00a127ffb645615" - integrity sha512-f0guAlbeg7Z0m3gKjCfBCu7FG9qS3M3oL5OQQxlvGoPtK7/qg3+W+KQV73O2/sbuS54n0Kh2mvT5K2FWzF5vVQ== +"@google-cloud/storage@^5.3.0": + version "5.14.1" + resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-5.14.1.tgz#dff5d9e9a05f622c2d4f52f9c926f037e8f1bde3" + integrity sha512-rDwL5QKFs4p20lXep/ELYscCpCiaiPK9H9QTnPTqRQgswsDO0p+SEupq0Uw00+r4SfbHnjtvYTzBFlTu7Gn34g== dependencies: - "@google-cloud/common" "^2.1.1" - "@google-cloud/paginator" "^2.0.0" - "@google-cloud/promisify" "^1.0.0" + "@google-cloud/common" "^3.7.0" + "@google-cloud/paginator" "^3.0.0" + "@google-cloud/promisify" "^2.0.0" arrify "^2.0.0" + async-retry "^1.3.1" compressible "^2.0.12" - concat-stream "^2.0.0" - date-and-time "^0.13.0" - duplexify "^3.5.0" + date-and-time "^2.0.0" + duplexify "^4.0.0" extend "^3.0.2" - gaxios "^3.0.0" - gcs-resumable-upload "^2.2.4" + gcs-resumable-upload "^3.3.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 "^2.2.0" + p-limit "^3.0.1" pumpify "^2.0.0" - readable-stream "^3.4.0" snakeize "^0.1.0" stream-events "^1.0.1" - through2 "^3.0.0" xdg-basedir "^4.0.0" "@grpc/grpc-js@^1.3.2", "@grpc/grpc-js@~1.3.0": @@ -1947,21 +1941,6 @@ dependencies: "@types/node" ">=12.12.47" -"@grpc/grpc-js@~1.0.3": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.0.5.tgz#09948c0810e62828fdd61455b2eb13d7879888b0" - integrity sha512-Hm+xOiqAhcpT9RYM8lc15dbQD7aQurM7ZU8ulmulepiPlN7iwBXXwP3vSBUimoFoApRqz7pSIisXU8pZaCB4og== - dependencies: - semver "^6.2.0" - -"@grpc/proto-loader@^0.5.1": - version "0.5.6" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.5.6.tgz#1dea4b8a6412b05e2d58514d507137b63a52a98d" - integrity sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ== - dependencies: - lodash.camelcase "^4.3.0" - protobufjs "^6.8.6" - "@grpc/proto-loader@^0.6.0", "@grpc/proto-loader@^0.6.1": version "0.6.4" resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.6.4.tgz#5438c0d771e92274e77e631babdc14456441cbdc" @@ -2093,6 +2072,11 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-0.24.0.tgz#1028ef0e0923b24916158d80d2ddfd67ea8b6740" integrity sha512-a/szuMQV0Quy0/M7kKdglcbRSoorleyyOwbTNNJ32O+RBN766wbQlMTvdimImTmwYWGr+NJOni1EcC242WlRcA== +"@panva/asn1.js@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6" + integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw== + "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" @@ -2196,6 +2180,15 @@ "@angular-devkit/schematics" "12.1.3" jsonc-parser "3.0.0" +"@schematics/angular@^12.0.0": + version "12.2.5" + resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-12.2.5.tgz#5ff5f0fdd219a5994f9f4b94ec4f6b24f63d672b" + integrity sha512-Ln2GyO7Y00PrQKjqCONCDb4dwGzGboH3zIJvicWzFO+ZgkNLr/dsitGKm8b8OfR/UEiBcnK72xwPj9FWfXA4EQ== + dependencies: + "@angular-devkit/core" "12.2.5" + "@angular-devkit/schematics" "12.2.5" + jsonc-parser "3.0.0" + "@schematics/angular@^8.3.8": version "8.3.29" resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-8.3.29.tgz#da747539230dae16111c56422339c02dbb17681d" @@ -2262,6 +2255,11 @@ dependencies: "@types/node" "*" +"@types/cors@^2.8.5": + version "2.8.12" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" + integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== + "@types/duplexify@^3.6.0": version "3.6.0" resolved "https://registry.yarnpkg.com/@types/duplexify/-/duplexify-3.6.0.tgz#dfc82b64bd3a2168f5bd26444af165bf0237dcd8" @@ -2300,6 +2298,14 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== +"@types/express-jwt@0.0.42": + version "0.0.42" + resolved "https://registry.yarnpkg.com/@types/express-jwt/-/express-jwt-0.0.42.tgz#4f04e1fadf9d18725950dc041808a4a4adf7f5ae" + integrity sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag== + dependencies: + "@types/express" "*" + "@types/express-unless" "*" + "@types/express-serve-static-core@*": version "4.17.19" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz#00acfc1632e729acac4f1530e9e16f6dd1508a1d" @@ -2309,6 +2315,32 @@ "@types/qs" "*" "@types/range-parser" "*" +"@types/express-serve-static-core@^4.17.18": + version "4.17.24" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz#ea41f93bf7e0d59cd5a76665068ed6aab6815c07" + integrity sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express-unless@*": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@types/express-unless/-/express-unless-0.5.2.tgz#07e29883d280778588644b03563d8796f870f20e" + integrity sha512-Q74UyYRX/zIgl1HSp9tUX2PlG8glkVm+59r7aK4KGKzC5jqKIOX6rrVLRQrzpZUQ84VukHtRoeAuon2nIssHPQ== + dependencies: + "@types/express" "*" + +"@types/express@*": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/express@4.17.3": version "4.17.3" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.3.tgz#38e4458ce2067873b09a73908df488870c303bd9" @@ -2332,13 +2364,6 @@ dependencies: "@types/node" "*" -"@types/fs-extra@^8.0.1": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.2.tgz#7125cc2e4bdd9bd2fc83005ffdb1d0ba00cca61f" - integrity sha512-SvSrYXfWSc7R4eqnOzbQF4TZmfpNSM9FrSWLU3EUnWBuyZqNBOrv1B1JA3byUDPUl9z4Ab3jeZG2eDdySlgNMg== - dependencies: - "@types/node" "*" - "@types/glob@*": version "7.1.4" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.4.tgz#ea59e21d2ee5c517914cb4bc8e4153b99e566672" @@ -2447,11 +2472,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.41.tgz#cf48562b53ab6cf85d28dde95f1d06815af275c8" integrity sha512-Q+eSkdYQJ2XK1AJnr4Ji8Gvk3sRDybEwfTvtL9CA25FFUSD2EgZQewN6VCyWYZCXg5MWZdwogdTNBhlWRcWS1w== -"@types/node@^8.10.59": - version "8.10.66" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3" - integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== - "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -2618,6 +2638,11 @@ dependencies: "@types/node" "*" +"@types/triple-beam@^1.3.0": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.2.tgz#38ecb64f01aa0d02b7c8f4222d7c38af6316fef8" + integrity sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g== + "@types/webpack-sources@^0.1.5": version "0.1.9" resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.9.tgz#da69b06eb34f6432e6658acb5a6893c55d983920" @@ -2627,6 +2652,13 @@ "@types/source-list-map" "*" source-map "^0.6.1" +"@types/winston@^2.4.4": + version "2.4.4" + resolved "https://registry.yarnpkg.com/@types/winston/-/winston-2.4.4.tgz#48cc744b7b42fad74b9a2e8490e0112bd9a3d08d" + integrity sha512-BVGCztsypW8EYwJ+Hq+QNYiT/MUyCif0ouBH+flrY66O5W+KIXAMML6E/0fJpm7VjIzgangahl5S03bJJQGrZw== + dependencies: + winston "*" + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" @@ -2923,6 +2955,16 @@ ajv@8.6.0: require-from-string "^2.0.2" uri-js "^4.2.2" +ajv@8.6.2: + version "8.6.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.2.tgz#2fb45e0e5fcbc0813326c1c3da535d1881bb0571" + integrity sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + ajv@^6.1.0, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.12.6: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -3148,11 +3190,6 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= -array-filter@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" - integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM= - array-find-index@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" @@ -3210,7 +3247,7 @@ arrify@^1.0.0, arrify@^1.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= -arrify@^2.0.0: +arrify@^2.0.0, arrify@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== @@ -3268,6 +3305,13 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== +async-retry@^1.3.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280" + integrity sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw== + dependencies: + retry "0.13.1" + async@^1.3.0: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -3313,13 +3357,6 @@ autoprefixer@^9.6.1: postcss "^7.0.32" postcss-value-parser "^4.1.0" -available-typed-arrays@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" - integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ== - dependencies: - array-filter "^1.0.0" - aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -4591,16 +4628,6 @@ concat-stream@^1.5.0: readable-stream "^2.2.2" typedarray "^0.0.6" -concat-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" - integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.0.2" - typedarray "^0.0.6" - concurrently@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-2.2.0.tgz#bad248e0bb129fb1621768903a6311d45d56895a" @@ -5259,10 +5286,10 @@ data-uri-to-buffer@3: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== -date-and-time@^0.13.0: - version "0.13.1" - resolved "https://registry.yarnpkg.com/date-and-time/-/date-and-time-0.13.1.tgz#d12ba07ac840d5b112dc4c83f8a03e8a51f78dd6" - integrity sha512-/Uge9DJAT+s+oAcDxtBhyR8+sKjUnZbYmyhbmWjTHNtX7B7oWD8YyYdeXcBRbwSj6hVvj+IQegJam7m7czhbFw== +date-and-time@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/date-and-time/-/date-and-time-2.0.0.tgz#99f5fb6b6c7bcd4d1f6dcbeb37553dc0ff797b65" + integrity sha512-HJSzj25iPm8E01nt+rSmCIlwjsmjvKfUivG/kXBglpymcHF1FolWAqWwTEV4FvN1Lx5UjPf0J1W4H8yQsVBfFg== date-format@^2.1.0: version "2.1.0" @@ -5293,7 +5320,7 @@ debug@3.1.0, debug@~3.1.0: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== @@ -5358,27 +5385,6 @@ deep-equal@^1.0.1: object-keys "^1.1.1" regexp.prototype.flags "^1.2.0" -deep-equal@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.5.tgz#55cd2fe326d83f9cbf7261ef0e060b3f724c5cb9" - integrity sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw== - dependencies: - call-bind "^1.0.0" - es-get-iterator "^1.1.1" - get-intrinsic "^1.0.1" - is-arguments "^1.0.4" - is-date-object "^1.0.2" - is-regex "^1.1.1" - isarray "^2.0.5" - object-is "^1.1.4" - object-keys "^1.1.1" - object.assign "^4.1.2" - regexp.prototype.flags "^1.3.0" - side-channel "^1.0.3" - which-boxed-primitive "^1.0.1" - which-collection "^1.0.1" - which-typed-array "^1.1.2" - deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -5390,9 +5396,9 @@ deep-freeze@0.0.1: integrity sha1-OgsABd4YZygZ39OM0x+RF5yJPoQ= deep-is@^0.1.3, deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== deepmerge@^4.2.2: version "4.2.2" @@ -5655,7 +5661,7 @@ duplexer@^0.1.1, duplexer@^0.1.2: resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== -duplexify@^3.4.2, duplexify@^3.5.0, duplexify@^3.6.0: +duplexify@^3.4.2, duplexify@^3.6.0: version "3.7.1" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== @@ -5665,7 +5671,7 @@ duplexify@^3.4.2, duplexify@^3.5.0, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" -duplexify@^4.0.0: +duplexify@^4.0.0, duplexify@^4.1.1: version "4.1.2" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" integrity sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw== @@ -5675,16 +5681,6 @@ duplexify@^4.0.0: readable-stream "^3.1.1" stream-shift "^1.0.0" -duplexify@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.1.tgz#7027dc374f157b122a8ae08c2d3ea4d2d953aa61" - integrity sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA== - dependencies: - end-of-stream "^1.4.1" - inherits "^2.0.3" - readable-stream "^3.1.1" - stream-shift "^1.0.0" - ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -5836,56 +5832,11 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2: - version "1.18.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4" - integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw== - dependencies: - call-bind "^1.0.2" - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - get-intrinsic "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.2" - is-callable "^1.2.3" - is-negative-zero "^2.0.1" - is-regex "^1.1.2" - is-string "^1.0.5" - object-inspect "^1.9.0" - object-keys "^1.1.1" - object.assign "^4.1.2" - string.prototype.trimend "^1.0.4" - string.prototype.trimstart "^1.0.4" - unbox-primitive "^1.0.0" - -es-get-iterator@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.2.tgz#9234c54aba713486d7ebde0220864af5e2b283f7" - integrity sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.0" - has-symbols "^1.0.1" - is-arguments "^1.1.0" - is-map "^2.0.2" - is-set "^2.0.2" - is-string "^1.0.5" - isarray "^2.0.5" - es-module-lexer@^0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.7.1.tgz#c2c8e0f46f2df06274cdaf0dd3f3b33e0a0b267d" integrity sha512-MgtWFl5No+4S3TmhDmCz2ObFGm6lEpTnzbQi+Dd+pw4mlTIZTmM2iAs5gRlmx5zS9luzobCSBSI90JM/1/JgOw== -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - es5-ext@^0.10.12, es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: version "0.10.53" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" @@ -6448,19 +6399,21 @@ find-versions@^4.0.0: dependencies: semver-regex "^3.1.2" -firebase-admin@^8.10.0: - version "8.13.0" - resolved "https://registry.yarnpkg.com/firebase-admin/-/firebase-admin-8.13.0.tgz#997d34ae8357d7dc162ba622148bbebcf7f2e923" - integrity sha512-krXj5ncWMJBhCpXSn9UFY6zmDWjFjqgx+1e9ATXKFYndEjmKtNBuJzqdrAdDh7aTUR7X6+0TPx4Hbc08kd0lwQ== +firebase-admin@^9.11.1: + version "9.11.1" + resolved "https://registry.yarnpkg.com/firebase-admin/-/firebase-admin-9.11.1.tgz#b4f472ed51951937f333a4d88a0693ad37ffc90a" + integrity sha512-Y9fjelljy6MKqwsSbM/UN1k8gBQh5zfm5fCTe0Z6Gch2T3nDUIPsTcf+jfe4o40/MPYuybili9XJjTMmM2e5MQ== dependencies: - "@firebase/database" "^0.6.0" - "@types/node" "^8.10.59" + "@firebase/database" "^0.10.0" + "@firebase/database-types" "^0.7.2" + "@types/node" ">=12.12.47" dicer "^0.3.0" jsonwebtoken "^8.5.1" - node-forge "^0.7.6" + jwks-rsa "^2.0.2" + node-forge "^0.10.0" optionalDependencies: - "@google-cloud/firestore" "^3.0.0" - "@google-cloud/storage" "^4.1.2" + "@google-cloud/firestore" "^4.5.0" + "@google-cloud/storage" "^5.3.0" firebase-functions-test@^0.2.2: version "0.2.3" @@ -6471,19 +6424,20 @@ firebase-functions-test@^0.2.2: lodash "^4.17.5" firebase-functions@^3.6.0: - version "3.13.3" - resolved "https://registry.yarnpkg.com/firebase-functions/-/firebase-functions-3.13.3.tgz#f18e44baa05954cd86f32376dec03ac3c20fd262" - integrity sha512-VBYRDkfZtkTgepBVEEmie/9cjV3exNG3Xr0SPasgBSrFlG/fpcw/2AUeXvAfgWx2nIqZbRZXsR1L3LOPTkB11A== + version "3.15.5" + resolved "https://registry.yarnpkg.com/firebase-functions/-/firebase-functions-3.15.5.tgz#d0b9d719c88332048f59ee3ecda5755cb0dabda2" + integrity sha512-w5Twk7txwfPFasx+i25f0U4fLiSa06XvwOIoEwazGYLPOLe18b2jCDQ9X8aeJm8S7FZE40ODWGVTLtF6dX6xHg== dependencies: + "@types/cors" "^2.8.5" "@types/express" "4.17.3" cors "^2.8.5" express "^4.17.1" lodash "^4.17.14" firebase-tools@^9.0.0: - version "9.17.0" - resolved "https://registry.yarnpkg.com/firebase-tools/-/firebase-tools-9.17.0.tgz#34d924a81aecc7c59eb4193a774d8408f571d02a" - integrity sha512-+srVeGz6w3ouR567ELHe33Nm0nwhhVvu5IHuQOaH0qQvDzX8LgBGqCpatqgYToUmPgDMGG34f1d+aa8vQ0pEBw== + version "9.18.0" + resolved "https://registry.yarnpkg.com/firebase-tools/-/firebase-tools-9.18.0.tgz#70e07132f3c10edfd8b73005a4b608294ca42a1b" + integrity sha512-yth3C6ZdzkXbQVKgmrIYn/NBvkCnstEfsTsqvLUL+Mo0s5Cq+JR8A8DKLJHyWBrWjKkT5R9VfefnkhHCUrjRNw== dependencies: "@google-cloud/pubsub" "^2.7.0" "@types/archiver" "^5.1.0" @@ -6619,11 +6573,6 @@ for-in@^1.0.2: resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= -foreach@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" - integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= - forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -6780,28 +6729,6 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -gaxios@^2.0.0, gaxios@^2.1.0: - version "2.3.4" - resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-2.3.4.tgz#eea99353f341c270c5f3c29fc46b8ead56f0a173" - integrity sha512-US8UMj8C5pRnao3Zykc4AAVr+cffoNKRTg9Rsf2GiuZCW69vgJj38VK2PzlPuQU73FZ/nTk9/Av6/JGcE1N9vA== - 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" - -gaxios@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-3.2.0.tgz#11b6f0e8fb08d94a10d4d58b044ad3bec6dd486a" - 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" - gaxios@^4.0.0: version "4.3.0" resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-4.3.0.tgz#ad4814d89061f85b97ef52aed888c5dbec32f774" @@ -6813,14 +6740,6 @@ gaxios@^4.0.0: is-stream "^2.0.0" node-fetch "^2.3.0" -gcp-metadata@^3.4.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-3.5.0.tgz#6d28343f65a6bbf8449886a0c0e4a71c77577055" - integrity sha512-ZQf+DLZ5aKcRpLzYUyBS3yo3N0JSa82lNDO8rj3nMSlovLcz2riKFBsYgDzeXcv75oo5eqB2lx+B14UvPoCRnA== - dependencies: - gaxios "^2.1.0" - json-bigint "^0.3.0" - gcp-metadata@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-4.3.0.tgz#0423d06becdbfb9cbb8762eaacf14d5324997900" @@ -6829,15 +6748,16 @@ gcp-metadata@^4.2.0: gaxios "^4.0.0" json-bigint "^1.0.0" -gcs-resumable-upload@^2.2.4: - version "2.3.3" - resolved "https://registry.yarnpkg.com/gcs-resumable-upload/-/gcs-resumable-upload-2.3.3.tgz#02c616ed17eff6676e789910aeab3907d412c5f8" - integrity sha512-sf896I5CC/1AxeaGfSFg3vKMjUq/r+A3bscmVzZm10CElyRanN0XwPu/MxeIO4LSP+9uF6yKzXvNsaTsMXUG6Q== +gcs-resumable-upload@^3.3.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/gcs-resumable-upload/-/gcs-resumable-upload-3.3.1.tgz#bb3b0d776ce64b7c40d81fffadac7d54d878a9f3" + integrity sha512-WyC0i4VkslIdrdmeM5PNuGzANALLXTG5RoHb08OE30gYT+FEvCDPiA8KOjV2s1wOu9ngEW4+IuzBjtP/ni7UdQ== dependencies: abort-controller "^3.0.0" configstore "^5.0.0" - gaxios "^2.0.0" - google-auth-library "^5.0.0" + extend "^3.0.2" + gaxios "^4.0.0" + google-auth-library "^7.0.0" pumpify "^2.0.0" stream-events "^1.0.4" @@ -6856,7 +6776,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.1, get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: +get-intrinsic@^1.0.2: version "1.1.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== @@ -6895,6 +6815,11 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + get-uri@3: version "3.0.2" resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-3.0.2.tgz#f0ef1356faabc70e1f9404fa3b66b2ba9bfc725c" @@ -7093,21 +7018,6 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" -google-auth-library@^5.0.0, google-auth-library@^5.5.0: - version "5.10.1" - resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-5.10.1.tgz#504ec75487ad140e68dd577c21affa363c87ddff" - integrity sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg== - dependencies: - arrify "^2.0.0" - base64-js "^1.3.0" - ecdsa-sig-formatter "^1.0.11" - fast-text-encoding "^1.0.0" - gaxios "^2.1.0" - gcp-metadata "^3.4.0" - gtoken "^4.1.0" - jws "^4.0.0" - lru-cache "^5.0.0" - google-auth-library@^6.1.3: version "6.1.6" resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-6.1.6.tgz#deacdcdb883d9ed6bac78bb5d79a078877fdf572" @@ -7138,26 +7048,20 @@ google-auth-library@^7.0.0, google-auth-library@^7.6.1: jws "^4.0.0" lru-cache "^6.0.0" -google-gax@^1.15.3: - version "1.15.3" - resolved "https://registry.yarnpkg.com/google-gax/-/google-gax-1.15.3.tgz#e88cdcbbd19c7d88cc5fd7d7b932c4d1979a5aca" - integrity sha512-3JKJCRumNm3x2EksUTw4P1Rad43FTpqrtW9jzpf3xSMYXx+ogaqTM1vGo7VixHB4xkAyATXVIa3OcNSh8H9zsQ== +google-auth-library@^7.0.2: + version "7.9.1" + resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-7.9.1.tgz#b90a3a0fa67d6ba78c43ffdeeb0a66fcebe6fb91" + integrity sha512-cWGykH2WBR+UuYPGRnGVZ6Cjq2ftQiEIFjQWNIRIauZH7hUWoYTr/lkKUqLTYt5dex77nlWWVQ8aPV80mhfp5w== dependencies: - "@grpc/grpc-js" "~1.0.3" - "@grpc/proto-loader" "^0.5.1" - "@types/fs-extra" "^8.0.1" - "@types/long" "^4.0.0" - abort-controller "^3.0.0" - duplexify "^3.6.0" - google-auth-library "^5.0.0" - is-stream-ended "^0.1.4" - lodash.at "^4.6.0" - lodash.has "^4.5.2" - node-fetch "^2.6.0" - protobufjs "^6.8.9" - retry-request "^4.0.0" - semver "^6.0.0" - walkdir "^0.4.0" + 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@^2.24.1: version "2.25.0" @@ -7178,13 +7082,6 @@ google-gax@^2.24.1: protobufjs "6.11.2" retry-request "^4.0.0" -google-p12-pem@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-2.0.5.tgz#b1c44164d567ae894f7a19b4ff362a06be5b793b" - integrity sha512-7RLkxwSsMsYh9wQ5Vb2zRtkAHvqPvfoMGag+nugl1noYO7gf0844Yr9TIFA5NEBMAeVt2Z+Imu7CQMp3oNatzQ== - dependencies: - node-forge "^0.10.0" - google-p12-pem@^3.0.3: version "3.1.2" resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-3.1.2.tgz#c3d61c2da8e10843ff830fdb0d2059046238c1d4" @@ -7224,16 +7121,6 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= -gtoken@^4.1.0: - version "4.1.4" - resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-4.1.4.tgz#925ff1e7df3aaada06611d30ea2d2abf60fcd6a7" - integrity sha512-VxirzD0SWoFUo5p8RDP8Jt2AGyOmyYcT/pOUgDKJCK+iSw0TMqwrVfY37RXTNmoKwrzmDHSk0GMT9FsgVmnVSA== - dependencies: - gaxios "^2.1.0" - google-p12-pem "^2.0.0" - jws "^4.0.0" - mime "^2.2.0" - gtoken@^5.0.4: version "5.3.1" resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-5.3.1.tgz#c1c2598a826f2b5df7c6bb53d7be6cf6d50c3c78" @@ -7302,11 +7189,6 @@ has-ansi@^2.0.0: dependencies: ansi-regex "^2.0.0" -has-bigints@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" - integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== - has-binary2@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" @@ -7783,25 +7665,6 @@ inquirer@8.1.1: strip-ansi "^6.0.0" through "^2.3.6" -inquirer@^6.2.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" - integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== - dependencies: - ansi-escapes "^3.2.0" - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^2.0.0" - lodash "^4.17.12" - mute-stream "0.0.7" - run-async "^2.2.0" - rxjs "^6.4.0" - string-width "^2.1.0" - strip-ansi "^5.1.0" - through "^2.3.6" - inquirer@~6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.3.1.tgz#7a413b5e7950811013a3db491c61d1f3b776e8e7" @@ -7885,7 +7748,7 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-arguments@^1.0.4, is-arguments@^1.1.0: +is-arguments@^1.0.4: version "1.1.0" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9" integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg== @@ -7902,11 +7765,6 @@ is-arrayish@^0.3.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== -is-bigint@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a" - integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA== - is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" @@ -7921,23 +7779,11 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-boolean-object@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.1.tgz#3c0878f035cb821228d350d2e1e36719716a3de8" - integrity sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng== - dependencies: - call-bind "^1.0.2" - is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.4, is-callable@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" - integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== - is-ci@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" @@ -7978,7 +7824,7 @@ is-data-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-date-object@^1.0.1, is-date-object@^1.0.2: +is-date-object@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.4.tgz#550cfcc03afada05eea3dd30981c7b09551f73e5" integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A== @@ -8085,21 +7931,11 @@ is-lambda@^1.0.1: resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU= -is-map@^2.0.1, is-map@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" - integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== - is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= -is-negative-zero@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" - integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== - is-npm@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" @@ -8110,11 +7946,6 @@ is-npm@^5.0.0: resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== -is-number-object@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb" - integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw== - is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -8204,7 +8035,7 @@ is-reference@^1.2.1: dependencies: "@types/estree" "*" -is-regex@^1.0.4, is-regex@^1.1.1, is-regex@^1.1.2: +is-regex@^1.0.4: version "1.1.3" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f" integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ== @@ -8217,11 +8048,6 @@ is-resolvable@^1.1.0: resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== -is-set@^2.0.1, is-set@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" - integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== - is-stream-ended@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-stream-ended/-/is-stream-ended-0.1.4.tgz#f50224e95e06bce0e356d440a4827cd35b267eda" @@ -8237,23 +8063,11 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -is-string@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.6.tgz#3fe5d5992fb0d93404f32584d4b0179a71b54a5f" - integrity sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w== - is-subset@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY= -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - is-text-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" @@ -8261,17 +8075,6 @@ is-text-path@^1.0.0: dependencies: text-extensions "^1.0.0" -is-typed-array@^1.1.3: - version "1.1.5" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.5.tgz#f32e6e096455e329eb7b423862456aa213f0eb4e" - integrity sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug== - dependencies: - available-typed-arrays "^1.0.2" - call-bind "^1.0.2" - es-abstract "^1.18.0-next.2" - foreach "^2.0.5" - has-symbols "^1.0.1" - is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -8292,16 +8095,6 @@ is-utf8@^0.2.0: resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= -is-weakmap@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" - integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== - -is-weakset@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83" - integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw== - is-what@^3.12.0: version "3.14.1" resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1" @@ -8317,7 +8110,7 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= -is-wsl@^2.1.1, is-wsl@^2.2.0: +is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== @@ -8353,11 +8146,6 @@ isarray@2.0.1: resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4= -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - isbinaryfile@^4.0.6: version "4.0.8" resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.8.tgz#5d34b94865bd4946633ecc78a026fc76c5b11fcf" @@ -8502,6 +8290,13 @@ join-path@^1.1.1: url-join "0.0.1" valid-url "^1" +jose@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/jose/-/jose-2.0.5.tgz#29746a18d9fff7dcf9d5d2a6f62cb0c7cd27abd3" + integrity sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA== + dependencies: + "@panva/asn1.js" "^1.0.0" + jquery@^3.4.1: version "3.6.0" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470" @@ -8552,13 +8347,6 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= -json-bigint@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-0.3.1.tgz#0c1729d679f580d550899d6a2226c228564afe60" - integrity sha512-DGWnSzmusIreWlEupsUelHrhwmPPE+FiQvg+drKfk2p+bdEYa5mp4PJ8JsCWqae0M2jQNb0HPvnwvf1qOTThzQ== - dependencies: - bignumber.js "^9.0.0" - json-bigint@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" @@ -8720,6 +8508,17 @@ jwa@^2.0.0: ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" +jwks-rsa@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/jwks-rsa/-/jwks-rsa-2.0.4.tgz#59d95e39f300783a8582ef8aa37d5ebbc6a8aa6f" + integrity sha512-iJqVCECYZZ+3oPmY1qXv3Fq+3ywDtuNEVBvG41pPlaR0zyGxa12nC0beAOBBUhETJmc05puS50mRQN4NkCGhmg== + dependencies: + "@types/express-jwt" "0.0.42" + debug "^4.3.2" + jose "^2.0.5" + limiter "^1.1.5" + lru-memoizer "^2.1.4" + jws@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" @@ -8933,6 +8732,11 @@ lie@~3.3.0: dependencies: immediate "~3.0.5" +limiter@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2" + integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA== + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -9039,16 +8843,16 @@ lodash._shimkeys@~2.4.1: dependencies: lodash._objecttypes "~2.4.1" -lodash.at@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.at/-/lodash.at-4.6.0.tgz#93cdce664f0a1994ea33dd7cd40e23afd11b0ff8" - integrity sha1-k83OZk8KGZTqM9181A4jr9EbD/g= - lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -9069,11 +8873,6 @@ lodash.flatten@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= -lodash.has@^4.5.2: - version "4.5.2" - resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" - integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI= - lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" @@ -9182,7 +8981,7 @@ lodash.values@^2.4.1: dependencies: lodash.keys "~2.4.1" -lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.5.1: +lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.5.1: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -9269,7 +9068,7 @@ lru-cache@^2.5.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" integrity sha1-bUUk6LlV+V1PW1iFHOId1y+06VI= -lru-cache@^5.0.0, lru-cache@^5.1.1: +lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== @@ -9283,6 +9082,22 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lru-cache@~4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e" + integrity sha1-HRdnnAac2l0ECZGgnbwsDbN35V4= + dependencies: + pseudomap "^1.0.1" + yallist "^2.0.0" + +lru-memoizer@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/lru-memoizer/-/lru-memoizer-2.1.4.tgz#b864d92b557f00b1eeb322156a0409cb06dafac6" + integrity sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ== + dependencies: + lodash.clonedeep "^4.5.0" + lru-cache "~4.0.0" + lru-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" @@ -9577,20 +9392,20 @@ mime-db@1.49.0, "mime-db@>= 1.43.0 < 2": resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA== -mime-types@^2.0.8, mime-types@^2.1.27, mime-types@~2.1.17: - version "2.1.30" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d" - integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg== - dependencies: - mime-db "1.47.0" - -mime-types@^2.1.12, mime-types@^2.1.16, mime-types@^2.1.31, mime-types@~2.1.19, mime-types@~2.1.24: +mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.16, mime-types@^2.1.31, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.32" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5" integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A== dependencies: mime-db "1.49.0" +mime-types@^2.1.27, mime-types@~2.1.17: + version "2.1.30" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d" + integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg== + dependencies: + mime-db "1.47.0" + mime@1.6.0, mime@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" @@ -9995,7 +9810,7 @@ node-fetch-npm@^2.0.2: json-parse-better-errors "^1.0.0" safe-buffer "^5.1.1" -node-fetch@2.6.1, node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1: +node-fetch@2.6.1, node-fetch@^2.3.0, node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== @@ -10005,11 +9820,6 @@ node-forge@^0.10.0: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== -node-forge@^0.7.6: - version "0.7.6" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" - integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== - node-gyp@^7.1.0: version "7.1.2" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.1.2.tgz#21a810aebb187120251c3bcec979af1587b188ae" @@ -10271,7 +10081,7 @@ object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== -object-is@^1.0.1, object-is@^1.1.4: +object-is@^1.0.1: version "1.1.5" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== @@ -10291,7 +10101,7 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.1.0, object.assign@^4.1.2: +object.assign@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== @@ -10369,13 +10179,14 @@ open@^6.3.0: dependencies: is-wsl "^1.1.0" -open@^7.0.3: - version "7.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" - integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== +"open@^7.0.3 || ^8.0.0": + version "8.0.9" + resolved "https://registry.yarnpkg.com/open/-/open-8.0.9.tgz#a7a739fed91dfa3734094255badbeabd71116a12" + integrity sha512-vbCrqMav3K8mCCy8NdK4teUky0tpDrBbuiDLduCdVhc5oA9toJMip9rBkuwdwSI9E7NOkz4VkLWPi8DD2MP1gQ== dependencies: - is-docker "^2.0.0" - is-wsl "^2.1.1" + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" openapi3-ts@^2.0.1: version "2.0.1" @@ -10509,7 +10320,7 @@ p-limit@^2.0.0, p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.2, p-limit@^3.1.0: +p-limit@^3.0.1, p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -11624,7 +11435,7 @@ proto3-json-serializer@^0.1.1: resolved "https://registry.yarnpkg.com/proto3-json-serializer/-/proto3-json-serializer-0.1.3.tgz#3b4d5f481dbb923dd88e259ed03b0629abc9a8e7" integrity sha512-X0DAtxCBsy1NDn84huVFGOFgBslT2gBmM+85nY6/5SOAaCon1jzVNdvi74foIyFvs5CjtSbQsepsM5TsyNhqQw== -protobufjs@6.11.2, protobufjs@^6.10.0, protobufjs@^6.8.6, protobufjs@^6.8.9: +protobufjs@6.11.2, protobufjs@^6.10.0, protobufjs@^6.8.6: version "6.11.2" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.2.tgz#de39fabd4ed32beaa08e9bb1e30d08544c1edf8b" integrity sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw== @@ -11703,6 +11514,11 @@ prr@~1.0.1: resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= +pseudomap@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= + psl@^1.1.28: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -11947,7 +11763,7 @@ readable-stream@1.1.x: isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@2 || 3", readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: +readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -12085,7 +11901,7 @@ regex-parser@^2.2.11: resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.11.tgz#3b37ec9049e19479806e878cabe7c1ca83ccfe58" integrity sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q== -regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.0: +regexp.prototype.flags@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== @@ -12277,7 +12093,7 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -retry-request@^4.0.0: +retry-request@^4.0.0, retry-request@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-4.2.2.tgz#b7d82210b6d2651ed249ba3497f07ea602f1a903" integrity sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg== @@ -12285,6 +12101,11 @@ retry-request@^4.0.0: debug "^4.1.1" extend "^3.0.2" +retry@0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + retry@^0.10.0: version "0.10.1" resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" @@ -12764,7 +12585,7 @@ shelljs@^0.8.0, shelljs@^0.8.3: interpret "^1.0.0" rechoir "^0.6.2" -side-channel@^1.0.3, side-channel@^1.0.4: +side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== @@ -13241,22 +13062,6 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string.prototype.trimend@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" - integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - -string.prototype.trimstart@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" - integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -13575,16 +13380,16 @@ tcp-port-used@^1.0.1: debug "4.3.1" is2 "^2.0.6" -teeny-request@^6.0.0: - version "6.0.3" - resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-6.0.3.tgz#b617f9d5b7ba95c76a3f257f6ba2342b70228b1f" - integrity sha512-TZG/dfd2r6yeji19es1cUIwAlVD8y+/svB1kAC2Y0bjEyysrfbO8EZvJBRwIE6WkwmUoB7uvWLwTIhJbMXZ1Dw== +teeny-request@^7.0.0: + version "7.1.2" + resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-7.1.2.tgz#69f8d4c3c2958cf0040f9a5c32a5c9db591d412a" + integrity sha512-Mr4NYZuniKDpgcLxdBkDE1CcWy98Aw1ennn6oNofen+XWUvDs+ZZzBAujy6XOAVwwLLZMwEQSfdljUI+ebs4Ww== dependencies: http-proxy-agent "^4.0.0" https-proxy-agent "^5.0.0" - node-fetch "^2.2.0" + node-fetch "^2.6.1" stream-events "^1.0.5" - uuid "^7.0.0" + uuid "^8.0.0" tempfile@^1.1.1: version "1.1.1" @@ -13663,14 +13468,6 @@ through2@^2.0.0, through2@^2.0.2: readable-stream "~2.3.6" xtend "~4.0.1" -through2@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.2.tgz#99f88931cfc761ec7678b41d5d7336b5b6a07bf4" - integrity sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ== - dependencies: - inherits "^2.0.4" - readable-stream "2 || 3" - through@2, "through@>=2.2.7 <3", through@X.X.X, through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -13868,7 +13665,7 @@ tslib@2.3.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== -tslib@^1.10.0, tslib@^1.11.1, tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0: +tslib@^1.10.0, tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -14070,16 +13867,6 @@ uglify-to-browserify@~1.0.0: resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" integrity sha1-bgkk1r2mta/jSeOabWMoUKD4grc= -unbox-primitive@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" - integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== - dependencies: - function-bind "^1.1.1" - has-bigints "^1.0.1" - has-symbols "^1.0.2" - which-boxed-primitive "^1.0.2" - underscore@>=1.8.3, underscore@^1.9.1: version "1.13.1" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1" @@ -14291,7 +14078,7 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@8.3.2: +uuid@8.3.2, uuid@^8.0.0: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== @@ -14306,11 +14093,6 @@ uuid@^3.0.0, uuid@^3.3.2, uuid@^3.4.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^7.0.0: - version "7.0.3" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" - integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== - valid-url@^1: version "1.0.9" resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200" @@ -14355,11 +14137,6 @@ void-elements@^2.0.0: resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w= -walkdir@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.4.1.tgz#dc119f83f4421df52e3061e514228a2db20afa39" - integrity sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ== - watchpack@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.2.0.tgz#47d78f5415fe550ecd740f99fe2882323a58b1ce" @@ -14558,27 +14335,6 @@ when@^3.7.5: resolved "https://registry.yarnpkg.com/when/-/when-3.7.8.tgz#c7130b6a7ea04693e842cdc9e7a1f2aa39a39f82" integrity sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I= -which-boxed-primitive@^1.0.1, which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - 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" - -which-collection@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" - integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== - dependencies: - is-map "^2.0.1" - is-set "^2.0.1" - is-weakmap "^2.0.1" - is-weakset "^2.0.1" - which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" @@ -14589,19 +14345,6 @@ which-pm-runs@^1.0.0: resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= -which-typed-array@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.4.tgz#8fcb7d3ee5adf2d771066fba7cf37e32fe8711ff" - integrity sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA== - dependencies: - available-typed-arrays "^1.0.2" - call-bind "^1.0.0" - es-abstract "^1.18.0-next.1" - foreach "^2.0.5" - function-bind "^1.1.1" - has-symbols "^1.0.1" - is-typed-array "^1.1.3" - which@^1.2.1, which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -14648,7 +14391,7 @@ winston-transport@^4.4.0: readable-stream "^2.3.7" triple-beam "^1.2.0" -winston@^3.0.0: +winston@*, winston@^3.0.0: version "3.3.3" resolved "https://registry.yarnpkg.com/winston/-/winston-3.3.3.tgz#ae6172042cafb29786afa3d09c8ff833ab7c9170" integrity sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw== @@ -14732,12 +14475,7 @@ ws@^6.2.1: dependencies: async-limiter "~1.0.0" -ws@^7.2.3: - version "7.5.4" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.4.tgz#56bfa20b167427e138a7795de68d134fe92e21f9" - integrity sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg== - -ws@^7.3.1: +ws@^7.2.3, ws@^7.3.1: version "7.5.5" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881" integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w== @@ -14802,6 +14540,11 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== +yallist@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= + yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3, yallist@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"