Skip to content

Commit eea540a

Browse files
authored
[FDC] Provide flutter as an option during init dataconnect (#9084)
1 parent 4cd0e15 commit eea540a

File tree

7 files changed

+53
-37
lines changed

7 files changed

+53
-37
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
- `firebase emulator:start` use a default project if no project can be found. (#9072)
1+
- `firebase emulator:start` use a default project `demo-no-project` if no project can be found. (#9072)
2+
- `firebase init dataconnect` also supports bootstrapping flutter template. (#9084)

src/init/features/dataconnect/create_app.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,15 @@ export async function createNextApp(webAppId: string): Promise<void> {
2626
await executeCommand("npx", args);
2727
}
2828

29+
/** Create a Flutter app using flutter create. */
30+
export async function createFlutterApp(webAppId: string): Promise<void> {
31+
const args = ["create", webAppId];
32+
await executeCommand("flutter", args);
33+
}
34+
2935
// Function to execute a command asynchronously and pipe I/O
3036
async function executeCommand(command: string, args: string[]): Promise<void> {
31-
logLabeledBullet("dataconnect", `Running ${clc.bold(`${command} ${args.join(" ")}`)}`);
37+
logLabeledBullet("dataconnect", `> ${clc.bold(`${command} ${args.join(" ")}`)}`);
3238
return new Promise((resolve, reject) => {
3339
// spawn returns a ChildProcess object
3440
const childProcess = spawn(command, args, {

src/init/features/dataconnect/sdk.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ import {
2828
logLabeledWarning,
2929
logLabeledBullet,
3030
newUniqueId,
31+
commandExistsSync,
3132
} from "../../../utils";
3233
import { DataConnectEmulator } from "../../../emulator/dataconnectEmulator";
3334
import { getGlobalDefaultAccount } from "../../../auth";
34-
import { createNextApp, createReactApp } from "./create_app";
35+
import { createFlutterApp, createNextApp, createReactApp } from "./create_app";
3536
import { trackGA4 } from "../../../track";
3637
import { dirExistsSync, listFiles } from "../../../fsutils";
3738

@@ -56,25 +57,32 @@ export async function askQuestions(setup: Setup): Promise<void> {
5657

5758
info.apps = await chooseApp();
5859
if (!info.apps.length) {
59-
// By default, create an React web app.
60-
const existingFilesAndDirs = listFiles(cwd);
61-
const webAppId = newUniqueId("web-app", existingFilesAndDirs);
60+
const npxMissingWarning = commandExistsSync("npx")
61+
? ""
62+
: clc.yellow(" (you need to install Node.js first)");
63+
const flutterMissingWarning = commandExistsSync("flutter")
64+
? ""
65+
: clc.yellow(" (you need to install Flutter first)");
66+
6267
const choice = await select({
6368
message: `Do you want to create an app template?`,
6469
choices: [
6570
// TODO: Create template tailored to FDC.
66-
{ name: "React", value: "react" },
67-
{ name: "Next.JS", value: "next" },
68-
// TODO: Add flutter here.
71+
{ name: `React${npxMissingWarning}`, value: "react" },
72+
{ name: `Next.JS${npxMissingWarning}`, value: "next" },
73+
{ name: `Flutter${flutterMissingWarning}`, value: "flutter" },
6974
{ name: "no", value: "no" },
7075
],
7176
});
7277
switch (choice) {
7378
case "react":
74-
await createReactApp(webAppId);
79+
await createReactApp(newUniqueId("web-app", listFiles(cwd)));
7580
break;
7681
case "next":
77-
await createNextApp(webAppId);
82+
await createNextApp(newUniqueId("web-app", listFiles(cwd)));
83+
break;
84+
case "flutter":
85+
await createFlutterApp(newUniqueId("flutter_app", listFiles(cwd)));
7886
break;
7987
case "no":
8088
break;

src/mcp/errors.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2-
import { commandExistsSync, mcpError } from "./util";
2+
import { mcpError } from "./util";
3+
import { commandExistsSync } from "../utils";
34

45
export const NO_PROJECT_ERROR = mcpError(
56
'No active project was found. Use the `firebase_update_environment` tool to set the project directory to an absolute folder location containing a firebase.json config file. Alternatively, change the MCP server config to add [...,"--dir","/absolute/path/to/project/directory"] in its command-line arguments.',

src/mcp/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export class FirebaseMcpServer {
183183
}
184184

185185
async getEmulatorHubClient(): Promise<EmulatorHubClient | undefined> {
186-
// Single initilization
186+
// Single initialization
187187
if (this.emulatorHubClient) {
188188
return this.emulatorHubClient;
189189
}

src/mcp/util.ts

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2-
import { execSync } from "child_process";
32
import { dump } from "js-yaml";
4-
import { platform } from "os";
53
import { ServerFeature } from "./types";
64
import {
75
apphostingOrigin,
@@ -64,28 +62,6 @@ export function mcpError(message: Error | string | unknown, code?: string): Call
6462
* Wraps a throwing function with a safe conversion to mcpError.
6563
*/
6664

67-
/**
68-
* Checks if a command exists in the system.
69-
*/
70-
export function commandExistsSync(command: string): boolean {
71-
try {
72-
const isWindows = platform() === "win32";
73-
// For Windows, `where` is more appropriate. It also often outputs the path.
74-
// For Unix-like systems, `which` is standard.
75-
// The `2> nul` (Windows) or `2>/dev/null` (Unix) redirects stderr to suppress error messages.
76-
// The `>` nul / `>/dev/null` redirects stdout as we only care about the exit code.
77-
const commandToCheck = isWindows
78-
? `where "${command}" > nul 2> nul`
79-
: `which "${command}" > /dev/null 2> /dev/null`;
80-
81-
execSync(commandToCheck);
82-
return true; // If execSync doesn't throw, the command was found (exit code 0)
83-
} catch (error) {
84-
// If the command is not found, execSync will throw an error (non-zero exit code)
85-
return false;
86-
}
87-
}
88-
8965
const SERVER_FEATURE_APIS: Record<ServerFeature, string> = {
9066
core: "",
9167
firestore: firestoreOrigin(),

src/utils.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import { readTemplateSync } from "./templates";
2525
import { isVSCodeExtension } from "./vsCodeUtils";
2626
import { Config } from "./config";
2727
import { dirExistsSync, fileExistsSync } from "./fsutils";
28+
import { platform } from "node:os";
29+
import { execSync } from "node:child_process";
2830
export const IS_WINDOWS = process.platform === "win32";
2931
const SUCCESS_CHAR = IS_WINDOWS ? "+" : "✔";
3032
const WARNING_CHAR = IS_WINDOWS ? "!" : "⚠";
@@ -1006,3 +1008,25 @@ export function newUniqueId(recommended: string, existingIDs: string[]): string
10061008
}
10071009
return id;
10081010
}
1011+
1012+
/**
1013+
* Checks if a command exists in the system.
1014+
*/
1015+
export function commandExistsSync(command: string): boolean {
1016+
try {
1017+
const isWindows = platform() === "win32";
1018+
// For Windows, `where` is more appropriate. It also often outputs the path.
1019+
// For Unix-like systems, `which` is standard.
1020+
// The `2> nul` (Windows) or `2>/dev/null` (Unix) redirects stderr to suppress error messages.
1021+
// The `>` nul / `>/dev/null` redirects stdout as we only care about the exit code.
1022+
const commandToCheck = isWindows
1023+
? `where "${command}" > nul 2> nul`
1024+
: `which "${command}" > /dev/null 2> /dev/null`;
1025+
1026+
execSync(commandToCheck);
1027+
return true; // If execSync doesn't throw, the command was found (exit code 0)
1028+
} catch (error) {
1029+
// If the command is not found, execSync will throw an error (non-zero exit code)
1030+
return false;
1031+
}
1032+
}

0 commit comments

Comments
 (0)