From fadefbe2deca3168e9a86b5c30fd79ab86f17324 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Thu, 2 Oct 2025 18:40:41 -0700 Subject: [PATCH 01/13] Show init button even if not login yet --- firebase-vscode/src/core/index.ts | 7 +++---- firebase-vscode/webviews/SidebarApp.tsx | 15 +++++++-------- src/commands/init.ts | 5 ++++- src/init/features/project.ts | 5 +++++ 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/firebase-vscode/src/core/index.ts b/firebase-vscode/src/core/index.ts index 1b70508edd1..3196cdc6270 100644 --- a/firebase-vscode/src/core/index.ts +++ b/firebase-vscode/src/core/index.ts @@ -15,6 +15,7 @@ import { registerWebhooks } from "./webhook"; import { createE2eMockable } from "../utils/test_hooks"; import { runTerminalTask } from "../data-connect/terminal"; import { AnalyticsLogger } from "../analytics"; +import { EmulatorHub } from "../emulator/hub"; export async function registerCore( broker: ExtensionBrokerImpl, @@ -66,10 +67,8 @@ export async function registerCore( ); return; } - const initCommand = currentProjectId.value - ? `${settings.firebasePath} init dataconnect --project ${currentProjectId.value}` - : `${settings.firebasePath} init dataconnect`; - + const projectId = currentProjectId.value || EmulatorHub.MISSING_PROJECT_PLACEHOLDER; + const initCommand = `${settings.firebasePath} init dataconnect --project ${projectId}`; initSpy.call("firebase init", initCommand, { focus: true }); }); diff --git a/firebase-vscode/webviews/SidebarApp.tsx b/firebase-vscode/webviews/SidebarApp.tsx index 16389d4e17a..318a34f7f95 100644 --- a/firebase-vscode/webviews/SidebarApp.tsx +++ b/firebase-vscode/webviews/SidebarApp.tsx @@ -277,14 +277,13 @@ export function SidebarApp() { - {user.value && - (isInitialized.value ? ( - - ) : ( - - - - ))} + {isInitialized.value ? ( + + ) : ( + + + + )} ); } diff --git a/src/commands/init.ts b/src/commands/init.ts index 8186241d498..77ba3f724cc 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -16,6 +16,7 @@ import { isEnabled } from "../experiments"; import { readTemplateSync } from "../templates"; import { FirebaseError } from "../error"; import { logBullet } from "../utils"; +import { EmulatorHub } from "../emulator/hub"; const homeDir = os.homedir(); @@ -147,7 +148,6 @@ ${[...featureNames] export const command = new Command("init [feature]") .description("interactively configure the current directory as a Firebase project directory") .help(HELP) - .before(requireAuth) .action(initAction); /** @@ -164,6 +164,9 @@ export async function initAction(feature: string, options: Options): Promise { setup.project = {}; + if (options.projectId === EmulatorHub.MISSING_PROJECT_PLACEHOLDER) { + logger.info(`Skipping Firebase project given --project=${options.projectId}`); + return; + } logger.info(); logger.info(`First, let's associate this project directory with a Firebase project.`); From b4d470ca320e08403a01f3eb0a61dcbea543218a Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Thu, 2 Oct 2025 19:38:10 -0700 Subject: [PATCH 02/13] compile --- firebase-vscode/src/core/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-vscode/src/core/index.ts b/firebase-vscode/src/core/index.ts index 3196cdc6270..94020481f26 100644 --- a/firebase-vscode/src/core/index.ts +++ b/firebase-vscode/src/core/index.ts @@ -15,7 +15,7 @@ import { registerWebhooks } from "./webhook"; import { createE2eMockable } from "../utils/test_hooks"; import { runTerminalTask } from "../data-connect/terminal"; import { AnalyticsLogger } from "../analytics"; -import { EmulatorHub } from "../emulator/hub"; +import { EmulatorHub } from "../../../src/emulator/hub"; export async function registerCore( broker: ExtensionBrokerImpl, From 7b5c744683fa02321652a7c496b049dbe3acbdc1 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Thu, 2 Oct 2025 19:43:37 -0700 Subject: [PATCH 03/13] changelog --- CHANGELOG.md | 1 + firebase-vscode/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45060095ac8..0059e21513e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Fix Functions MCP log tool to normalize sort order and surface Cloud Logging error details (#9247) +- `firebase init` support a way to skip project setup and login via `--project demo-no-project` (#9251) diff --git a/firebase-vscode/CHANGELOG.md b/firebase-vscode/CHANGELOG.md index a688cf5bb98..8ceb9b09527 100644 --- a/firebase-vscode/CHANGELOG.md +++ b/firebase-vscode/CHANGELOG.md @@ -1,6 +1,7 @@ ## NEXT - [Added] Refine / Generate Operation Code Lens. +- [] ## 1.8.0 From 86ff3a7b1ca24078d80633aabfdcdb1d427189bb Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Thu, 2 Oct 2025 19:49:23 -0700 Subject: [PATCH 04/13] m --- src/commands/init.ts | 5 ----- src/init/features/project.ts | 2 ++ 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index 77ba3f724cc..39409efcac6 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -8,7 +8,6 @@ import { getAllAccounts } from "../auth"; import { init, Setup } from "../init"; import { logger } from "../logger"; import { checkbox, confirm } from "../prompt"; -import { requireAuth } from "../requireAuth"; import * as fsutils from "../fsutils"; import * as utils from "../utils"; import { Options } from "../options"; @@ -16,7 +15,6 @@ import { isEnabled } from "../experiments"; import { readTemplateSync } from "../templates"; import { FirebaseError } from "../error"; import { logBullet } from "../utils"; -import { EmulatorHub } from "../emulator/hub"; const homeDir = os.homedir(); @@ -164,9 +162,6 @@ export async function initAction(feature: string, options: Options): Promise Date: Thu, 2 Oct 2025 20:25:57 -0700 Subject: [PATCH 05/13] Update src/init/features/project.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/init/features/project.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/init/features/project.ts b/src/init/features/project.ts index bb2dcf21620..502ea4208fa 100644 --- a/src/init/features/project.ts +++ b/src/init/features/project.ts @@ -102,8 +102,8 @@ async function projectChoicePrompt(options: any): Promise { setup.project = {}; - if (options.projectId === EmulatorHub.MISSING_PROJECT_PLACEHOLDER) { - logger.info(`Skipping Firebase project given --project=${options.projectId}`); + if (options.project === EmulatorHub.MISSING_PROJECT_PLACEHOLDER) { + logger.info(`Skipping Firebase project setup given --project=${options.project}`); return; } await requireAuth(options); From c1806076618fec19413670db39f078fb4eb807d6 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Fri, 3 Oct 2025 13:00:14 -0700 Subject: [PATCH 06/13] move auth after project options --- src/init/features/project.ts | 193 ++++++++++++++--------------------- src/management/projects.ts | 15 ++- 2 files changed, 89 insertions(+), 119 deletions(-) diff --git a/src/init/features/project.ts b/src/init/features/project.ts index 502ea4208fa..5b206d7009f 100644 --- a/src/init/features/project.ts +++ b/src/init/features/project.ts @@ -1,99 +1,26 @@ import * as clc from "colorette"; import * as _ from "lodash"; -import { FirebaseError } from "../../error"; import { addFirebaseToCloudProjectAndLog, createFirebaseProjectAndLog, getFirebaseProject, - getOrPromptProject, promptAvailableProjectId, promptProjectCreation, + selectProjectInteractively, } from "../../management/projects"; import { FirebaseProjectMetadata } from "../../types/project"; import { logger } from "../../logger"; import * as utils from "../../utils"; import * as prompt from "../../prompt"; -import { Options } from "../../options"; -import { EmulatorHub } from "../../emulator/hub"; import { requireAuth } from "../../requireAuth"; +import { Constants } from "../../emulator/constants"; const OPTION_NO_PROJECT = "Don't set up a default project"; const OPTION_USE_PROJECT = "Use an existing project"; const OPTION_NEW_PROJECT = "Create a new project"; const OPTION_ADD_FIREBASE = "Add Firebase to an existing Google Cloud Platform project"; -/** - * Used in init flows to keep information about the project - basically - * a shorter version of {@link FirebaseProjectMetadata} with some additional fields. - */ -export interface InitProjectInfo { - id: string; // maps to FirebaseProjectMetadata.projectId - label?: string; - instance?: string; // maps to FirebaseProjectMetadata.resources.realtimeDatabaseInstance - location?: string; // maps to FirebaseProjectMetadata.resources.locationId -} - -function toInitProjectInfo(projectMetaData: FirebaseProjectMetadata): InitProjectInfo { - const { projectId, displayName, resources } = projectMetaData; - return { - id: projectId, - label: `${projectId}` + (displayName ? ` (${displayName})` : ""), - instance: resources?.realtimeDatabaseInstance, - location: resources?.locationId, - }; -} - -async function promptAndCreateNewProject(options: Options): Promise { - utils.logBullet( - "If you want to create a project in a Google Cloud organization or folder, please use " + - `"firebase projects:create" instead, and return to this command when you've created the project.`, - ); - const { projectId, displayName } = await promptProjectCreation(options); - // N.B. This shouldn't be possible because of the validator on the input field, but it - // is being left around in case there's something I don't know. - if (!projectId) { - throw new FirebaseError("Project ID cannot be empty"); - } - - return await createFirebaseProjectAndLog(projectId, { displayName }); -} - -async function promptAndAddFirebaseToCloudProject(): Promise { - const projectId = await promptAvailableProjectId(); - if (!projectId) { - // N.B. This shouldn't be possible because of the validator on the input field, but it - // is being left around in case there's something I don't know. - throw new FirebaseError("Project ID cannot be empty"); - } - return await addFirebaseToCloudProjectAndLog(projectId); -} - -/** - * Prompt the user about how they would like to select a project. - * @param options the Firebase CLI options object. - * @return the project metadata, or undefined if no project was selected. - */ -async function projectChoicePrompt(options: any): Promise { - const choices = [OPTION_USE_PROJECT, OPTION_NEW_PROJECT, OPTION_ADD_FIREBASE, OPTION_NO_PROJECT]; - const projectSetupOption: string = await prompt.select<(typeof choices)[number]>({ - message: "Please select an option:", - choices, - }); - - switch (projectSetupOption) { - case OPTION_USE_PROJECT: - return getOrPromptProject(options); - case OPTION_NEW_PROJECT: - return promptAndCreateNewProject(options); - case OPTION_ADD_FIREBASE: - return promptAndAddFirebaseToCloudProject(); - default: - // Do nothing if user chooses NO_PROJECT - return; - } -} - /** * Sets up the default project if provided and writes .firebaserc file. * @param setup A helper object to use for the rest of the init features. @@ -102,63 +29,95 @@ async function projectChoicePrompt(options: any): Promise { setup.project = {}; - if (options.project === EmulatorHub.MISSING_PROJECT_PLACEHOLDER) { - logger.info(`Skipping Firebase project setup given --project=${options.project}`); - return; - } - await requireAuth(options); logger.info(); logger.info(`First, let's associate this project directory with a Firebase project.`); logger.info( `You can create multiple project aliases by running ${clc.bold("firebase use --add")}, `, ); - logger.info(`but for now we'll just set up a default project.`); logger.info(); + if (options.project) { + // If the user presented a project with `--project`, try to fetch that project. + if (Constants.isDemoProject(options.project)) { + logger.info(`Skipping Firebase project setup because a demo project is provided`); + return; + } + await requireAuth(options); + await usingProject(setup, config, options.project, "--project flag"); + return; + } const projectFromRcFile = setup.rcfile?.projects?.default; - if (projectFromRcFile && !options.project) { - utils.logBullet(`.firebaserc already has a default project, using ${projectFromRcFile}.`); - // we still need to get project info in case user wants to init firestore or storage, which - // require a resource location: - const rcProject: FirebaseProjectMetadata = await getFirebaseProject(projectFromRcFile); - setup.projectId = rcProject.projectId; - setup.projectLocation = rcProject?.resources?.locationId; + if (projectFromRcFile) { + await usingProject(setup, config, projectFromRcFile as string, ".firebaserc"); return; } - - let projectMetaData; - if (options.project) { - // If the user presented a project with `--project`, try to fetch that project. - logger.debug(`Using project from CLI flag: ${options.project}`); - projectMetaData = await getFirebaseProject(options.project); - } else { - const projectEnvVar = utils.envOverride("FIREBASE_PROJECT", ""); + const projectEnvVar = utils.envOverride("FIREBASE_PROJECT", ""); + if (projectEnvVar) { // If env var $FIREBASE_PROJECT is set, try to fetch that project. // This is used in some shell scripts e.g. under https://firebase.tools/. - if (projectEnvVar) { - logger.debug(`Using project from $FIREBASE_PROJECT: ${projectEnvVar}`); - projectMetaData = await getFirebaseProject(projectEnvVar); - } else { - if (options.nonInteractive) { - logger.info( - "No default project found. Continuing without a project in non interactive mode.", - ); - return; - } - projectMetaData = await projectChoicePrompt(options); - if (!projectMetaData) { - return; - } + await usingProject(setup, config, projectEnvVar, "$FIREBASE_PROJECT"); + return; + } + if (options.nonInteractive) { + logger.info("No default project found. Continuing without a project in non interactive mode."); + return; + } + + // Prompt users about how to setup a project. + const choices = [OPTION_USE_PROJECT, OPTION_NEW_PROJECT, OPTION_ADD_FIREBASE, OPTION_NO_PROJECT]; + const projectSetupOption: string = await prompt.select<(typeof choices)[number]>({ + message: "Please select an option:", + choices, + }); + switch (projectSetupOption) { + case OPTION_USE_PROJECT: { + await requireAuth(options); + const pm = await selectProjectInteractively(); + return await usingProjectMetadata(setup, config, pm); + } + case OPTION_NEW_PROJECT: { + utils.logBullet( + "If you want to create a project in a Google Cloud organization or folder, please use " + + `"firebase projects:create" instead, and return to this command when you've created the project.`, + ); + await requireAuth(options); + const { projectId, displayName } = await promptProjectCreation(options); + const pm = await createFirebaseProjectAndLog(projectId, { displayName }); + return await usingProjectMetadata(setup, config, pm); } + case OPTION_ADD_FIREBASE: { + await requireAuth(options); + const pm = await addFirebaseToCloudProjectAndLog(await promptAvailableProjectId()); + return await usingProjectMetadata(setup, config, pm); + } + default: + // Do nothing if user chooses NO_PROJECT + return; } +} + +async function usingProject( + setup: any, + config: any, + projectId: string, + from: string, +): Promise { + const pm = await getFirebaseProject(projectId); + const label = `${pm.projectId}` + (pm.displayName ? ` (${pm.displayName})` : ""); + utils.logBullet(`Using project ${label} from ${from}.`); + await usingProjectMetadata(setup, config, pm); +} - const projectInfo = toInitProjectInfo(projectMetaData); - utils.logBullet(`Using project ${projectInfo.label}`); +async function usingProjectMetadata( + setup: any, + config: any, + pm: FirebaseProjectMetadata, +): Promise { // write "default" alias and activate it immediately - _.set(setup.rcfile, "projects.default", projectInfo.id); - setup.projectId = projectInfo.id; - setup.instance = projectInfo.instance; - setup.projectLocation = projectInfo.location; - utils.makeActiveProject(config.projectDir, projectInfo.id); + _.set(setup.rcfile, "projects.default", pm.projectId); + setup.projectId = pm.projectId; + setup.instance = pm.resources?.realtimeDatabaseInstance; + setup.projectLocation = pm.resources?.locationId; + utils.makeActiveProject(config.projectDir, pm.projectId); } diff --git a/src/management/projects.ts b/src/management/projects.ts index a45ea60fd09..88748e69fe9 100644 --- a/src/management/projects.ts +++ b/src/management/projects.ts @@ -11,6 +11,7 @@ import * as utils from "../utils"; import { FirebaseProjectMetadata, CloudProjectInfo, ProjectPage } from "../types/project"; import { bestEffortEnsure } from "../ensureApiEnabled"; import { Options } from "../options"; +import { Constants } from "../emulator/constants"; const TIMEOUT_MILLIS = 30000; const MAXIMUM_PROMPT_LIST = 100; @@ -46,6 +47,9 @@ export async function promptProjectCreation( } else if (projectId.length > 30) { return "Project ID cannot be longer than 30 characters"; } + if (Constants.isDemoProject(projectId)) { + return "Project ID cannot starts with demo-"; + } try { // Best effort. We should still allow project creation even if this fails. @@ -172,7 +176,7 @@ export async function getOrPromptProject( return selectProjectInteractively(); } -async function selectProjectInteractively( +export async function selectProjectInteractively( pageSize: number = MAXIMUM_PROMPT_LIST, ): Promise { const { projects, nextPageToken } = await getFirebaseProjectPage(pageSize); @@ -252,9 +256,16 @@ export async function promptAvailableProjectId(): Promise { if (nextPageToken) { // Prompt for project ID if we can't list all projects in 1 page - return await prompt.input( + const projectId = await prompt.input( "Please input the ID of the Google Cloud Project you would like to add Firebase:", ); + if (!projectId) { + throw new FirebaseError("Project ID cannot be empty"); + } + if (Constants.isDemoProject(projectId)) { + throw new FirebaseError("Project ID cannot starts with demo-"); + } + return projectId; } else { const choices = projects .filter((p: CloudProjectInfo) => !!p) From 0ef83e2139cca2dd4ce3189ac5713fd1bacbf8ee Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Fri, 3 Oct 2025 13:01:42 -0700 Subject: [PATCH 07/13] move auth after project options --- src/init/features/project.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/init/features/project.ts b/src/init/features/project.ts index 5b206d7009f..7fcd3f9e731 100644 --- a/src/init/features/project.ts +++ b/src/init/features/project.ts @@ -49,6 +49,7 @@ export async function doSetup(setup: any, config: any, options: any): Promise Date: Fri, 3 Oct 2025 13:17:53 -0700 Subject: [PATCH 08/13] changelog --- firebase-vscode/CHANGELOG.md | 2 +- src/commands/init.ts | 1 + src/init/features/project.ts | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/firebase-vscode/CHANGELOG.md b/firebase-vscode/CHANGELOG.md index 8ceb9b09527..23afe5f27f8 100644 --- a/firebase-vscode/CHANGELOG.md +++ b/firebase-vscode/CHANGELOG.md @@ -1,7 +1,7 @@ ## NEXT - [Added] Refine / Generate Operation Code Lens. -- [] +- [Added] Support run "firebase init" without login and project. ## 1.8.0 diff --git a/src/commands/init.ts b/src/commands/init.ts index 39409efcac6..d0659443527 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -154,6 +154,7 @@ export const command = new Command("init [feature]") * @param options Command options */ export async function initAction(feature: string, options: Options): Promise { + console.log("options", options.project); if (feature && !featureNames.includes(feature)) { return utils.reject( clc.bold(feature) + diff --git a/src/init/features/project.ts b/src/init/features/project.ts index 7fcd3f9e731..97334b50d6d 100644 --- a/src/init/features/project.ts +++ b/src/init/features/project.ts @@ -44,7 +44,7 @@ export async function doSetup(setup: any, config: any, options: any): Promise { const pm = await getFirebaseProject(projectId); const label = `${pm.projectId}` + (pm.displayName ? ` (${pm.displayName})` : ""); - utils.logBullet(`Using project ${label} from ${from}.`); + utils.logBullet(`Using project ${label} ${from ? "from ${from}" : ""}.`); await usingProjectMetadata(setup, config, pm); } From fe8e0f0c95a5376156a5359786d3840f67759e75 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Fri, 3 Oct 2025 13:28:27 -0700 Subject: [PATCH 09/13] tests --- src/init/features/project.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/init/features/project.spec.ts b/src/init/features/project.spec.ts index 15a305d3c8e..8ad133c7f32 100644 --- a/src/init/features/project.spec.ts +++ b/src/init/features/project.spec.ts @@ -8,6 +8,7 @@ import * as projectManager from "../../management/projects"; import { Config } from "../../config"; import { FirebaseProjectMetadata } from "../../types/project"; import * as promptImport from "../../prompt"; +import * as requireAuthImport from "../../requireAuth"; const TEST_FIREBASE_PROJECT: FirebaseProjectMetadata = { projectId: "my-project-123", @@ -34,6 +35,7 @@ describe("project", () => { let emptyConfig: Config; beforeEach(() => { + sandbox.stub(requireAuthImport, "requireAuth").resolves(); getProjectStub = sandbox.stub(projectManager, "getFirebaseProject"); createFirebaseProjectStub = sandbox.stub(projectManager, "createFirebaseProjectAndLog"); getOrPromptProjectStub = sandbox.stub(projectManager, "getOrPromptProject"); From 4743e370b250e06323c75ad1fbf889f75476c536 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Fri, 3 Oct 2025 14:09:53 -0700 Subject: [PATCH 10/13] fix tests --- src/init/features/project.spec.ts | 42 +------------------------------ src/init/features/project.ts | 4 +++ src/management/projects.ts | 27 +++++++++----------- 3 files changed, 17 insertions(+), 56 deletions(-) diff --git a/src/init/features/project.spec.ts b/src/init/features/project.spec.ts index 8ad133c7f32..868a5e6afc9 100644 --- a/src/init/features/project.spec.ts +++ b/src/init/features/project.spec.ts @@ -96,26 +96,6 @@ describe("project", () => { displayName: "my-project", }); }); - - it("should throw if project ID is empty after prompt", async () => { - const options = {}; - const setup = { config: {}, rcfile: {} }; - prompt.select.onFirstCall().resolves("Create a new project"); - prompt.input.resolves(""); - configstoreSetStub.onFirstCall().resolves(); - - let err; - try { - await doSetup(setup, emptyConfig, options); - } catch (e: any) { - err = e; - } - - expect(err.message).to.equal("Project ID cannot be empty"); - expect(prompt.select).to.be.calledOnce; - expect(prompt.input).to.be.calledTwice; - expect(createFirebaseProjectStub).to.be.not.called; - }); }); describe('with "Add Firebase resources to GCP project" option', () => { @@ -139,27 +119,6 @@ describe("project", () => { expect(promptAvailableProjectIdStub).to.be.calledOnce; expect(addFirebaseProjectStub).to.be.calledOnceWith("my-project-123"); }); - - it("should throw if project ID is empty after prompt", async () => { - const options = {}; - const setup = { config: {}, rcfile: {} }; - prompt.select - .onFirstCall() - .resolves("Add Firebase to an existing Google Cloud Platform project"); - promptAvailableProjectIdStub.onFirstCall().resolves(""); - - let err; - try { - await doSetup(setup, emptyConfig, options); - } catch (e: any) { - err = e; - } - - expect(err.message).to.equal("Project ID cannot be empty"); - expect(prompt.select).to.be.calledOnce; - expect(promptAvailableProjectIdStub).to.be.calledOnce; - expect(addFirebaseProjectStub).to.be.not.called; - }); }); describe(`with "Don't set up a default project" option`, () => { @@ -183,6 +142,7 @@ describe("project", () => { options = {}; setup = { config: {}, rcfile: { projects: { default: "my-project-123" } } }; getProjectStub.onFirstCall().resolves(TEST_FIREBASE_PROJECT); + configstoreSetStub.onFirstCall().resolves(); }); it("should not prompt", async () => { diff --git a/src/init/features/project.ts b/src/init/features/project.ts index 97334b50d6d..1e7b6527f57 100644 --- a/src/init/features/project.ts +++ b/src/init/features/project.ts @@ -15,6 +15,7 @@ import * as utils from "../../utils"; import * as prompt from "../../prompt"; import { requireAuth } from "../../requireAuth"; import { Constants } from "../../emulator/constants"; +import { FirebaseError } from "../../error"; const OPTION_NO_PROJECT = "Don't set up a default project"; const OPTION_USE_PROJECT = "Use an existing project"; @@ -116,6 +117,9 @@ async function usingProjectMetadata( config: any, pm: FirebaseProjectMetadata, ): Promise { + if (!pm) { + throw new FirebaseError("null FirebaseProjectMetadata"); + } // write "default" alias and activate it immediately _.set(setup.rcfile, "projects.default", pm.projectId); setup.projectId = pm.projectId; diff --git a/src/management/projects.ts b/src/management/projects.ts index 88748e69fe9..65b465aa150 100644 --- a/src/management/projects.ts +++ b/src/management/projects.ts @@ -186,15 +186,20 @@ export async function selectProjectInteractively( if (nextPageToken) { // Prompt user for project ID if we can't list all projects in 1 page logger.debug(`Found more than ${projects.length} projects, selecting via prompt`); - return selectProjectByPrompting(); + return await getFirebaseProject(await selectProjectByPrompting()); } return selectProjectFromList(projects); } -async function selectProjectByPrompting(): Promise { +async function selectProjectByPrompting(): Promise { const projectId = await prompt.input("Please input the project ID you would like to use:"); - - return await getFirebaseProject(projectId); + if (!projectId) { + throw new FirebaseError("Project ID cannot be empty"); + } + if (Constants.isDemoProject(projectId)) { + throw new FirebaseError("Project ID cannot starts with demo-"); + } + return projectId; } /** @@ -255,17 +260,9 @@ export async function promptAvailableProjectId(): Promise { } if (nextPageToken) { - // Prompt for project ID if we can't list all projects in 1 page - const projectId = await prompt.input( - "Please input the ID of the Google Cloud Project you would like to add Firebase:", - ); - if (!projectId) { - throw new FirebaseError("Project ID cannot be empty"); - } - if (Constants.isDemoProject(projectId)) { - throw new FirebaseError("Project ID cannot starts with demo-"); - } - return projectId; + // Prompt user for project ID if we can't list all projects in 1 page + logger.debug(`Found more than ${projects.length} projects, selecting via prompt`); + return await selectProjectByPrompting(); } else { const choices = projects .filter((p: CloudProjectInfo) => !!p) From 2dd41c8e49483236c0b9d651776cd8611682d630 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Fri, 3 Oct 2025 14:15:27 -0700 Subject: [PATCH 11/13] m --- src/commands/init.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index d0659443527..39409efcac6 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -154,7 +154,6 @@ export const command = new Command("init [feature]") * @param options Command options */ export async function initAction(feature: string, options: Options): Promise { - console.log("options", options.project); if (feature && !featureNames.includes(feature)) { return utils.reject( clc.bold(feature) + From edbb6dfe4d766816320134a3b7fd7c260798fed9 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Mon, 6 Oct 2025 08:56:34 -0700 Subject: [PATCH 12/13] add metrics --- CHANGELOG.md | 2 +- firebase-vscode/src/analytics.ts | 1 + firebase-vscode/src/core/index.ts | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0059e21513e..785ab5980f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,2 @@ - Fix Functions MCP log tool to normalize sort order and surface Cloud Logging error details (#9247) -- `firebase init` support a way to skip project setup and login via `--project demo-no-project` (#9251) +- `firebase init` only requires `firebase login` when a valid project is passed. It accepts demo projects without login. (#9251) diff --git a/firebase-vscode/src/analytics.ts b/firebase-vscode/src/analytics.ts index 20c5aba4fb7..b65d8a0114f 100644 --- a/firebase-vscode/src/analytics.ts +++ b/firebase-vscode/src/analytics.ts @@ -27,6 +27,7 @@ export enum DATA_CONNECT_EVENT_NAME { MOVE_TO_CONNECTOR = "move_to_connector", START_EMULATOR_FROM_EXECUTION = "start_emulator_from_execution", REFUSE_START_EMULATOR_FROM_EXECUTION = "refuse_start_emulator_from_execution", + INIT = "init", INIT_SDK = "init_sdk", INIT_SDK_CLI = "init_sdk_cli", INIT_SDK_CODELENSE = "init_sdk_codelense", diff --git a/firebase-vscode/src/core/index.ts b/firebase-vscode/src/core/index.ts index 94020481f26..4032ecf5286 100644 --- a/firebase-vscode/src/core/index.ts +++ b/firebase-vscode/src/core/index.ts @@ -14,7 +14,7 @@ import { upsertFile } from "../data-connect/file-utils"; import { registerWebhooks } from "./webhook"; import { createE2eMockable } from "../utils/test_hooks"; import { runTerminalTask } from "../data-connect/terminal"; -import { AnalyticsLogger } from "../analytics"; +import { AnalyticsLogger, DATA_CONNECT_EVENT_NAME } from "../analytics"; import { EmulatorHub } from "../../../src/emulator/hub"; export async function registerCore( @@ -67,6 +67,7 @@ export async function registerCore( ); return; } + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.INIT); const projectId = currentProjectId.value || EmulatorHub.MISSING_PROJECT_PLACEHOLDER; const initCommand = `${settings.firebasePath} init dataconnect --project ${projectId}`; initSpy.call("firebase init", initCommand, { focus: true }); From 63658b42075035fc5c2ebab0a44ba9b6905bb695 Mon Sep 17 00:00:00 2001 From: Joe Hanley Date: Mon, 6 Oct 2025 10:00:43 -0700 Subject: [PATCH 13/13] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 785ab5980f0..44dcbdd780e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,2 @@ - Fix Functions MCP log tool to normalize sort order and surface Cloud Logging error details (#9247) -- `firebase init` only requires `firebase login` when a valid project is passed. It accepts demo projects without login. (#9251) +- Fixed an issue where `firebase init` would require log in even when no project is selected. (#9251)