diff --git a/package-lock.json b/package-lock.json index eb7345108..0fe303560 100644 --- a/package-lock.json +++ b/package-lock.json @@ -80,7 +80,8 @@ "ts-patch": "^3.2.1", "ts-transformer-keys": "^0.4.4", "tslint": "~6.1.0", - "typescript": ">=5.5 <5.7" + "typescript": ">=5.5 <5.7", + "yaml": "^2.7.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -10112,6 +10113,14 @@ "node": ">=10" } }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -22521,11 +22530,15 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/yargs": { diff --git a/package.json b/package.json index c1b65f784..a3752fe74 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "test:chrome-headless": "npx --yes firebase-tools@latest emulators:exec --project=demo-123 \"ng test --watch=false --browsers=ChromeHeadless\"", "test:firefox-headless": "npx --yes firebase-tools@latest emulators:exec --project=demo-123 \"ng test --watch=false --browsers=FirefoxHeadless\"", "lint": "ng lint", + "lint:fix": "ng lint --fix", "test:node": "node -r tsconfig-paths/register ./dist/out-tsc/jasmine/tools/jasmine.mjs --input-type=commonjs", "test:node-esm": "node -r tsconfig-paths/register ./dist/out-tsc/jasmine/tools/jasmine.mjs", "test:typings": "node ./tools/run-typings-test.js", @@ -116,7 +117,8 @@ "ts-patch": "^3.2.1", "ts-transformer-keys": "^0.4.4", "tslint": "~6.1.0", - "typescript": ">=5.5 <5.7" + "typescript": ">=5.5 <5.7", + "yaml": "^2.7.0" }, "typings": "index.d.ts" } diff --git a/src/schematics/deploy/actions.jasmine.ts b/src/schematics/deploy/actions.jasmine.ts index 890e76350..abae2e375 100644 --- a/src/schematics/deploy/actions.jasmine.ts +++ b/src/schematics/deploy/actions.jasmine.ts @@ -63,6 +63,9 @@ const initMocks = () => { create: () => Promise.reject(), } }, + init() { + return Promise.resolve() + }, deploy: (_: FirebaseDeployConfig) => Promise.resolve(), use: () => Promise.resolve(), logger: { diff --git a/src/schematics/interfaces.ts b/src/schematics/interfaces.ts index 916ac7725..a21df2168 100644 --- a/src/schematics/interfaces.ts +++ b/src/schematics/interfaces.ts @@ -21,7 +21,7 @@ export const featureOptions = [ { name: 'App Check', value: FEATURES.AppCheck }, { name: 'Firestore', value: FEATURES.Firestore }, { name: 'Realtime Database', value: FEATURES.Database }, - // { name: 'Data Connect', value: FEATURES.DataConnect }, + { name: 'Data Connect', value: FEATURES.DataConnect }, { name: 'Cloud Functions (callable)', value: FEATURES.Functions }, { name: 'Cloud Messaging', value: FEATURES.Messaging }, { name: 'Performance Monitoring', value: FEATURES.Performance }, @@ -132,6 +132,8 @@ export interface FirebaseTools { serve(options: any): Promise; use(options: any, lol: any): Promise; + + init(feature: string, options: any): Promise; } export interface FirebaseHostingRewrite { @@ -149,9 +151,14 @@ export interface FirebaseHostingConfig { export type FirebaseFunctionsConfig = Record; +export interface DataConnectConfig { + source?: string; +} + export interface FirebaseJSON { hosting?: FirebaseHostingConfig[] | FirebaseHostingConfig; functions?: FirebaseFunctionsConfig; + dataconnect?: DataConnectConfig; } export interface FirebaseRcTarget { @@ -225,3 +232,36 @@ export interface Workspace { defaultProject?: string; projects: Record; } + +export interface ConnectorConfig { + location: string; + connector: string; + service: string; +} +export interface ConnectorYaml { + connectorId: string; + generate?: { + javascriptSdk?: { + package: string; + outputDir: string; + packageJsonDir?: string; + angular?: boolean; + } + } +} +export interface DataConnectYaml { + location: string; + serviceId: string; + connectorDirs: string[]; +} +export interface DataConnectConnectorConfig { + connectorYaml: ConnectorYaml; + connectorConfig?: ConnectorConfig; + angular?: boolean; + package?: string; +} + +export interface PackageJson { + dependencies: Record; + devDependencies: Record; +} diff --git a/src/schematics/setup/index.ts b/src/schematics/setup/index.ts index fb26e8250..06e29f789 100644 --- a/src/schematics/setup/index.ts +++ b/src/schematics/setup/index.ts @@ -1,17 +1,20 @@ -import { writeFileSync } from 'fs'; +import { readFileSync, writeFileSync } from 'fs'; import { join } from 'path'; import { asWindowsPath, normalize } from '@angular-devkit/core'; import { SchematicContext, Tree, chain } from '@angular-devkit/schematics'; import { addRootProvider } from '@schematics/angular/utility'; import { getFirebaseTools } from '../firebaseTools'; import { - DeployOptions, FEATURES, FirebaseApp, FirebaseProject, + DataConnectConnectorConfig, + DeployOptions, FEATURES, FirebaseApp, FirebaseJSON, FirebaseProject, } from '../interfaces'; import { addIgnoreFiles, featureToRules, getFirebaseProjectNameFromHost, getProject, + parseDataConnectConfig, + setupTanstackDependencies, } from '../utils'; import { appPrompt, featuresPrompt, projectPrompt, userPrompt } from './prompts'; @@ -19,6 +22,9 @@ export interface SetupConfig extends DeployOptions { firebaseProject: FirebaseProject, firebaseApp?: FirebaseApp, sdkConfig?: Record, + firebaseJsonConfig?: FirebaseJSON; + dataConnectConfig?: DataConnectConnectorConfig | null; + firebaseJsonPath: string; } export const setupProject = @@ -35,7 +41,7 @@ export const setupProject = config.sdkConfig ? `{ ${Object.entries(config.sdkConfig).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ")} }` : "" }))`; }), - ...featureToRules(features, projectName), + ...featureToRules(features, projectName, config.dataConnectConfig), ]); } }; @@ -56,6 +62,10 @@ export const ngAddSetupProject = ( // Add the firebase files if they don't exist already so login.use works if (!host.exists('/firebase.json')) { writeFileSync(join(projectRoot, 'firebase.json'), '{}'); } + + let firebaseJson: FirebaseJSON = JSON.parse( + readFileSync(join(projectRoot, "firebase.json")).toString() + ); const user = await userPrompt({ projectRoot }); const defaultUser = await firebaseTools.login(options); @@ -72,6 +82,11 @@ export const ngAddSetupProject = ( let firebaseApp: FirebaseApp|undefined; let sdkConfig: Record|undefined; + const setupConfig: SetupConfig = { + ...options, firebaseProject, firebaseApp, sdkConfig, + firebaseJsonConfig: firebaseJson, + firebaseJsonPath: projectRoot + }; if (features.length) { firebaseApp = await appPrompt(firebaseProject, undefined, { projectRoot }); @@ -79,12 +94,45 @@ export const ngAddSetupProject = ( const result = await firebaseTools.apps.sdkconfig('web', firebaseApp.appId, { nonInteractive: true, projectRoot }); sdkConfig = result.sdkConfig; delete sdkConfig.locationId; - + setupConfig.sdkConfig = sdkConfig; + setupConfig.firebaseApp = firebaseApp; + // set up data connect locally if data connect hasn't already been initialized. + if(features.includes(FEATURES.DataConnect)) { + if (!firebaseJson.dataconnect) { + try { + await firebaseTools.init("dataconnect", { + projectRoot, + project: firebaseProject.projectId, + }); + // Update firebaseJson values to include newly added dataconnect field in firebase.json. + firebaseJson = JSON.parse( + readFileSync(join(projectRoot, "firebase.json")).toString() + ); + setupConfig.firebaseJsonConfig = firebaseJson; + } catch (e) { + console.error(e); + } + } + let dataConnectConfig = parseDataConnectConfig(setupConfig); + if(!dataConnectConfig?.connectorYaml.generate?.javascriptSdk) { + await firebaseTools.init("dataconnect:sdk", { + projectRoot, + project: firebaseProject.projectId, + }); + } + // Parse through sdk again + dataConnectConfig = parseDataConnectConfig(setupConfig); + if(dataConnectConfig?.angular) { + context.logger.info('Generated Angular SDK Enabled.'); + } else { + context.logger.info('Generated Angular SDK Disabled. Please add `angular: true` to your connector.yaml'); + } + setupTanstackDependencies(host, context); + setupConfig.dataConnectConfig = dataConnectConfig; + } + } - return setupProject(host, context, features, { - ...options, firebaseProject, firebaseApp, sdkConfig, - }); - + return setupProject(host, context, features, setupConfig); } }; diff --git a/src/schematics/utils.ts b/src/schematics/utils.ts index 73fea996b..b42afb07c 100644 --- a/src/schematics/utils.ts +++ b/src/schematics/utils.ts @@ -1,16 +1,37 @@ -import { readFileSync } from 'fs'; -import { join } from 'path'; -import { Rule, SchematicsException, Tree, chain } from '@angular-devkit/schematics'; -import { addRootProvider } from '@schematics/angular/utility'; -import { overwriteIfExists } from './common'; -import { DeployOptions, FEATURES, FirebaseApp, FirebaseRc, Workspace } from './interfaces'; +import { readFileSync } from "fs"; +import { join } from "path"; +import { + Rule, + SchematicContext, + SchematicsException, + Tree, + chain, +} from "@angular-devkit/schematics"; +import { NodePackageInstallTask } from "@angular-devkit/schematics/tasks"; +import { addRootProvider } from "@schematics/angular/utility"; +import { parse } from "yaml"; +import { overwriteIfExists, safeReadJSON, stringifyFormatted } from "./common"; +import { + ConnectorConfig, + ConnectorYaml, + DataConnectConnectorConfig, + DataConnectYaml, + DeployOptions, + FEATURES, + FirebaseApp, + FirebaseRc, + PackageJson, + Workspace, +} from "./interfaces"; +import { SetupConfig } from "./setup"; -export const shortAppId = (app?: FirebaseApp) => app?.appId?.split('/').pop(); +export const shortAppId = (app?: FirebaseApp) => app?.appId?.split("/").pop(); -export function getWorkspace( - host: Tree -): { path: string; workspace: Workspace } { - const path = '/angular.json'; +export function getWorkspace(host: Tree): { + path: string; + workspace: Workspace; +} { + const path = "/angular.json"; const configBuffer = path && host.read(path); if (!configBuffer) { @@ -18,16 +39,16 @@ export function getWorkspace( } // eslint-disable-next-line @typescript-eslint/no-var-requires - const { parse } = require('jsonc-parser'); + const { parse } = require("jsonc-parser"); - const workspace = parse(configBuffer.toString()) as Workspace|undefined; + const workspace = parse(configBuffer.toString()) as Workspace | undefined; if (!workspace) { - throw new SchematicsException('Could not parse angular.json'); + throw new SchematicsException("Could not parse angular.json"); } return { path, - workspace + workspace, }; } @@ -37,31 +58,31 @@ export const getProject = (options: DeployOptions, host: Tree) => { if (!projectName) { throw new SchematicsException( - 'No Angular project selected and no default project in the workspace' + "No Angular project selected and no default project in the workspace" ); } const project = workspace.projects[projectName]; if (!project) { throw new SchematicsException( - 'The specified Angular project is not defined in this workspace' + "The specified Angular project is not defined in this workspace" ); } - if (project.projectType !== 'application') { + if (project.projectType !== "application") { throw new SchematicsException( `Deploy requires an Angular project type of "application" in angular.json` ); } - return {project, projectName}; + return { project, projectName }; }; export function getFirebaseProjectNameFromHost( host: Tree, target: string -): [string|undefined, string|undefined] { - const buffer = host.read('/.firebaserc'); +): [string | undefined, string | undefined] { + const buffer = host.read("/.firebaserc"); if (!buffer) { return [undefined, undefined]; } @@ -72,8 +93,8 @@ export function getFirebaseProjectNameFromHost( export function getFirebaseProjectNameFromFs( root: string, target: string -): [string|undefined, string|undefined] { - const path = join(root, '.firebaserc'); +): [string | undefined, string | undefined] { + const path = join(root, ".firebaserc"); try { const buffer = readFileSync(path); const rc: FirebaseRc = JSON.parse(buffer.toString()); @@ -83,10 +104,13 @@ export function getFirebaseProjectNameFromFs( } } -const projectFromRc = (rc: FirebaseRc, target: string): [string|undefined, string|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] + (project) => !!rc.targets?.[project]?.hosting?.[target] ); const site = project && rc.targets?.[project]?.hosting?.[target]?.[0]; return [project || defaultProject, site]; @@ -104,26 +128,43 @@ export function addFixesToServer(host: Tree) { if (text === null) { throw new SchematicsException(`File ${serverPath} does not exist.`); } - const sourceText = text.toString('utf-8'); - const addZonePatch = !sourceText.includes('import \'zone.js/dist/zone-patch-rxjs\';'); + const sourceText = text.toString("utf-8"); + const addZonePatch = !sourceText.includes( + "import 'zone.js/dist/zone-patch-rxjs';" + ); if (addZonePatch) { - overwriteIfExists(host, serverPath, sourceText.replace('import \'zone.js/dist/zone-node\';', `import 'zone.js/dist/zone-node'; -${addZonePatch ? 'import \'zone.js/dist/zone-patch-rxjs\';' : ''}`)); + overwriteIfExists( + host, + serverPath, + sourceText.replace( + "import 'zone.js/dist/zone-node';", + `import 'zone.js/dist/zone-node'; +${addZonePatch ? "import 'zone.js/dist/zone-patch-rxjs';" : ""}` + ) + ); } return host; } -export function featureToRules(features: FEATURES[], projectName: string) { - return features.map(feature => { - switch (feature) { +export function featureToRules( + features: FEATURES[], + projectName: string, + dataConnectConfig?: DataConnectConnectorConfig | null +) { + return features + .map((feature) => { + switch (feature) { case FEATURES.AppCheck: // TODO make this smarter in Angular Universal - return addRootProvider(projectName, ({code, external}) => { - external('initializeAppCheck', '@angular/fire/app-check'); - external('ReCaptchaEnterpriseProvider', '@angular/fire/app-check'); - return code`${external('provideAppCheck', '@angular/fire/app-check')}(() => { + return addRootProvider(projectName, ({ code, external }) => { + external("initializeAppCheck", "@angular/fire/app-check"); + external("ReCaptchaEnterpriseProvider", "@angular/fire/app-check"); + return code`${external( + "provideAppCheck", + "@angular/fire/app-check" + )}(() => { // TODO get a reCAPTCHA Enterprise here https://console.cloud.google.com/security/recaptcha?project=_ const provider = new ReCaptchaEnterpriseProvider(/* reCAPTCHA Enterprise site key */); return initializeAppCheck(undefined, { provider, isTokenAutoRefreshEnabled: true }); @@ -131,83 +172,144 @@ export function featureToRules(features: FEATURES[], projectName: string) { }); case FEATURES.Analytics: return chain([ - addRootProvider(projectName, ({code, external}) => { - external('getAnalytics', '@angular/fire/analytics'); - return code`${external('provideAnalytics', '@angular/fire/analytics')}(() => getAnalytics())`; + addRootProvider(projectName, ({ code, external }) => { + external("getAnalytics", "@angular/fire/analytics"); + return code`${external( + "provideAnalytics", + "@angular/fire/analytics" + )}(() => getAnalytics())`; }), // TODO if using Angular router - addRootProvider(projectName, ({code, external}) => { - return code`${external('ScreenTrackingService', '@angular/fire/analytics')}`; + 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')}`; - }) - ] : []), - ]) + ...(features.includes(FEATURES.Authentication) + ? [ + addRootProvider(projectName, ({ code, external }) => { + return code`${external( + "UserTrackingService", + "@angular/fire/analytics" + )}`; + }), + ] + : []), + ]); case FEATURES.Authentication: - return addRootProvider(projectName, ({code, external}) => { - external('getAuth', '@angular/fire/auth'); - return code`${external('provideAuth', '@angular/fire/auth')}(() => getAuth())`; + return addRootProvider(projectName, ({ code, external }) => { + external("getAuth", "@angular/fire/auth"); + return code`${external( + "provideAuth", + "@angular/fire/auth" + )}(() => getAuth())`; }); case FEATURES.Database: - return addRootProvider(projectName, ({code, external}) => { - external('getDatabase', '@angular/fire/database'); - return code`${external('provideDatabase', '@angular/fire/database')}(() => getDatabase())`; + return addRootProvider(projectName, ({ code, external }) => { + external("getDatabase", "@angular/fire/database"); + return code`${external( + "provideDatabase", + "@angular/fire/database" + )}(() => getDatabase())`; }); case FEATURES.DataConnect: - throw "unimplemented."; - // TODO need to add the generation of the project, grab the connector config - // that requires this go async - // return addRootProvider(projectName, ({code, external}) => { - // external('getDataConnect', '@angular/fire/data-connect'); - // return code`${external('provideDataConnect', '@angular/fire/data-connect')}(() => getDataConnect())`; - // }); + return addRootProvider(projectName, ({ code, external }) => { + external("getDataConnect", "@angular/fire/data-connect"); + let configAsStr = "{}"; + + const config = dataConnectConfig; + let angularConfig: undefined | string; + if (config) { + if (config.package) { + configAsStr = external("connectorConfig", config.package); + } else { + configAsStr = `{${Object.keys(config.connectorConfig as ConnectorConfig).map( + (key) => `${key}: "${(config.connectorConfig as ConnectorConfig)[key]}"` + ).join(',')}}`; + } + if (config.angular) { + angularConfig = `, ${external( + "provideTanStackQuery", + "@tanstack/angular-query-experimental" + )}(new ${external( + "QueryClient", + "@tanstack/angular-query-experimental" + )}())`; + } + } + return code`${external( + "provideDataConnect", + "@angular/fire/data-connect" + )}(() => getDataConnect(${configAsStr}))${angularConfig ?? ""}`; + }); case FEATURES.Firestore: - return addRootProvider(projectName, ({code, external}) => { - external('getFirestore', '@angular/fire/firestore'); - return code`${external('provideFirestore', '@angular/fire/firestore')}(() => getFirestore())`; + return addRootProvider(projectName, ({ code, external }) => { + external("getFirestore", "@angular/fire/firestore"); + return code`${external( + "provideFirestore", + "@angular/fire/firestore" + )}(() => getFirestore())`; }); case FEATURES.Functions: - return addRootProvider(projectName, ({code, external}) => { - external('getFunctions', '@angular/fire/functions'); - return code`${external('provideFunctions', '@angular/fire/functions')}(() => getFunctions())`; + return addRootProvider(projectName, ({ code, external }) => { + external("getFunctions", "@angular/fire/functions"); + return code`${external( + "provideFunctions", + "@angular/fire/functions" + )}(() => getFunctions())`; }); case FEATURES.Messaging: // TODO add the service worker - return addRootProvider(projectName, ({code, external}) => { - external('getMessaging', '@angular/fire/messaging'); - return code`${external('provideMessaging', '@angular/fire/messaging')}(() => getMessaging())`; + return addRootProvider(projectName, ({ code, external }) => { + external("getMessaging", "@angular/fire/messaging"); + return code`${external( + "provideMessaging", + "@angular/fire/messaging" + )}(() => getMessaging())`; }); case FEATURES.Performance: - return addRootProvider(projectName, ({code, external}) => { - external('getPerformance', '@angular/fire/performance'); - return code`${external('providePerformance', '@angular/fire/performance')}(() => getPerformance())`; + return addRootProvider(projectName, ({ code, external }) => { + external("getPerformance", "@angular/fire/performance"); + return code`${external( + "providePerformance", + "@angular/fire/performance" + )}(() => getPerformance())`; }); case FEATURES.Storage: - return addRootProvider(projectName, ({code, external}) => { - external('getStorage', '@angular/fire/storage'); - return code`${external('provideStorage', '@angular/fire/storage')}(() => getStorage())`; + return addRootProvider(projectName, ({ code, external }) => { + external("getStorage", "@angular/fire/storage"); + return code`${external( + "provideStorage", + "@angular/fire/storage" + )}(() => getStorage())`; }); case FEATURES.RemoteConfig: // TODO consider downloading the defaults - return addRootProvider(projectName, ({code, external}) => { - external('getRemoteConfig', '@angular/fire/remote-config'); - return code`${external('provideRemoteConfig', '@angular/fire/remote-config')}(() => getRemoteConfig())`; + return addRootProvider(projectName, ({ code, external }) => { + external("getRemoteConfig", "@angular/fire/remote-config"); + return code`${external( + "provideRemoteConfig", + "@angular/fire/remote-config" + )}(() => getRemoteConfig())`; }); case FEATURES.VertexAI: - return addRootProvider(projectName, ({code, external}) => { - external('getVertexAI', '@angular/fire/vertexai'); - return code`${external('provideVertexAI', '@angular/fire/vertexai')}(() => getVertexAI())`; + return addRootProvider(projectName, ({ code, external }) => { + external("getVertexAI", "@angular/fire/vertexai"); + return code`${external( + "provideVertexAI", + "@angular/fire/vertexai" + )}(() => getVertexAI())`; }); default: return undefined; } - }).filter((it): it is Rule => !!it); + }) + .filter((it): it is Rule => !!it); } export const addIgnoreFiles = (host: Tree) => { - const path = '/.gitignore'; + const path = "/.gitignore"; if (!host.exists(path)) { return host; } @@ -218,14 +320,81 @@ export const addIgnoreFiles = (host: Tree) => { } const content = buffer.toString(); - if (!content.includes('# Firebase')) { - overwriteIfExists(host, path, content.concat(` + if (!content.includes("# Firebase")) { + overwriteIfExists( + host, + path, + content.concat(` # Firebase .firebase *-debug.log .runtimeconfig.json -`)); +`) + ); } return host; }; + +export function parseDataConnectConfig( + config: SetupConfig +): DataConnectConnectorConfig | null { + if (!config.firebaseJsonConfig) { + throw new Error("No firebase json"); + } + if (!config.firebaseJsonConfig.dataconnect?.source) { + throw new Error( + "Couldn't find data connect configuration. Running `firebase init dataconnect`" + ); + } + const dataConnectFolder = join( + config.firebaseJsonPath, + config.firebaseJsonConfig.dataconnect?.source + ); + const sourcePath = join(dataConnectFolder, "dataconnect.yaml"); + try { + const fileAsStr = readFileSync(sourcePath).toString(); + const dataConnectYaml: DataConnectYaml = parse(fileAsStr); + const connectorPath = join( + dataConnectFolder, + dataConnectYaml.connectorDirs[0], + "connector.yaml" + ); + const connectorAsStr = readFileSync(connectorPath).toString(); + const connectorJson: ConnectorYaml = parse(connectorAsStr); + if (!connectorJson?.generate?.javascriptSdk) { + return { connectorYaml: connectorJson }; + } + return { + connectorYaml: connectorJson, + connectorConfig: { + connector: connectorJson.connectorId, + location: dataConnectYaml.location, + service: dataConnectYaml.serviceId, + }, + package: connectorJson.generate.javascriptSdk.package, + angular: connectorJson.generate.javascriptSdk.angular, + }; + } catch (e) { + console.error("Couldn't parse dataconnect.yaml", e); + return null; + } +} + +export function setupTanstackDependencies( + host: Tree, + context: SchematicContext +) { + const packageJson: PackageJson = safeReadJSON("package.json", host); + const tanstackFirebasePackage = "@tanstack-query-firebase/angular"; + if ( + !packageJson.dependencies[tanstackFirebasePackage] && + !packageJson.devDependencies[tanstackFirebasePackage] + ) { + packageJson.dependencies[tanstackFirebasePackage] = + "^1.0.0"; + packageJson.dependencies["@tanstack/angular-query-experimental"] = "5.66.4"; + overwriteIfExists(host, "package.json", stringifyFormatted(packageJson)); + context.addTask(new NodePackageInstallTask()); + } +}