diff --git a/schema/connector-yaml.json b/schema/connector-yaml.json index 729a747d128..20bbfc76c78 100644 --- a/schema/connector-yaml.json +++ b/schema/connector-yaml.json @@ -2,6 +2,24 @@ "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "definitions": { + "adminNodeSdk": { + "additionalProperties": true, + "type": "object", + "properties": { + "outputDir": { + "type": "string", + "description": "Path to the directory where generated files should be written to." + }, + "package": { + "type": "string", + "description": "The package name to use for the generated code." + }, + "packageJSONDir": { + "type": "string", + "description": "The directory containing the package.json to install the generated package in." + } + } + }, "javascriptSdk": { "additionalProperties": true, "type": "object", @@ -90,6 +108,18 @@ ], "description": "Configuration for a generated Javascript SDK" }, + "adminNodeSdk": { + "oneOf": [ + { "$ref": "#/definitions/adminNodeSdk" }, + { + "type": "array", + "items": { + "$ref": "#/definitions/adminNodeSdk" + } + } + ], + "description": "Configuration for a generated Admin Node SDK" + }, "dartSdk": { "oneOf": [ { "$ref": "#/definitions/dartSdk" }, diff --git a/src/appUtils.spec.ts b/src/appUtils.spec.ts index 3289df025df..78ac9276d90 100644 --- a/src/appUtils.spec.ts +++ b/src/appUtils.spec.ts @@ -620,6 +620,69 @@ describe("appUtils", () => { ]); }); + it("should detect an admin app with firebase-admin dependency", async () => { + mockfs({ + [testDir]: { + "package.json": JSON.stringify({ + dependencies: { + "firebase-admin": "1.0.0", + }, + }), + }, + }); + const apps = cleanUndefinedFields(await detectApps(testDir)); + expect(apps).to.have.deep.members([ + { + platform: Platform.ADMIN_NODE, + directory: ".", + }, + ]); + }); + + it("should detect an admin app with firebase-functions dependency", async () => { + mockfs({ + [testDir]: { + "package.json": JSON.stringify({ + dependencies: { + "firebase-functions": "1.0.0", + }, + }), + }, + }); + const apps = cleanUndefinedFields(await detectApps(testDir)); + expect(apps).to.have.deep.members([ + { + platform: Platform.ADMIN_NODE, + directory: ".", + }, + ]); + }); + + it("should detect an admin and client app", async () => { + mockfs({ + [testDir]: { + "package.json": JSON.stringify({ + dependencies: { + "firebase-admin": "1.0.0", + firebase: "1.0.0", + }, + }), + }, + }); + const apps = cleanUndefinedFields(await detectApps(testDir)); + expect(apps).to.have.deep.members([ + { + platform: Platform.ADMIN_NODE, + directory: ".", + }, + { + platform: Platform.WEB, + directory: ".", + frameworks: [], + }, + ]); + }); + it("should detect angular web framework", async () => { mockfs({ [testDir]: { diff --git a/src/appUtils.ts b/src/appUtils.ts index 35b72ac7e69..31dc79f4d54 100644 --- a/src/appUtils.ts +++ b/src/appUtils.ts @@ -11,6 +11,7 @@ export enum Platform { WEB = "WEB", IOS = "IOS", FLUTTER = "FLUTTER", + ADMIN_NODE = "ADMIN_NODE", } /** @@ -62,7 +63,11 @@ export async function detectApps(dirPath: string): Promise { const pubSpecYamlFiles = await detectFiles(dirPath, "pubspec.yaml"); const srcMainFolders = await detectFiles(dirPath, "src/main/"); const xCodeProjects = await detectFiles(dirPath, "*.xcodeproj/"); - const webApps = await Promise.all(packageJsonFiles.map((p) => packageJsonToWebApp(dirPath, p))); + const adminAndWebApps = ( + await Promise.all(packageJsonFiles.map((p) => packageJsonToAdminOrWebApp(dirPath, p))) + ).flat(); + console.log("packageJsonFiles", packageJsonFiles); + console.log("adminAndWebApps", adminAndWebApps); const flutterAppPromises = await Promise.all( pubSpecYamlFiles.map((f) => processFlutterDir(dirPath, f)), @@ -80,7 +85,7 @@ export async function detectApps(dirPath: string): Promise { const iosApps = iosAppPromises .flat() .filter((a) => !flutterApps.some((f) => isPathInside(f.directory, a.directory))); - return [...webApps, ...flutterApps, ...androidApps, ...iosApps]; + return [...flutterApps, ...androidApps, ...iosApps, ...adminAndWebApps]; } async function processIosDir(dirPath: string, filePath: string): Promise { @@ -164,14 +169,35 @@ function isPathInside(parent: string, child: string): boolean { return !relativePath.startsWith(`..`); } -async function packageJsonToWebApp(dirPath: string, packageJsonFile: string): Promise { +export function getAllDepsFromPackageJson(packageJson: PackageJSON) { + const devDependencies = Object.keys(packageJson.devDependencies ?? {}); + const dependencies = Object.keys(packageJson.dependencies ?? {}); + const allDeps = Array.from(new Set([...devDependencies, ...dependencies])); + return allDeps; +} + +async function packageJsonToAdminOrWebApp( + dirPath: string, + packageJsonFile: string, +): Promise { const fullPath = path.join(dirPath, packageJsonFile); const packageJson = JSON.parse((await fs.readFile(fullPath)).toString()) as PackageJSON; - return { - platform: Platform.WEB, - directory: path.dirname(packageJsonFile), - frameworks: getFrameworksFromPackageJson(packageJson), - }; + const allDeps = getAllDepsFromPackageJson(packageJson); + const detectedApps = []; + if (allDeps.includes("firebase-admin") || allDeps.includes("firebase-functions")) { + detectedApps.push({ + platform: Platform.ADMIN_NODE, + directory: path.dirname(packageJsonFile), + }); + } + if (allDeps.includes("firebase") || detectedApps.length === 0) { + detectedApps.push({ + platform: Platform.WEB, + directory: path.dirname(packageJsonFile), + frameworks: getFrameworksFromPackageJson(packageJson), + }); + } + return detectedApps; } const WEB_FRAMEWORKS: Framework[] = Object.values(Framework); @@ -215,9 +241,7 @@ async function detectAppIdsForPlatform( } function getFrameworksFromPackageJson(packageJson: PackageJSON): Framework[] { - const devDependencies = Object.keys(packageJson.devDependencies ?? {}); - const dependencies = Object.keys(packageJson.dependencies ?? {}); - const allDeps = Array.from(new Set([...devDependencies, ...dependencies])); + const allDeps = getAllDepsFromPackageJson(packageJson); return WEB_FRAMEWORKS.filter((framework) => WEB_FRAMEWORKS_SIGNALS[framework].find((dep) => allDeps.includes(dep)), ); diff --git a/src/commands/dataconnect-sdk-generate.ts b/src/commands/dataconnect-sdk-generate.ts index c9370de208c..db587df5176 100644 --- a/src/commands/dataconnect-sdk-generate.ts +++ b/src/commands/dataconnect-sdk-generate.ts @@ -99,7 +99,8 @@ async function loadAllWithSDKs( c.connectorYaml.generate?.javascriptSdk || c.connectorYaml.generate?.kotlinSdk || c.connectorYaml.generate?.swiftSdk || - c.connectorYaml.generate?.dartSdk + c.connectorYaml.generate?.dartSdk || + c.connectorYaml.generate?.adminNodeSdk ); }), ); diff --git a/src/dataconnect/types.ts b/src/dataconnect/types.ts index e990b2422d5..c9b7dfeaa2d 100644 --- a/src/dataconnect/types.ts +++ b/src/dataconnect/types.ts @@ -146,6 +146,7 @@ export interface Generate { swiftSdk?: SwiftSDK | SwiftSDK[]; kotlinSdk?: KotlinSDK | KotlinSDK[]; dartSdk?: DartSDK | DartSDK[]; + adminNodeSdk?: AdminNodeSDK | AdminNodeSDK[]; } export interface SupportedFrameworks { @@ -153,6 +154,12 @@ export interface SupportedFrameworks { angular?: boolean; } +export interface AdminNodeSDK { + outputDir: string; + package: string; + packageJsonDir?: string; +} + export interface JavascriptSDK extends SupportedFrameworks { outputDir: string; package: string; diff --git a/src/init/features/dataconnect/sdk.spec.ts b/src/init/features/dataconnect/sdk.spec.ts index 123d865f29a..7c5365fa5c1 100644 --- a/src/init/features/dataconnect/sdk.spec.ts +++ b/src/init/features/dataconnect/sdk.spec.ts @@ -108,6 +108,18 @@ describe("addSdkGenerateToConnectorYaml", () => { }, ]); }); + + it("should add adminSdk for admin node platform", () => { + app.platform = Platform.ADMIN_NODE; + addSdkGenerateToConnectorYaml(connectorInfo, connectorYaml, app); + expect(connectorYaml.generate?.adminNodeSdk).to.deep.equal([ + { + outputDir: "../app/src/dataconnect-admin-generated", + package: "@dataconnect/admin-generated", + packageJsonDir: "../app", + }, + ]); + }); }); describe("chooseApp", () => { diff --git a/src/init/features/dataconnect/sdk.ts b/src/init/features/dataconnect/sdk.ts index 9dc689843eb..873aaed0e2b 100644 --- a/src/init/features/dataconnect/sdk.ts +++ b/src/init/features/dataconnect/sdk.ts @@ -9,6 +9,7 @@ import { Config } from "../../../config"; import { Setup } from "../.."; import { loadAll } from "../../../dataconnect/load"; import { + AdminNodeSDK, ConnectorInfo, ConnectorYaml, DartSDK, @@ -193,10 +194,14 @@ export function initAppCounters(info: SdkRequiredInfo): { [key: string]: number num_android_apps: 0, num_ios_apps: 0, num_flutter_apps: 0, + num_admin_node_apps: 0, }; for (const app of info.apps ?? []) { switch (app.platform) { + case Platform.ADMIN_NODE: + counts.num_admin_node_apps++; + break; case Platform.WEB: counts.num_web_apps++; break; @@ -350,6 +355,24 @@ export function addSdkGenerateToConnectorYaml( const generate = connectorYaml.generate; switch (app.platform) { + case Platform.ADMIN_NODE: { + const adminNodeSdk: AdminNodeSDK = { + outputDir: path.relative( + connectorDir, + path.join(appDir, `src/dataconnect-admin-generated`), + ), + package: `@dataconnect/admin-generated`, + packageJsonDir: path.relative(connectorDir, appDir), + }; + if (!isArray(generate?.adminNodeSdk)) { + generate.adminNodeSdk = generate.adminNodeSdk ? [generate.adminNodeSdk] : []; + } + if (!generate.adminNodeSdk.some((s) => s.outputDir === adminNodeSdk.outputDir)) { + generate.adminNodeSdk.push(adminNodeSdk); + } + break; + } + case Platform.WEB: { const javascriptSdk: JavascriptSDK = { outputDir: path.relative(connectorDir, path.join(appDir, `src/dataconnect-generated`)),