Skip to content

Commit 2add8f1

Browse files
committed
Revert changes to dataconnect appFinder utils and manual project entry
1 parent 1f34e98 commit 2add8f1

File tree

4 files changed

+82
-63
lines changed

4 files changed

+82
-63
lines changed

src/bin/mcp.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { resolve } from "path";
1212
const STARTUP_MESSAGE = `
1313
This is a running process of the Firebase MCP server. This command should only be executed by an MCP client. An example MCP client configuration might be:
1414
15-
An example MCP client configuration for apps using Firebase Build tools might be:
1615
{
1716
"mcpServers": {
1817
"firebase": {
@@ -28,7 +27,6 @@ export async function mcp(): Promise<void> {
2827
options: {
2928
only: { type: "string", default: "" },
3029
dir: { type: "string" },
31-
project: { type: "string" },
3230
"generate-tool-list": { type: "boolean", default: false },
3331
"generate-prompt-list": { type: "boolean", default: false },
3432
"generate-resource-list": { type: "boolean", default: false },
@@ -56,11 +54,9 @@ export async function mcp(): Promise<void> {
5654
const activeFeatures = (values.only || "")
5755
.split(",")
5856
.filter((f) => SERVER_FEATURES.includes(f as ServerFeature)) as ServerFeature[];
59-
6057
const server = new FirebaseMcpServer({
6158
activeFeatures,
6259
projectRoot: values.dir ? resolve(values.dir) : undefined,
63-
projectId: values["project"],
6460
});
6561
await server.start();
6662
if (process.stdin.isTTY) process.stderr.write(STARTUP_MESSAGE);

src/dataconnect/appFinder.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ describe("detectApps", () => {
105105
fs.outputFileSync(`${testDir}/web/package.json`, "{}");
106106
fs.mkdirpSync(`${testDir}/android/src/main`);
107107
const apps = await detectApps(testDir);
108-
expect(apps).to.have.deep.members([
108+
expect(apps).to.deep.equal([
109109
{
110110
platform: Platform.WEB,
111111
directory: `web`,
@@ -172,6 +172,8 @@ describe("detectApps", () => {
172172
frameworks: [],
173173
},
174174
];
175-
expect(apps).to.have.deep.members(expected);
175+
expect(apps.sort((a, b) => a.directory.localeCompare(b.directory))).to.deep.equal(
176+
expected.sort((a, b) => a.directory.localeCompare(b.directory)),
177+
);
176178
});
177179
});

src/dataconnect/appFinder.ts

Lines changed: 76 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1+
import * as fs from "fs-extra";
12
import * as path from "path";
3+
import { glob } from "glob";
24
import { Framework, Platform } from "./types";
3-
import {
4-
detectApps as appUtilsDetectApps,
5-
getPlatformsFromFolder,
6-
Platform as AppUtilsPlatform,
7-
Framework as AppUtilsFramework,
8-
App as AppUtilsApp,
9-
} from "../appUtils";
5+
import { PackageJSON } from "../frameworks/compose/discover/runtime/node";
106

117
export interface App {
128
platform: Platform;
@@ -21,61 +17,96 @@ export function appDescription(a: App): string {
2117

2218
/** Given a directory, determine the platform type */
2319
export async function getPlatformFromFolder(dirPath: string): Promise<Platform> {
24-
const platforms = await getPlatformsFromFolder(dirPath);
25-
26-
if (platforms.length === 0) {
20+
const apps = await detectApps(dirPath);
21+
const hasWeb = apps.some((app) => app.platform === Platform.WEB);
22+
const hasAndroid = apps.some((app) => app.platform === Platform.ANDROID);
23+
const hasIOS = apps.some((app) => app.platform === Platform.IOS);
24+
const hasDart = apps.some((app) => app.platform === Platform.FLUTTER);
25+
if (!hasWeb && !hasAndroid && !hasIOS && !hasDart) {
2726
return Platform.NONE;
27+
} else if (hasWeb && !hasAndroid && !hasIOS && !hasDart) {
28+
return Platform.WEB;
29+
} else if (hasAndroid && !hasWeb && !hasIOS && !hasDart) {
30+
return Platform.ANDROID;
31+
} else if (hasIOS && !hasWeb && !hasAndroid && !hasDart) {
32+
return Platform.IOS;
33+
} else if (hasDart && !hasWeb && !hasIOS && !hasAndroid) {
34+
return Platform.FLUTTER;
2835
}
29-
30-
// Its not clear which platform the app directory is
36+
// At this point, its not clear which platform the app directory is
3137
// because we found indicators for multiple platforms.
32-
if (platforms.length > 1) {
33-
return Platform.MULTIPLE;
34-
}
35-
36-
return toDataConnectPlatform(platforms[0]);
38+
return Platform.MULTIPLE;
3739
}
3840

3941
/** Detects the apps in a given directory */
4042
export async function detectApps(dirPath: string): Promise<App[]> {
41-
return appUtilsDetectApps(dirPath).then((apps) => apps.map(toDataConnectApp));
43+
const packageJsonFiles = await detectFiles(dirPath, "package.json");
44+
const pubSpecYamlFiles = await detectFiles(dirPath, "pubspec.yaml");
45+
const srcMainFolders = await detectFiles(dirPath, "src/main/");
46+
const xCodeProjects = await detectFiles(dirPath, "*.xcodeproj/");
47+
const webApps = await Promise.all(packageJsonFiles.map((p) => packageJsonToWebApp(dirPath, p)));
48+
const flutterApps = pubSpecYamlFiles.map((f) => ({
49+
platform: Platform.FLUTTER,
50+
directory: path.dirname(f),
51+
}));
52+
const androidApps = srcMainFolders
53+
.map((f) => ({
54+
platform: Platform.ANDROID,
55+
directory: path.dirname(path.dirname(f)),
56+
}))
57+
.filter((a) => !flutterApps.some((f) => isPathInside(f.directory, a.directory)));
58+
const iosApps = xCodeProjects
59+
.map((f) => ({
60+
platform: Platform.IOS,
61+
directory: path.dirname(f),
62+
}))
63+
.filter((a) => !flutterApps.some((f) => isPathInside(f.directory, a.directory)));
64+
return [...webApps, ...flutterApps, ...androidApps, ...iosApps];
4265
}
4366

4467
export function isPathInside(parent: string, child: string): boolean {
4568
const relativePath = path.relative(parent, child);
4669
return !relativePath.startsWith(`..`);
4770
}
4871

49-
function toDataConnectPlatform(platform: AppUtilsPlatform): Platform {
50-
switch (platform) {
51-
case AppUtilsPlatform.IOS:
52-
return Platform.IOS;
53-
case AppUtilsPlatform.ANDROID:
54-
return Platform.ANDROID;
55-
case AppUtilsPlatform.FLUTTER:
56-
return Platform.FLUTTER;
57-
case AppUtilsPlatform.WEB:
58-
return Platform.WEB;
59-
}
72+
async function packageJsonToWebApp(dirPath: string, packageJsonFile: string): Promise<App> {
73+
const fullPath = path.join(dirPath, packageJsonFile);
74+
const packageJson = JSON.parse((await fs.readFile(fullPath)).toString());
75+
return {
76+
platform: Platform.WEB,
77+
directory: path.dirname(packageJsonFile),
78+
frameworks: getFrameworksFromPackageJson(packageJson),
79+
};
6080
}
6181

62-
function toDataConnectFramework(framework: AppUtilsFramework): Framework {
63-
switch (framework) {
64-
case AppUtilsFramework.ANGULAR:
65-
return "angular";
66-
case AppUtilsFramework.REACT:
67-
return "react";
68-
}
82+
export const WEB_FRAMEWORKS: Framework[] = ["react", "angular"];
83+
export const WEB_FRAMEWORKS_SIGNALS: { [key in Framework]: string[] } = {
84+
react: ["react", "next"],
85+
angular: ["@angular/core"],
86+
};
87+
88+
export function getFrameworksFromPackageJson(packageJson: PackageJSON): Framework[] {
89+
const devDependencies = Object.keys(packageJson.devDependencies ?? {});
90+
const dependencies = Object.keys(packageJson.dependencies ?? {});
91+
const allDeps = Array.from(new Set([...devDependencies, ...dependencies]));
92+
return WEB_FRAMEWORKS.filter((framework) =>
93+
WEB_FRAMEWORKS_SIGNALS[framework]!.find((dep) => allDeps.includes(dep)),
94+
);
6995
}
7096

71-
function toDataConnectApp(app: AppUtilsApp): App {
72-
const output: App = {
73-
platform: toDataConnectPlatform(app.platform),
74-
directory: app.directory,
97+
async function detectFiles(dirPath: string, filePattern: string): Promise<string[]> {
98+
const options = {
99+
cwd: dirPath,
100+
ignore: [
101+
"**/dataconnect*/**",
102+
"**/node_modules/**", // Standard dependency directory
103+
"**/dist/**", // Common build output
104+
"**/build/**", // Common build output
105+
"**/out/**", // Another common build output
106+
"**/.next/**", // Next.js build directory
107+
"**/coverage/**", // Test coverage reports
108+
],
109+
absolute: false,
75110
};
76-
77-
if (app.frameworks) {
78-
output.frameworks = app.frameworks.map((framework) => toDataConnectFramework(framework));
79-
}
80-
return output;
111+
return glob(`**/${filePattern}`, options);
81112
}

src/mcp/index.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ export class FirebaseMcpServer {
6666
private _readyPromises: { resolve: () => void; reject: (err: unknown) => void }[] = [];
6767
startupRoot?: string;
6868
cachedProjectDir?: string;
69-
cachedProjectId?: string;
7069
server: Server;
7170
activeFeatures?: ServerFeature[];
7271
detectedFeatures?: ServerFeature[];
@@ -103,11 +102,7 @@ export class FirebaseMcpServer {
103102
return trackGA4(event, { ...params, ...clientInfoParams });
104103
}
105104

106-
constructor(options: {
107-
activeFeatures?: ServerFeature[];
108-
projectRoot?: string;
109-
projectId?: string;
110-
}) {
105+
constructor(options: { activeFeatures?: ServerFeature[]; projectRoot?: string }) {
111106
this.activeFeatures = options.activeFeatures;
112107
this.startupRoot = options.projectRoot || process.env.PROJECT_ROOT;
113108
this.server = new Server({ name: "firebase", version: SERVER_VERSION });
@@ -151,7 +146,6 @@ export class FirebaseMcpServer {
151146
return {};
152147
});
153148

154-
this.cachedProjectId = options.projectId;
155149
this.detectProjectRoot();
156150
this.detectActiveFeatures();
157151
}
@@ -271,11 +265,7 @@ export class FirebaseMcpServer {
271265
}
272266

273267
async resolveOptions(): Promise<Partial<Options>> {
274-
const options: Partial<Options> = {
275-
cwd: this.cachedProjectDir,
276-
isMCP: true,
277-
projectId: this.cachedProjectId,
278-
};
268+
const options: Partial<Options> = { cwd: this.cachedProjectDir, isMCP: true };
279269
await cmd.prepare(options);
280270
return options;
281271
}

0 commit comments

Comments
 (0)