Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
109 changes: 70 additions & 39 deletions src/init/features/emulators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,105 +9,136 @@
import { Config } from "../../config";
import { EmulatorsConfig } from "../../firebaseConfig";

interface EmulatorsInitSelections {
emulators?: Emulators[];
download?: boolean;
export interface EmulatorsInfo {
emulators: Emulators[];
download: boolean;
config: EmulatorsConfig;
}

export async function doSetup(setup: Setup, config: Config) {
export async function emulatorsAskQuestions(setup: Setup, config: Config): Promise<void> {
const choices = ALL_SERVICE_EMULATORS.map((e) => {
return {
value: e,
// TODO: latest versions of inquirer have a name vs description.
// We should learn more and whether it's worth investing in.
name: Constants.description(e),
checked: config?.has(e) || config?.has(`emulators.${e}`),
};
});

const selections: EmulatorsInitSelections = {};
selections.emulators = await checkbox<Emulators>({
const selectedEmulators = await checkbox<Emulators>({
message:
"Which Firebase emulators do you want to set up? " +
"Press Space to select emulators, then Enter to confirm your choices.",
choices: choices,
});

if (!selections.emulators) {
if (!selectedEmulators || !selectedEmulators.length) {
return;
}

setup.config.emulators = setup.config.emulators || {};
const emulators: EmulatorsConfig = setup.config.emulators || {};
for (const selected of selections.emulators) {
if (selected === "extensions") continue;
const selectedEmulator = emulators[selected] || {};
setup.featureInfo = setup.featureInfo || {};
const emulatorsInfo: EmulatorsInfo = {
emulators: selectedEmulators,
config: {},
download: false,
};
setup.featureInfo.emulators = emulatorsInfo;

const newConfig = emulatorsInfo.config;
const existingConfig = setup.config.emulators || {};

const currentPort = selectedEmulator.port;
for (const selected of selectedEmulators) {
if (selected === "extensions") continue;
newConfig[selected] = {};
const currentPort = existingConfig[selected]?.port;
if (currentPort) {
utils.logBullet(`Port for ${selected} already configured: ${clc.cyan(currentPort)}`);
} else {
selectedEmulator.port = await number({
newConfig[selected]!.port = await number({
message: `Which port do you want to use for the ${clc.underline(selected)} emulator?`,
default: Constants.getDefaultPort(selected),
});
}
emulators[selected] = selectedEmulator;

const additionalInitFn = AdditionalInitFns[selected];
if (additionalInitFn) {
const additionalOptions = await additionalInitFn(config);
if (additionalOptions) {
emulators[selected] = {
...setup.config.emulators[selected],
...additionalOptions,
};
Object.assign(newConfig[selected]!, additionalOptions);
}
}
}

if (selections.emulators.length) {
if (selectedEmulators.length) {
const uiDesc = Constants.description(Emulators.UI);
if (setup.config.emulators.ui && setup.config.emulators.ui.enabled !== false) {
const currentPort = setup.config.emulators.ui.port || "(automatic)";
utils.logBullet(`${uiDesc} already enabled with port: ${clc.cyan(currentPort)}`);
} else {
const ui = setup.config.emulators.ui || {};
setup.config.emulators.ui = ui;
const existingUiConfig = existingConfig.ui || {};
newConfig.ui = {};

ui.enabled = await confirm({
let enableUi: boolean;
if (existingUiConfig.enabled !== undefined) {
utils.logBullet(`${uiDesc} already ${existingUiConfig.enabled ? "enabled" : "disabled"}.`);
enableUi = existingUiConfig.enabled;
} else {
enableUi = await confirm({
message: `Would you like to enable the ${uiDesc}?`,
default: true,
});
}
newConfig.ui.enabled = enableUi;

if (ui.enabled) {
ui.port = await number({
if (newConfig.ui.enabled) {
const currentPort = existingUiConfig.port;
if (currentPort) {
utils.logBullet(`Port for ${uiDesc} already configured: ${clc.cyan(currentPort)}`);
} else {
newConfig.ui.port = await number({
message: `Which port do you want to use for the ${clc.underline(uiDesc)} (leave empty to use any available port)?`,
required: false,
});
}
}
}

selections.download = await confirm({
if (selectedEmulators.length) {
emulatorsInfo.download = await confirm({
message: "Would you like to download the emulators now?",
default: true,
});
}
}

export async function emulatorsActuate(setup: Setup, config: Config): Promise<void> {
const emulatorsInfo = setup.featureInfo?.emulators;
if (!emulatorsInfo) {
return;
}

setup.config.emulators = setup.config.emulators || {};
const emulatorsConfig = setup.config.emulators;

// Merge the config from the questions into the main config.
for (const emulatorName of Object.keys(emulatorsInfo.config)) {
const key = emulatorName as keyof EmulatorsConfig;
if (key === "ui") {
emulatorsConfig.ui = { ...emulatorsConfig.ui, ...emulatorsInfo.config.ui };
} else if (emulatorsInfo.config[key]) {
emulatorsConfig[key] = { ...emulatorsConfig[key], ...emulatorsInfo.config[key] };

Check failure on line 124 in src/init/features/emulators.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (20)

Spread types may only be created from object types.

Check failure on line 124 in src/init/features/emulators.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (22)

Spread types may only be created from object types.

Check failure on line 124 in src/init/features/emulators.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (22)

Spread types may only be created from object types.

Check failure on line 124 in src/init/features/emulators.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (20)

Spread types may only be created from object types.

Check failure on line 124 in src/init/features/emulators.ts

View workflow job for this annotation

GitHub Actions / unit (20)

Spread types may only be created from object types.

Check failure on line 124 in src/init/features/emulators.ts

View workflow job for this annotation

GitHub Actions / unit (22)

Spread types may only be created from object types.

Check failure on line 124 in src/init/features/emulators.ts

View workflow job for this annotation

GitHub Actions / check-json-schema (22)

Spread types may only be created from object types.

Check failure on line 124 in src/init/features/emulators.ts

View workflow job for this annotation

GitHub Actions / check-json-schema (20)

Spread types may only be created from object types.

Check failure on line 124 in src/init/features/emulators.ts

View workflow job for this annotation

GitHub Actions / check-json-schema (20)

Spread types may only be created from object types.

Check failure on line 124 in src/init/features/emulators.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Spread types may only be created from object types.

Check failure on line 124 in src/init/features/emulators.ts

View workflow job for this annotation

GitHub Actions / unit (22)

Spread types may only be created from object types.

Check failure on line 124 in src/init/features/emulators.ts

View workflow job for this annotation

GitHub Actions / unit (20)

Spread types may only be created from object types.
}
}

// Set the default behavior to be single project mode.
if (setup.config.emulators.singleProjectMode === undefined) {
setup.config.emulators.singleProjectMode = true;
if (emulatorsConfig.singleProjectMode === undefined) {
emulatorsConfig.singleProjectMode = true;
}

if (selections.download) {
for (const selected of selections.emulators) {
if (emulatorsInfo.download) {
for (const selected of emulatorsInfo.emulators) {
if (isDownloadableEmulator(selected)) {
await downloadIfNecessary(selected);
}
}

if (setup?.config?.emulators?.ui?.enabled) {
downloadIfNecessary(Emulators.UI);
if (emulatorsConfig.ui?.enabled) {
await downloadIfNecessary(Emulators.UI);
}
}
}
}
7 changes: 6 additions & 1 deletion src/init/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
dataconnectSdk?: features.DataconnectSdkInfo;
storage?: features.StorageInfo;
apptesting?: features.ApptestingInfo;
emulators?: features.EmulatorsInfo;

Check failure on line 41 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (20)

'"/Users/runner/work/firebase-tools/firebase-tools/src/init/features/index"' has no exported member named 'EmulatorsInfo'. Did you mean 'emulators'?

Check failure on line 41 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (22)

'"/Users/runner/work/firebase-tools/firebase-tools/src/init/features/index"' has no exported member named 'EmulatorsInfo'. Did you mean 'emulators'?

Check failure on line 41 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (22)

'"/Users/runner/work/firebase-tools/firebase-tools/src/init/features/index"' has no exported member named 'EmulatorsInfo'. Did you mean 'emulators'?

Check failure on line 41 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (20)

'"/Users/runner/work/firebase-tools/firebase-tools/src/init/features/index"' has no exported member named 'EmulatorsInfo'. Did you mean 'emulators'?

Check failure on line 41 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / unit (20)

'"/home/runner/work/firebase-tools/firebase-tools/src/init/features/index"' has no exported member named 'EmulatorsInfo'. Did you mean 'emulators'?

Check failure on line 41 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / unit (22)

'"/home/runner/work/firebase-tools/firebase-tools/src/init/features/index"' has no exported member named 'EmulatorsInfo'. Did you mean 'emulators'?

Check failure on line 41 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / check-json-schema (22)

'"/home/runner/work/firebase-tools/firebase-tools/src/init/features/index"' has no exported member named 'EmulatorsInfo'. Did you mean 'emulators'?

Check failure on line 41 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / check-json-schema (20)

'"/home/runner/work/firebase-tools/firebase-tools/src/init/features/index"' has no exported member named 'EmulatorsInfo'. Did you mean 'emulators'?

Check failure on line 41 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / check-json-schema (20)

'"/home/runner/work/firebase-tools/firebase-tools/src/init/features/index"' has no exported member named 'EmulatorsInfo'. Did you mean 'emulators'?

Check failure on line 41 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

'"/home/runner/work/firebase-tools/firebase-tools/src/init/features/index"' has no exported member named 'EmulatorsInfo'. Did you mean 'emulators'?

Check failure on line 41 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / unit (22)

'"/home/runner/work/firebase-tools/firebase-tools/src/init/features/index"' has no exported member named 'EmulatorsInfo'. Did you mean 'emulators'?

Check failure on line 41 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / unit (20)

'"/home/runner/work/firebase-tools/firebase-tools/src/init/features/index"' has no exported member named 'EmulatorsInfo'. Did you mean 'emulators'?
}

interface Feature {
Expand Down Expand Up @@ -84,7 +85,11 @@
askQuestions: features.storageAskQuestions,
actuate: features.storageActuate,
},
{ name: "emulators", doSetup: features.emulators },
{
name: "emulators",
askQuestions: features.emulatorsAskQuestions,

Check failure on line 90 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (20)

Property 'emulatorsAskQuestions' does not exist on type 'typeof import("/Users/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 90 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (22)

Property 'emulatorsAskQuestions' does not exist on type 'typeof import("/Users/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 90 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (22)

Property 'emulatorsAskQuestions' does not exist on type 'typeof import("/Users/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 90 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (20)

Property 'emulatorsAskQuestions' does not exist on type 'typeof import("/Users/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 90 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / unit (20)

Property 'emulatorsAskQuestions' does not exist on type 'typeof import("/home/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 90 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / unit (22)

Property 'emulatorsAskQuestions' does not exist on type 'typeof import("/home/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 90 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / check-json-schema (22)

Property 'emulatorsAskQuestions' does not exist on type 'typeof import("/home/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 90 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / check-json-schema (20)

Property 'emulatorsAskQuestions' does not exist on type 'typeof import("/home/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 90 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / check-json-schema (20)

Property 'emulatorsAskQuestions' does not exist on type 'typeof import("/home/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 90 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Property 'emulatorsAskQuestions' does not exist on type 'typeof import("/home/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 90 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / unit (22)

Property 'emulatorsAskQuestions' does not exist on type 'typeof import("/home/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 90 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / unit (20)

Property 'emulatorsAskQuestions' does not exist on type 'typeof import("/home/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.
actuate: features.emulatorsActuate,

Check failure on line 91 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (20)

Property 'emulatorsActuate' does not exist on type 'typeof import("/Users/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 91 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (22)

Property 'emulatorsActuate' does not exist on type 'typeof import("/Users/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 91 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (22)

Property 'emulatorsActuate' does not exist on type 'typeof import("/Users/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 91 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (20)

Property 'emulatorsActuate' does not exist on type 'typeof import("/Users/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 91 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / unit (20)

Property 'emulatorsActuate' does not exist on type 'typeof import("/home/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 91 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / unit (22)

Property 'emulatorsActuate' does not exist on type 'typeof import("/home/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 91 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / check-json-schema (22)

Property 'emulatorsActuate' does not exist on type 'typeof import("/home/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 91 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / check-json-schema (20)

Property 'emulatorsActuate' does not exist on type 'typeof import("/home/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 91 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / check-json-schema (20)

Property 'emulatorsActuate' does not exist on type 'typeof import("/home/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 91 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Property 'emulatorsActuate' does not exist on type 'typeof import("/home/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 91 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / unit (22)

Property 'emulatorsActuate' does not exist on type 'typeof import("/home/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.

Check failure on line 91 in src/init/index.ts

View workflow job for this annotation

GitHub Actions / unit (20)

Property 'emulatorsActuate' does not exist on type 'typeof import("/home/runner/work/firebase-tools/firebase-tools/src/init/features/index")'.
},
{ name: "extensions", doSetup: features.extensions },
{ name: "project", doSetup: features.project }, // always runs, sets up .firebaserc
{ name: "remoteconfig", doSetup: features.remoteconfig },
Expand Down
61 changes: 58 additions & 3 deletions src/mcp/tools/core/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@
import { actuate, Setup, SetupInfo } from "../../../init/index";
import { freeTrialTermsLink } from "../../../dataconnect/freeTrial";
import { requireGeminiToS } from "../../errors";
import { Emulators } from "../../../emulator/types";

const emulatorHostPortSchema = z.object({
host: z.string().optional().describe("The host to use for the emulator."),
port: z.number().optional().describe("The port to use for the emulator."),
});

export const init = tool(
{
name: "init",
description:
"Initializes selected Firebase features in the workspace (Firestore, Data Connect, Realtime Database). All features are optional; provide only the products you wish to set up. " +
"Initializes selected Firebase features in the workspace (Firestore, Data Connect, Realtime Database, Emulators). All features are optional; provide only the products you wish to set up. " +
"You can initialize new features into an existing project directory, but re-initializing an existing feature may overwrite configuration. " +
"To deploy the initialized features, run the `firebase deploy` command after `firebase_init` tool.",
inputSchema: z.object({
Expand Down Expand Up @@ -121,6 +127,31 @@
.describe(
"Provide this object to initialize Firebase Storage in this project directory.",
),
emulators: z
.object({
auth: emulatorHostPortSchema.optional(),
database: emulatorHostPortSchema.optional(),
firestore: emulatorHostPortSchema.optional(),
functions: emulatorHostPortSchema.optional(),
hosting: emulatorHostPortSchema.optional(),
storage: emulatorHostPortSchema.optional(),
pubsub: emulatorHostPortSchema.optional(),
ui: z
.object({
enabled: z.boolean().optional(),
host: z.string().optional(),
port: z.union([z.string(), z.number()]).optional(),
})
.optional(),
singleProjectMode: z
.boolean()
.optional()
.describe("If true, do not warn on detection of multiple project IDs."),
})
.optional()
.describe(
"Provide this object to configure Firebase emulators in this project directory.",
),
}),
}),
annotations: {
Expand Down Expand Up @@ -178,8 +209,32 @@
apps: [],
};
}
if (features.storage) {
featuresList.push("storage");
featureInfo.storage = {
rulesFilename: features.storage.rules_filename,
rules: features.storage.rules,

Check failure on line 216 in src/mcp/tools/core/init.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (20)

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 216 in src/mcp/tools/core/init.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (22)

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 216 in src/mcp/tools/core/init.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (22)

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 216 in src/mcp/tools/core/init.ts

View workflow job for this annotation

GitHub Actions / vscode_unit (20)

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 216 in src/mcp/tools/core/init.ts

View workflow job for this annotation

GitHub Actions / unit (20)

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 216 in src/mcp/tools/core/init.ts

View workflow job for this annotation

GitHub Actions / unit (22)

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 216 in src/mcp/tools/core/init.ts

View workflow job for this annotation

GitHub Actions / check-json-schema (22)

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 216 in src/mcp/tools/core/init.ts

View workflow job for this annotation

GitHub Actions / check-json-schema (20)

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 216 in src/mcp/tools/core/init.ts

View workflow job for this annotation

GitHub Actions / check-json-schema (20)

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 216 in src/mcp/tools/core/init.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 216 in src/mcp/tools/core/init.ts

View workflow job for this annotation

GitHub Actions / unit (22)

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 216 in src/mcp/tools/core/init.ts

View workflow job for this annotation

GitHub Actions / unit (20)

Type 'string | undefined' is not assignable to type 'string'.
writeRules: true,
};
}
if (features.emulators) {
featuresList.push("emulators");
const emulatorKeys = Object.keys(features.emulators).filter(
(key) =>
key !== "ui" &&
key !== "singleProjectMode" &&
Object.values(Emulators).includes(key as Emulators),
) as Emulators[];

featureInfo.emulators = {
emulators: emulatorKeys,
config: features.emulators,
download: true, // Non-interactive, so default to downloading.
};
}

const setup: Setup = {
config: config?.src,
config: config?.src || {},
rcfile: rc?.data,
projectId: projectId,
features: [...featuresList],
Expand All @@ -206,4 +261,4 @@
`,
);
},
);
);
Loading