Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions schema/connector-yaml.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 containining the package.json to install the generated package in."
}
}
},
"javascriptSdk": {
"additionalProperties": true,
"type": "object",
Expand Down Expand Up @@ -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" },
Expand Down
62 changes: 62 additions & 0 deletions src/appUtils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,68 @@ 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: ".",
},
]);
});

it("should detect angular web framework", async () => {
mockfs({
[testDir]: {
Expand Down
44 changes: 33 additions & 11 deletions src/appUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
WEB = "WEB",
IOS = "IOS",
FLUTTER = "FLUTTER",
ADMIN_NODE = "ADMIN_NODE",
}

/**
Expand Down Expand Up @@ -62,7 +63,9 @@
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();

const flutterAppPromises = await Promise.all(
pubSpecYamlFiles.map((f) => processFlutterDir(dirPath, f)),
Expand All @@ -80,7 +83,7 @@
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<App[]> {
Expand Down Expand Up @@ -164,14 +167,35 @@
return !relativePath.startsWith(`..`);
}

async function packageJsonToWebApp(dirPath: string, packageJsonFile: string): Promise<App> {
export function getAllDepsFromPackageJson(packageJson: PackageJSON) {

Check warning on line 170 in src/appUtils.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing JSDoc comment

Check warning on line 170 in src/appUtils.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
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<App[]> {
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") || detectApps.length === 0) {
detectedApps.push({
platform: Platform.WEB,
directory: path.dirname(packageJsonFile),
frameworks: getFrameworksFromPackageJson(packageJson),
});
}
return detectedApps;
}

const WEB_FRAMEWORKS: Framework[] = Object.values(Framework);
Expand Down Expand Up @@ -215,9 +239,7 @@
}

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)),
);
Expand Down Expand Up @@ -257,8 +279,8 @@
export function extractAppIdentifierIos(fileContent: string): AppIdentifier[] {
const appIdRegex = /<key>GOOGLE_APP_ID<\/key>\s*<string>([^<]*)<\/string>/;
const bundleIdRegex = /<key>BUNDLE_ID<\/key>\s*<string>([^<]*)<\/string>/;
const appIdMatch = fileContent.match(appIdRegex);

Check warning on line 282 in src/appUtils.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Use the `RegExp#exec()` method instead
const bundleIdMatch = fileContent.match(bundleIdRegex);

Check warning on line 283 in src/appUtils.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Use the `RegExp#exec()` method instead
if (appIdMatch?.[1]) {
return [
{
Expand All @@ -278,12 +300,12 @@
export function extractAppIdentifiersAndroid(fileContent: string): AppIdentifier[] {
const identifiers: AppIdentifier[] = [];
try {
const config = JSON.parse(fileContent);

Check warning on line 303 in src/appUtils.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe assignment of an `any` value
if (config.client && Array.isArray(config.client)) {

Check warning on line 304 in src/appUtils.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .client on an `any` value

Check warning on line 304 in src/appUtils.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .client on an `any` value
for (const client of config.client) {

Check warning on line 305 in src/appUtils.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .client on an `any` value
if (client.client_info?.mobilesdk_app_id) {

Check warning on line 306 in src/appUtils.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .client_info on an `any` value
identifiers.push({
appId: client.client_info.mobilesdk_app_id,

Check warning on line 308 in src/appUtils.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe assignment of an `any` value
bundleId: client.client_info.android_client_info?.package_name,
});
}
Expand Down
3 changes: 2 additions & 1 deletion src/commands/dataconnect-sdk-generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}),
);
Expand Down
7 changes: 7 additions & 0 deletions src/dataconnect/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,20 @@ export interface Generate {
swiftSdk?: SwiftSDK | SwiftSDK[];
kotlinSdk?: KotlinSDK | KotlinSDK[];
dartSdk?: DartSDK | DartSDK[];
adminNodeSdk?: AdminNodeSDK | AdminNodeSDK[];
}

export interface SupportedFrameworks {
react?: boolean;
angular?: boolean;
}

export interface AdminNodeSDK {
outputDir: string;
package: string;
packageJsonDir?: string;
}

export interface JavascriptSDK extends SupportedFrameworks {
outputDir: string;
package: string;
Expand Down
13 changes: 13 additions & 0 deletions src/init/features/dataconnect/sdk.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -192,6 +204,7 @@ describe("chooseApp", () => {
expect(promptStub.called).to.be.false;
});

// TODO: Add a test for admin node.
it("should deduplicate apps with the same platform and directory", async () => {
const apps: App[] = [
{ platform: Platform.WEB, directory: "web", frameworks: [Framework.REACT], appId: "app1" },
Expand Down
23 changes: 23 additions & 0 deletions src/init/features/dataconnect/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Config } from "../../../config";
import { Setup } from "../..";
import { loadAll } from "../../../dataconnect/load";
import {
AdminNodeSDK,
ConnectorInfo,
ConnectorYaml,
DartSDK,
Expand Down Expand Up @@ -191,10 +192,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;
Expand Down Expand Up @@ -348,6 +353,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`)),
Expand Down
Loading