From 1350920c37233b19864e4e5522878eb644a1e132 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 26 Oct 2023 14:02:36 -0400 Subject: [PATCH] Standalone schematics (#3451) * Make ng-add work with standalone mode * Add simple AppCheck feature addition * Cleanup --- src/schematics/interfaces.ts | 6 +- src/schematics/ng-add.jasmine.ts | 105 +++++++-------- src/schematics/setup/index.ts | 91 +++++-------- src/schematics/utils.ts | 218 ++++++++++++------------------- 4 files changed, 171 insertions(+), 249 deletions(-) diff --git a/src/schematics/interfaces.ts b/src/schematics/interfaces.ts index 795aeaa9c..bd4446420 100644 --- a/src/schematics/interfaces.ts +++ b/src/schematics/interfaces.ts @@ -4,6 +4,7 @@ export const enum FEATURES { Hosting, Authentication, Analytics, + AppCheck, Database, Functions, Messaging, @@ -16,9 +17,10 @@ export const enum FEATURES { export const featureOptions = [ { name: 'ng deploy -- hosting', value: FEATURES.Hosting }, { name: 'Authentication', value: FEATURES.Authentication }, + { name: 'Google Analytics', value: FEATURES.Analytics }, + { name: 'App Check', value: FEATURES.AppCheck }, { name: 'Firestore', value: FEATURES.Firestore }, { name: 'Realtime Database', value: FEATURES.Database }, - { name: 'Analytics', value: FEATURES.Analytics }, { name: 'Cloud Functions (callable)', value: FEATURES.Functions }, { name: 'Cloud Messaging', value: FEATURES.Messaging }, { name: 'Performance Monitoring', value: FEATURES.Performance }, @@ -47,7 +49,7 @@ export interface NgAddNormalizedOptions { } export interface DeployOptions { - project: string; + project?: string; } export interface FirebaseProject { diff --git a/src/schematics/ng-add.jasmine.ts b/src/schematics/ng-add.jasmine.ts index f710efb72..ff5199318 100644 --- a/src/schematics/ng-add.jasmine.ts +++ b/src/schematics/ng-add.jasmine.ts @@ -299,114 +299,113 @@ describe('ng-add', () => { tree.create('package.json', JSON.stringify(generatePackageJson())); }); - it('generates new files if starting from scratch', async () => { - const result = await setupProject(tree, {} as any, [FEATURES.Hosting], { + it('generates new files if starting from scratch', () => { + setupProject(tree, {} as any, [FEATURES.Hosting], { firebaseProject: { projectId: FIREBASE_PROJECT } as any, projectType: PROJECT_TYPE.Static, project: PROJECT_NAME, prerender: false, - }) as Tree; - expect(result.read('firebase.json').toString()).toEqual(initialFirebaseJson); - expect(result.read('.firebaserc').toString()).toEqual(initialFirebaserc); - expect(result.read('angular.json').toString()).toEqual(initialAngularJson); + }); + expect(tree.read('firebase.json')?.toString()).toEqual(initialFirebaseJson); + expect(tree.read('.firebaserc')?.toString()).toEqual(initialFirebaserc); + expect(tree.read('angular.json')?.toString()).toEqual(initialAngularJson); }); - it('uses default project', async () => { - const result = await setupProject(tree, {} as any, [FEATURES.Hosting], { + it('uses default project', () => { + setupProject(tree, {} as any, [FEATURES.Hosting], { firebaseProject: { projectId: FIREBASE_PROJECT } as any, projectType: PROJECT_TYPE.Static, project: undefined, prerender: false, - }) as Tree; - expect(result.read('firebase.json').toString()).toEqual(overwriteFirebaseJson); - expect(result.read('.firebaserc').toString()).toEqual(overwriteFirebaserc); - expect(result.read('angular.json').toString()).toEqual(overwriteAngularJson); + }); + expect(tree.read('firebase.json')?.toString()).toEqual(overwriteFirebaseJson); + expect(tree.read('.firebaserc')?.toString()).toEqual(overwriteFirebaserc); + expect(tree.read('angular.json')?.toString()).toEqual(overwriteAngularJson); }); - it('runs if source root is relative to workspace root', async () => { + it('runs if source root is relative to workspace root', () => { const angularJson = generateAngularJson(); const project: {root: string, sourceRoot?: string} = angularJson.projects[PROJECT_NAME]; project.sourceRoot = `${project.root}/src`; tree.overwrite('angular.json', JSON.stringify(angularJson)); - const promise = setupProject(tree, {} as any, [FEATURES.Hosting], { + setupProject(tree, {} as any, [FEATURES.Hosting], { firebaseProject: { projectId: FIREBASE_PROJECT } as any, projectType: PROJECT_TYPE.Static, project: undefined, prerender: false, }); - await expectAsync(promise).toBeResolved(); }); - it('overrides existing files', async () => { - await setupProject(tree, {} as any, [FEATURES.Hosting], { + it('overrides existing files', () => { + 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(tree, {} as any, [FEATURES.Hosting], { + setupProject(tree, {} as any, [FEATURES.Hosting], { firebaseProject: { projectId: OTHER_FIREBASE_PROJECT_NAME } as any, projectType: PROJECT_TYPE.Static, project: OTHER_PROJECT_NAME, prerender: false, - }) as Tree; - expect(result.read('firebase.json').toString()).toEqual(projectFirebaseJson); - expect(result.read('.firebaserc').toString()).toEqual(projectFirebaserc); - expect(result.read('angular.json').toString()).toEqual(projectAngularJson); + }); + expect(tree.read('firebase.json')?.toString()).toEqual(projectFirebaseJson); + expect(tree.read('.firebaserc')?.toString()).toEqual(projectFirebaserc); + expect(tree.read('angular.json')?.toString()).toEqual(projectAngularJson); }); }); describe('error handling', () => { - it('fails if project not defined', async () => { + it('fails if project not defined', () => { const tree = Tree.empty(); const angularJSON = generateAngularJson(); delete angularJSON.defaultProject; tree.create('angular.json', JSON.stringify(angularJSON)); tree.create('package.json', JSON.stringify(generatePackageJson())); - await expectAsync(setupProject(tree, {} as any, [FEATURES.Hosting], { + expect(() => setupProject(tree, {} as any, [FEATURES.Hosting], { firebaseProject: { projectId: FIREBASE_PROJECT } as any, projectType: PROJECT_TYPE.Static, project: undefined, prerender: false, - })).toBeRejectedWith( + })).toThrow( new SchematicsException('No Angular project selected and no default project in the workspace') ); }); - it('Should throw if angular.json not found', async () => { - await expectAsync(setupProject(Tree.empty(), {} as any, [FEATURES.Hosting], { + it('Should throw if angular.json not found', () => { + expect(() => 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')); + })).toThrow(new SchematicsException('Could not find angular.json')); }); - it('Should throw if angular.json can not be parsed', async () => { + it('Should throw if angular.json can not be parsed', () => { const tree = Tree.empty(); tree.create('angular.json', 'hi'); tree.create('package.json', JSON.stringify(generatePackageJson())); - await expectAsync(setupProject(tree, {} as any, [FEATURES.Hosting], { + expect(() => 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')); + })).toThrow(new SchematicsException('Could not parse angular.json')); }); - it('Should throw if specified project does not exist ', async () => { + it('Should throw if specified project does not exist ', () => { const tree = Tree.empty(); tree.create('angular.json', JSON.stringify({ projects: {} })); tree.create('package.json', JSON.stringify(generatePackageJson())); - await expectAsync(setupProject(tree, {} as any, [FEATURES.Hosting], { + expect(() => 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')); + })).toThrow(new SchematicsException('The specified Angular project is not defined in this workspace')); }); - it('Should throw if specified project is not application', async () => { + it('Should throw if specified project is not application', () => { const tree = Tree.empty(); tree.create( 'angular.json', @@ -415,15 +414,15 @@ describe('ng-add', () => { }) ); tree.create('package.json', JSON.stringify(generatePackageJson())); - await expectAsync(setupProject(tree, {} as any, [FEATURES.Hosting], { + expect(() => 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')); + })).toThrow(new SchematicsException('Deploy requires an Angular project type of "application" in angular.json')); }); - it('Should throw if app does not have architect configured', async () => { + it('Should throw if app does not have architect configured', () => { const tree = Tree.empty(); tree.create( 'angular.json', @@ -432,12 +431,12 @@ describe('ng-add', () => { }) ); tree.create('package.json', JSON.stringify(generatePackageJson())); - await expectAsync(setupProject(tree, {} as any, [FEATURES.Hosting], { + expect(() => setupProject(tree, {} as any, [FEATURES.Hosting], { firebaseProject: { projectId: FIREBASE_PROJECT } as any, projectType: PROJECT_TYPE.Static, project: PROJECT_NAME, prerender: false, - })).toBeRejectedWith( + })).toThrow( new SchematicsException('Angular project "pie-ka-chu" has a malformed angular.json') ); }); @@ -474,17 +473,17 @@ describe('ng-add', () => { ).toThrowError(/firebase.json: Unexpected token/); });*/ - it('Should throw if .firebaserc is broken', async () => { + it('Should throw if .firebaserc is broken', () => { const tree = Tree.empty(); tree.create('angular.json', JSON.stringify(generateAngularJson())); tree.create('package.json', JSON.stringify(generatePackageJson())); tree.create('.firebaserc', `I'm broken 😔`); - await expectAsync(setupProject(tree, {} as any, [FEATURES.Hosting], { + expect(() => setupProject(tree, {} as any, [FEATURES.Hosting], { firebaseProject: { projectId: FIREBASE_PROJECT } as any, projectType: PROJECT_TYPE.Static, project: PROJECT_NAME, prerender: false, - })).toBeRejectedWith( + })).toThrow( parseInt(process.versions.node, 10) >= 20 ? new SchematicsException(`Error when parsing .firebaserc: Unexpected token 'I', "I'm broken 😔" is not valid JSON`) : new SchematicsException('Error when parsing .firebaserc: Unexpected token I in JSON at position 0') @@ -532,37 +531,39 @@ describe('ng-add', () => { describe('universal app', () => { - it('should add a @angular/fire builder', async () => { + it('should add a @angular/fire builder', () => { const tree = Tree.empty(); tree.create('angular.json', JSON.stringify(generateAngularJsonWithServer())); tree.create('package.json', JSON.stringify(generatePackageJson())); // TODO mock addTask - const result = await setupProject(tree, {addTask: () => undefined} as any, [FEATURES.Hosting], { + setupProject(tree, {addTask: () => undefined} as any, [FEATURES.Hosting], { firebaseProject: { projectId: FIREBASE_PROJECT } as any, projectType: PROJECT_TYPE.CloudFunctions, project: PROJECT_NAME, prerender: false, - }) as Tree; + }); - const workspace = JSON.parse((result.read('angular.json')).toString()); - expect(workspace.projects['pie-ka-chu'].architect.deploy).toBeTruthy(); + const angularJSON = tree.read('angular.json')?.toString(); + const workspace = angularJSON && JSON.parse(angularJSON); + expect(workspace?.projects['pie-ka-chu']?.architect?.deploy).toBeTruthy(); }); - it('should configure firebase.json', async () => { + it('should configure firebase.json', () => { const tree = Tree.empty(); tree.create('angular.json', JSON.stringify(generateAngularJsonWithServer())); tree.create('package.json', JSON.stringify(generatePackageJson())); // TODO mock addTask - const result = await setupProject(tree, {addTask: () => undefined} as any, [FEATURES.Hosting], { + setupProject(tree, {addTask: () => undefined} as any, [FEATURES.Hosting], { firebaseProject: { projectId: FIREBASE_PROJECT } as any, projectType: PROJECT_TYPE.CloudFunctions, project: PROJECT_NAME, prerender: false, - }) as Tree; + }); - const firebaseJson = JSON.parse((result.read('firebase.json')).toString()); + const firebaseJsonData= tree.read('firebase.json')?.toString(); + const firebaseJson = firebaseJsonData && JSON.parse(firebaseJsonData); expect(firebaseJson).toEqual(universalFirebaseJson); }); }); diff --git a/src/schematics/setup/index.ts b/src/schematics/setup/index.ts index a2c6fd6a9..3ec346dd1 100644 --- a/src/schematics/setup/index.ts +++ b/src/schematics/setup/index.ts @@ -1,7 +1,8 @@ import { writeFileSync } from 'fs'; import { join } from 'path'; import { asWindowsPath, normalize } from '@angular-devkit/core'; -import { SchematicContext, SchematicsException, Tree } from '@angular-devkit/schematics'; +import { SchematicContext, SchematicsException, Tree, chain } from '@angular-devkit/schematics'; +import { addRootImport } from '@schematics/angular/utility'; import { generateFirebaseRc, overwriteIfExists, @@ -15,7 +16,8 @@ import { } from '../interfaces'; import { FirebaseJSON, Workspace, WorkspaceProject } from '../interfaces'; import { - addEnvironmentEntry, addFixesToServer, addIgnoreFiles, addToNgModule, + addIgnoreFiles, + featureToRules, getFirebaseProjectNameFromHost, getProject, getWorkspace } from '../utils'; import { appPrompt, featuresPrompt, projectPrompt, projectTypePrompt, sitePrompt, userPrompt } from './prompts'; @@ -29,76 +31,51 @@ export interface SetupConfig extends DeployOptions { browserTarget?: string, serverTarget?: string, prerenderTarget?: string, - project: string, + project?: string, ssrRegion?: string, projectType?: PROJECT_TYPE, prerender?: boolean, } export const setupProject = - async (tree: Tree, context: SchematicContext, features: FEATURES[], config: SetupConfig) => { - const { path: workspacePath, workspace } = getWorkspace(tree); - - const { project, projectName } = getProject(config, tree); - - const sourcePath = project.sourceRoot ?? project.root; + (tree: Tree, context: SchematicContext, features: FEATURES[], config: SetupConfig) => { + const { projectName } = getProject(config, tree); addIgnoreFiles(tree); - const featuresToImport = features.filter(it => it !== FEATURES.Hosting); - if (featuresToImport.length > 0) { - addToNgModule(tree, { features: featuresToImport, sourcePath }); - addFixesToServer(tree); - } - - 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: undefined, - browserTarget: config.browserTarget, - serverTarget: config.serverTarget, - prerenderTarget: config.prerenderTarget, - ssrRegion: config.ssrRegion, - }; - if (features.includes(FEATURES.Hosting)) { - return setupFirebase({ + const { path: workspacePath, workspace } = getWorkspace(tree); + const { project, projectName } = getProject(config, tree); + setupFirebase({ workspace, workspacePath, - options, + options: { + project: projectName, + firebaseProject: config.firebaseProject, + firebaseApp: config.firebaseApp, + firebaseHostingSite: config.firebaseHostingSite, + sdkConfig: config.sdkConfig, + prerender: undefined, + browserTarget: config.browserTarget, + serverTarget: config.serverTarget, + prerenderTarget: config.prerenderTarget, + ssrRegion: config.ssrRegion, + }, tree, context, project }); - } else { - return Promise.resolve(); + } + + const featuresToImport = features.filter(it => it !== FEATURES.Hosting); + if (featuresToImport.length > 0) { + return chain([ + addRootImport(projectName, ({code, external}) => { + external('initializeApp', '@angular/fire/app'); + return code`${external('provideFirebaseApp', '@angular/fire/app')}(() => initializeApp(${JSON.stringify(config.sdkConfig)}))`; + }), + ...featureToRules(features, projectName), + ]); } }; @@ -151,7 +128,7 @@ export const ngAddSetupProject = ( } - await setupProject(host, context, features, { + return setupProject(host, context, features, { ...options, ...hosting, firebaseProject, firebaseApp, firebaseHostingSite, sdkConfig, }); diff --git a/src/schematics/utils.ts b/src/schematics/utils.ts index 1388330f4..4a0b1e9e4 100644 --- a/src/schematics/utils.ts +++ b/src/schematics/utils.ts @@ -1,10 +1,10 @@ import { readFileSync } from 'fs'; import { join } from 'path'; -import { SchematicsException, Tree } from '@angular-devkit/schematics'; +import { Rule, SchematicsException, Tree, chain } from '@angular-devkit/schematics'; import ts from '@schematics/angular/third_party/github.com/Microsoft/TypeScript/lib/typescript'; -import { addImportToModule, addProviderToModule, findNode, insertImport } from '@schematics/angular/utility/ast-utils'; -import { Change, InsertChange, ReplaceChange, applyToUpdateRecorder } from '@schematics/angular/utility/change'; -import { buildRelativePath } from '@schematics/angular/utility/find-module'; +import { addRootImport, addRootProvider } from '@schematics/angular/utility'; +import { findNode } from '@schematics/angular/utility/ast-utils'; +import { InsertChange, ReplaceChange, applyToUpdateRecorder } from '@schematics/angular/utility/change'; import { overwriteIfExists } from './common'; import { DeployOptions, FEATURES, FirebaseApp, FirebaseRc, Workspace, WorkspaceProject } from './interfaces'; @@ -177,140 +177,82 @@ ${addZonePatch ? 'import \'zone.js/dist/zone-patch-rxjs\';' : ''}`)); return host; } -export function addToNgModule(host: Tree, options: { sourcePath: string, features: FEATURES[]}) { - - const modulePath = `/${options.sourcePath}/app/app.module.ts`; - - 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 environmentsPath = buildRelativePath( - modulePath, - `/${options.sourcePath}/environments/environment` - ); - - const changes: Change[] = []; - - 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') - ) { - changes.push( - insertImport(source, modulePath, ['provideAnalytics', 'getAnalytics', 'ScreenTrackingService', 'UserTrackingService'] as any, '@angular/fire/analytics'), - ...addImportToModule(source, modulePath, `provideAnalytics(() => getAnalytics())`, null as any), - ...addProviderToModule(source, modulePath, ['ScreenTrackingService', 'UserTrackingService'] as any, 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 function featureToRules(features: FEATURES[], projectName: string) { + return features.map(feature => { + switch (feature) { + case FEATURES.AppCheck: + // TODO make this smarter in Angular Universal + return addRootImport(projectName, ({code, external}) => { + external('initializeAppCheck', '@angular/fire/app-check'); + external('ReCaptchaEnterpriseProvider', '@angular/fire/app-check'); + return code`${external('provideAppCheck', '@angular/fire/app-check')}(() => { + // TODO get a reCAPTCHA Enterprise here https://console.cloud.google.com/security/recaptcha?project=_ + const provider = new ReCaptchaEnterpriseProvider(/* reCAPTCHA Enterprise site key */); + return initializeAppCheck(undefined, { provider, isTokenAutoRefreshEnabled: true }); +})`; + }); + case FEATURES.Analytics: + return chain([ + addRootImport(projectName, ({code, external}) => { + external('getAnalytics', '@angular/fire/analytics'); + return code`${external('provideAnalytics', '@angular/fire/analytics')}(() => getAnalytics())`; + }), + // TODO if using Angular router + addRootProvider(projectName, ({code, external}) => { + return code`${external('ScreenTrackingService', '@angular/fire/analytics')}`; + }), + ...(features.includes(FEATURES.Authentication) ? [ + addRootProvider(projectName, ({code, external}) => { + return code`${external('UserTrackingService', '@angular/fire/analytics')}`; + }) + ] : []), + ]) + case FEATURES.Authentication: + return addRootImport(projectName, ({code, external}) => { + external('getAuth', '@angular/fire/auth'); + return code`${external('provideAuth', '@angular/fire/auth')}(() => getAuth())`; + }); + case FEATURES.Database: + return addRootImport(projectName, ({code, external}) => { + external('getDatabase', '@angular/fire/database'); + return code`${external('provideDatabase', '@angular/fire/database')}(() => getDatabase())`; + }); + case FEATURES.Firestore: + return addRootImport(projectName, ({code, external}) => { + external('getFirestore', '@angular/fire/firestore'); + return code`${external('provideFirestore', '@angular/fire/firestore')}(() => getFirestore())`; + }); + case FEATURES.Functions: + return addRootImport(projectName, ({code, external}) => { + external('getFunctions', '@angular/fire/functions'); + return code`${external('provideFunctions', '@angular/fire/functions')}(() => getFunctions())`; + }); + case FEATURES.Messaging: + // TODO add the service worker + return addRootImport(projectName, ({code, external}) => { + external('getMessaging', '@angular/fire/messaging'); + return code`${external('provideMessaging', '@angular/fire/messaging')}(() => getMessaging())`; + }); + case FEATURES.Performance: + return addRootImport(projectName, ({code, external}) => { + external('getPerformance', '@angular/fire/performance'); + return code`${external('providePerformance', '@angular/fire/performance')}(() => getPerformance())`; + }); + case FEATURES.Storage: + return addRootImport(projectName, ({code, external}) => { + external('getStorage', '@angular/fire/storage'); + return code`${external('provideStorage', '@angular/fire/storage')}(() => getStorage())`; + }); + case FEATURES.RemoteConfig: + // TODO consider downloading the defaults + return addRootImport(projectName, ({code, external}) => { + external('getRemoteConfig', '@angular/fire/remote-config'); + return code`${external('provideRemoteConfig', '@angular/fire/remote-config')}(() => getRemoteConfig())`; + }); + default: + return undefined; + } + }).filter((it): it is Rule => !!it); } export const addIgnoreFiles = (host: Tree) => {