Skip to content
Merged
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
- `firebase emulator:start` use a default project if no project can be found. (#9072)
- `firebase emulator:start` use a default project `demo-no-project` if no project can be found. (#9072)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressing feedback from a different PR #9072 (comment)

- `firebase init dataconnect` also supports bootstrapping flutter template. (#9084)
8 changes: 7 additions & 1 deletion src/init/features/dataconnect/create_app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,15 @@
await executeCommand("npx", args);
}

/** Create a Flutter app using flutter create. */
export async function createFlutterApp(webAppId: string): Promise<void> {
const args = ["create", webAppId];
await executeCommand("flutter", args);
}

// Function to execute a command asynchronously and pipe I/O
async function executeCommand(command: string, args: string[]): Promise<void> {
logLabeledBullet("dataconnect", `Running ${clc.bold(`${command} ${args.join(" ")}`)}`);
logLabeledBullet("dataconnect", `> ${clc.bold(`${command} ${args.join(" ")}`)}`);
return new Promise((resolve, reject) => {
// spawn returns a ChildProcess object
const childProcess = spawn(command, args, {
Expand All @@ -44,7 +50,7 @@
resolve();
} else {
// Command failed
reject(new Error(`Command failed with exit code ${code}`));

Check warning on line 53 in src/init/features/dataconnect/create_app.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Invalid type "number | null" of template literal expression
}
});

Expand Down
26 changes: 17 additions & 9 deletions src/init/features/dataconnect/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@
logLabeledWarning,
logLabeledBullet,
newUniqueId,
commandExistsSync,
} from "../../../utils";
import { DataConnectEmulator } from "../../../emulator/dataconnectEmulator";
import { getGlobalDefaultAccount } from "../../../auth";
import { createNextApp, createReactApp } from "./create_app";
import { createFlutterApp, createNextApp, createReactApp } from "./create_app";
import { trackGA4 } from "../../../track";
import { dirExistsSync, listFiles } from "../../../fsutils";

Expand All @@ -49,32 +50,39 @@
displayIOSWarning: boolean;
};

export async function askQuestions(setup: Setup): Promise<void> {

Check warning on line 53 in src/init/features/dataconnect/sdk.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing JSDoc comment
const info: SdkRequiredInfo = {
apps: [],
};

info.apps = await chooseApp();
if (!info.apps.length) {
// By default, create an React web app.
const existingFilesAndDirs = listFiles(cwd);
const webAppId = newUniqueId("web-app", existingFilesAndDirs);
const npxMissingWarning = commandExistsSync("npx")
? ""
: clc.yellow(" (you need to install Node.js first)");
const flutterMissingWarning = commandExistsSync("flutter")
? ""
: clc.yellow(" (you need to install Flutter first)");

const choice = await select({
message: `Do you want to create an app template?`,
choices: [
// TODO: Create template tailored to FDC.
{ name: "React", value: "react" },
{ name: "Next.JS", value: "next" },
// TODO: Add flutter here.
{ name: `React${npxMissingWarning}`, value: "react" },
{ name: `Next.JS${npxMissingWarning}`, value: "next" },
{ name: `Flutter${flutterMissingWarning}`, value: "flutter" },
{ name: "no", value: "no" },
],
});
switch (choice) {
case "react":
await createReactApp(webAppId);
await createReactApp(newUniqueId("web-app", listFiles(cwd)));
break;
case "next":
await createNextApp(webAppId);
await createNextApp(newUniqueId("web-app", listFiles(cwd)));
break;
case "flutter":
await createFlutterApp(newUniqueId("flutter_app", listFiles(cwd)));
break;
case "no":
break;
Expand Down Expand Up @@ -141,7 +149,7 @@
return apps;
}

export async function actuate(setup: Setup, config: Config) {

Check warning on line 152 in src/init/features/dataconnect/sdk.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing JSDoc comment

Check warning on line 152 in src/init/features/dataconnect/sdk.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
const fdcInfo = setup.featureInfo?.dataconnect;
const sdkInfo = setup.featureInfo?.dataconnectSdk;
if (!sdkInfo) {
Expand All @@ -166,7 +174,7 @@
}
}

async function actuateWithInfo(setup: Setup, config: Config, info: SdkRequiredInfo) {

Check warning on line 177 in src/init/features/dataconnect/sdk.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
if (!info.apps.length) {
// If no apps is specified, try to detect it again.
// In `firebase init dataconnect:sdk`, customer may create the app while the command is running.
Expand Down Expand Up @@ -241,7 +249,7 @@
* and short-circuit the prompt.
*
* `FDC_CONNECTOR` should have the same `<location>/<serviceId>/<connectorId>`.
* @param choices

Check warning on line 252 in src/init/features/dataconnect/sdk.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing JSDoc @param "choices" description

Check warning on line 252 in src/init/features/dataconnect/sdk.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Expected @param names to be "setup, config". Got "choices"
*/
async function chooseExistingConnector(setup: Setup, config: Config): Promise<ConnectorInfo> {
const serviceInfos = await loadAll(setup.projectId || "", config);
Expand Down
3 changes: 2 additions & 1 deletion src/mcp/errors.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { commandExistsSync, mcpError } from "./util";
import { mcpError } from "./util";
import { commandExistsSync } from "../utils";

export const NO_PROJECT_ERROR = mcpError(
'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.',
"PRECONDITION_FAILED",
);

export function mcpAuthError(skipADC: boolean): CallToolResult {

Check warning on line 10 in src/mcp/errors.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing JSDoc comment
const cmd = commandExistsSync("firebase") ? "firebase" : "npx -y firebase-tools";
if (skipADC) {
return mcpError(`The user is not currently logged into the Firebase CLI, which is required to use this tool. Please instruct the user to execute this shell command to sign in.
Expand All @@ -22,7 +23,7 @@
[ADC]: https://cloud.google.com/docs/authentication/application-default-credentials`);
}

export function mcpGeminiError(projectId: string) {

Check warning on line 26 in src/mcp/errors.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing JSDoc comment

Check warning on line 26 in src/mcp/errors.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
const consoleUrl = `https://firebase.corp.google.com/project/${projectId}/overview`;
return mcpError(
`This tool uses the Gemini in Firebase API. Visit Firebase Console to enable the Gemini in Firebase API ${consoleUrl} and try again.`,
Expand Down
2 changes: 1 addition & 1 deletion src/mcp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export class FirebaseMcpServer {
}

async getEmulatorHubClient(): Promise<EmulatorHubClient | undefined> {
// Single initilization
// Single initialization
if (this.emulatorHubClient) {
return this.emulatorHubClient;
}
Expand Down
24 changes: 0 additions & 24 deletions src/mcp/util.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { execSync } from "child_process";
import { dump } from "js-yaml";
import { platform } from "os";
import { ServerFeature } from "./types";
import {
apphostingOrigin,
Expand Down Expand Up @@ -64,28 +62,6 @@ export function mcpError(message: Error | string | unknown, code?: string): Call
* Wraps a throwing function with a safe conversion to mcpError.
*/

/**
* Checks if a command exists in the system.
*/
export function commandExistsSync(command: string): boolean {
try {
const isWindows = platform() === "win32";
// For Windows, `where` is more appropriate. It also often outputs the path.
// For Unix-like systems, `which` is standard.
// The `2> nul` (Windows) or `2>/dev/null` (Unix) redirects stderr to suppress error messages.
// The `>` nul / `>/dev/null` redirects stdout as we only care about the exit code.
const commandToCheck = isWindows
? `where "${command}" > nul 2> nul`
: `which "${command}" > /dev/null 2> /dev/null`;

execSync(commandToCheck);
return true; // If execSync doesn't throw, the command was found (exit code 0)
} catch (error) {
// If the command is not found, execSync will throw an error (non-zero exit code)
return false;
}
}

const SERVER_FEATURE_APIS: Record<ServerFeature, string> = {
core: "",
firestore: firestoreOrigin(),
Expand Down
24 changes: 24 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { readTemplateSync } from "./templates";
import { isVSCodeExtension } from "./vsCodeUtils";
import { Config } from "./config";
import { dirExistsSync, fileExistsSync } from "./fsutils";
import { platform } from "node:os";
import { execSync } from "node:child_process";
export const IS_WINDOWS = process.platform === "win32";
const SUCCESS_CHAR = IS_WINDOWS ? "+" : "✔";
const WARNING_CHAR = IS_WINDOWS ? "!" : "⚠";
Expand Down Expand Up @@ -1006,3 +1008,25 @@ export function newUniqueId(recommended: string, existingIDs: string[]): string
}
return id;
}

/**
* Checks if a command exists in the system.
*/
export function commandExistsSync(command: string): boolean {
try {
const isWindows = platform() === "win32";
// For Windows, `where` is more appropriate. It also often outputs the path.
// For Unix-like systems, `which` is standard.
// The `2> nul` (Windows) or `2>/dev/null` (Unix) redirects stderr to suppress error messages.
// The `>` nul / `>/dev/null` redirects stdout as we only care about the exit code.
const commandToCheck = isWindows
? `where "${command}" > nul 2> nul`
: `which "${command}" > /dev/null 2> /dev/null`;

execSync(commandToCheck);
return true; // If execSync doesn't throw, the command was found (exit code 0)
} catch (error) {
// If the command is not found, execSync will throw an error (non-zero exit code)
return false;
}
}
Loading