Skip to content

Commit 081a4bb

Browse files
committed
merge
1 parent d00e11f commit 081a4bb

File tree

9 files changed

+118
-176
lines changed

9 files changed

+118
-176
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
- Fix Functions MCP log tool to normalize sort order and surface Cloud Logging error details (#9247)
2+
- `firebase init` only requires `firebase login` when a valid project is passed. It accepts demo projects without login. (#9251)

firebase-vscode/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## NEXT
22

33
- [Added] Refine / Generate Operation Code Lens.
4+
- [Added] Support run "firebase init" without login and project.
45

56
## 1.8.0
67

firebase-vscode/src/analytics.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export enum DATA_CONNECT_EVENT_NAME {
2727
MOVE_TO_CONNECTOR = "move_to_connector",
2828
START_EMULATOR_FROM_EXECUTION = "start_emulator_from_execution",
2929
REFUSE_START_EMULATOR_FROM_EXECUTION = "refuse_start_emulator_from_execution",
30+
INIT = "init",
3031
INIT_SDK = "init_sdk",
3132
INIT_SDK_CLI = "init_sdk_cli",
3233
INIT_SDK_CODELENSE = "init_sdk_codelense",

firebase-vscode/src/core/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import { upsertFile } from "../data-connect/file-utils";
1414
import { registerWebhooks } from "./webhook";
1515
import { createE2eMockable } from "../utils/test_hooks";
1616
import { runTerminalTask } from "../data-connect/terminal";
17-
import { AnalyticsLogger } from "../analytics";
17+
import { AnalyticsLogger, DATA_CONNECT_EVENT_NAME } from "../analytics";
18+
import { EmulatorHub } from "../../../src/emulator/hub";
1819

1920
export async function registerCore(
2021
broker: ExtensionBrokerImpl,
@@ -66,10 +67,9 @@ export async function registerCore(
6667
);
6768
return;
6869
}
69-
const initCommand = currentProjectId.value
70-
? `${settings.firebasePath} init dataconnect --project ${currentProjectId.value}`
71-
: `${settings.firebasePath} init dataconnect`;
72-
70+
analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.INIT);
71+
const projectId = currentProjectId.value || EmulatorHub.MISSING_PROJECT_PLACEHOLDER;
72+
const initCommand = `${settings.firebasePath} init dataconnect --project ${projectId}`;
7373
initSpy.call("firebase init", initCommand, { focus: true });
7474
});
7575

firebase-vscode/webviews/SidebarApp.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -277,14 +277,13 @@ export function SidebarApp() {
277277
<ConfigPicker />
278278
</PanelSection>
279279

280-
{user.value &&
281-
(isInitialized.value ? (
282-
<Content />
283-
) : (
284-
<PanelSection isLast={true}>
285-
<Welcome />
286-
</PanelSection>
287-
))}
280+
{isInitialized.value ? (
281+
<Content />
282+
) : (
283+
<PanelSection isLast={true}>
284+
<Welcome />
285+
</PanelSection>
286+
)}
288287
</App>
289288
);
290289
}

src/commands/init.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { getAllAccounts } from "../auth";
88
import { init, Setup } from "../init";
99
import { logger } from "../logger";
1010
import { checkbox, confirm } from "../prompt";
11-
import { requireAuth } from "../requireAuth";
1211
import * as fsutils from "../fsutils";
1312
import * as utils from "../utils";
1413
import { Options } from "../options";
@@ -147,7 +146,6 @@ ${[...featureNames]
147146
export const command = new Command("init [feature]")
148147
.description("interactively configure the current directory as a Firebase project directory")
149148
.help(HELP)
150-
.before(requireAuth)
151149
.action(initAction);
152150

153151
/**

src/init/features/project.spec.ts

Lines changed: 3 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as projectManager from "../../management/projects";
88
import { Config } from "../../config";
99
import { FirebaseProjectMetadata } from "../../types/project";
1010
import * as promptImport from "../../prompt";
11+
import * as requireAuthImport from "../../requireAuth";
1112

1213
const TEST_FIREBASE_PROJECT: FirebaseProjectMetadata = {
1314
projectId: "my-project-123",
@@ -34,6 +35,7 @@ describe("project", () => {
3435
let emptyConfig: Config;
3536

3637
beforeEach(() => {
38+
sandbox.stub(requireAuthImport, "requireAuth").resolves();
3739
getProjectStub = sandbox.stub(projectManager, "getFirebaseProject");
3840
createFirebaseProjectStub = sandbox.stub(projectManager, "createFirebaseProjectAndLog");
3941
getOrPromptProjectStub = sandbox.stub(projectManager, "getOrPromptProject");
@@ -94,26 +96,6 @@ describe("project", () => {
9496
displayName: "my-project",
9597
});
9698
});
97-
98-
it("should throw if project ID is empty after prompt", async () => {
99-
const options = {};
100-
const setup = { config: {}, rcfile: {} };
101-
prompt.select.onFirstCall().resolves("Create a new project");
102-
prompt.input.resolves("");
103-
configstoreSetStub.onFirstCall().resolves();
104-
105-
let err;
106-
try {
107-
await doSetup(setup, emptyConfig, options);
108-
} catch (e: any) {
109-
err = e;
110-
}
111-
112-
expect(err.message).to.equal("Project ID cannot be empty");
113-
expect(prompt.select).to.be.calledOnce;
114-
expect(prompt.input).to.be.calledTwice;
115-
expect(createFirebaseProjectStub).to.be.not.called;
116-
});
11799
});
118100

119101
describe('with "Add Firebase resources to GCP project" option', () => {
@@ -137,27 +119,6 @@ describe("project", () => {
137119
expect(promptAvailableProjectIdStub).to.be.calledOnce;
138120
expect(addFirebaseProjectStub).to.be.calledOnceWith("my-project-123");
139121
});
140-
141-
it("should throw if project ID is empty after prompt", async () => {
142-
const options = {};
143-
const setup = { config: {}, rcfile: {} };
144-
prompt.select
145-
.onFirstCall()
146-
.resolves("Add Firebase to an existing Google Cloud Platform project");
147-
promptAvailableProjectIdStub.onFirstCall().resolves("");
148-
149-
let err;
150-
try {
151-
await doSetup(setup, emptyConfig, options);
152-
} catch (e: any) {
153-
err = e;
154-
}
155-
156-
expect(err.message).to.equal("Project ID cannot be empty");
157-
expect(prompt.select).to.be.calledOnce;
158-
expect(promptAvailableProjectIdStub).to.be.calledOnce;
159-
expect(addFirebaseProjectStub).to.be.not.called;
160-
});
161122
});
162123

163124
describe(`with "Don't set up a default project" option`, () => {
@@ -181,6 +142,7 @@ describe("project", () => {
181142
options = {};
182143
setup = { config: {}, rcfile: { projects: { default: "my-project-123" } } };
183144
getProjectStub.onFirstCall().resolves(TEST_FIREBASE_PROJECT);
145+
configstoreSetStub.onFirstCall().resolves();
184146
});
185147

186148
it("should not prompt", async () => {

src/init/features/project.ts

Lines changed: 83 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,27 @@
11
import * as clc from "colorette";
22
import * as _ from "lodash";
33

4-
import { FirebaseError } from "../../error";
54
import {
65
addFirebaseToCloudProjectAndLog,
76
createFirebaseProjectAndLog,
87
getFirebaseProject,
9-
getOrPromptProject,
108
promptAvailableProjectId,
119
promptProjectCreation,
10+
selectProjectInteractively,
1211
} from "../../management/projects";
1312
import { FirebaseProjectMetadata } from "../../types/project";
1413
import { logger } from "../../logger";
1514
import * as utils from "../../utils";
1615
import * as prompt from "../../prompt";
17-
import { Options } from "../../options";
16+
import { requireAuth } from "../../requireAuth";
17+
import { Constants } from "../../emulator/constants";
18+
import { FirebaseError } from "../../error";
1819

1920
const OPTION_NO_PROJECT = "Don't set up a default project";
2021
const OPTION_USE_PROJECT = "Use an existing project";
2122
const OPTION_NEW_PROJECT = "Create a new project";
2223
const OPTION_ADD_FIREBASE = "Add Firebase to an existing Google Cloud Platform project";
2324

24-
/**
25-
* Used in init flows to keep information about the project - basically
26-
* a shorter version of {@link FirebaseProjectMetadata} with some additional fields.
27-
*/
28-
export interface InitProjectInfo {
29-
id: string; // maps to FirebaseProjectMetadata.projectId
30-
label?: string;
31-
instance?: string; // maps to FirebaseProjectMetadata.resources.realtimeDatabaseInstance
32-
location?: string; // maps to FirebaseProjectMetadata.resources.locationId
33-
}
34-
35-
function toInitProjectInfo(projectMetaData: FirebaseProjectMetadata): InitProjectInfo {
36-
const { projectId, displayName, resources } = projectMetaData;
37-
return {
38-
id: projectId,
39-
label: `${projectId}` + (displayName ? ` (${displayName})` : ""),
40-
instance: resources?.realtimeDatabaseInstance,
41-
location: resources?.locationId,
42-
};
43-
}
44-
45-
async function promptAndCreateNewProject(options: Options): Promise<FirebaseProjectMetadata> {
46-
utils.logBullet(
47-
"If you want to create a project in a Google Cloud organization or folder, please use " +
48-
`"firebase projects:create" instead, and return to this command when you've created the project.`,
49-
);
50-
const { projectId, displayName } = await promptProjectCreation(options);
51-
// N.B. This shouldn't be possible because of the validator on the input field, but it
52-
// is being left around in case there's something I don't know.
53-
if (!projectId) {
54-
throw new FirebaseError("Project ID cannot be empty");
55-
}
56-
57-
return await createFirebaseProjectAndLog(projectId, { displayName });
58-
}
59-
60-
async function promptAndAddFirebaseToCloudProject(): Promise<FirebaseProjectMetadata> {
61-
const projectId = await promptAvailableProjectId();
62-
if (!projectId) {
63-
// N.B. This shouldn't be possible because of the validator on the input field, but it
64-
// is being left around in case there's something I don't know.
65-
throw new FirebaseError("Project ID cannot be empty");
66-
}
67-
return await addFirebaseToCloudProjectAndLog(projectId);
68-
}
69-
70-
/**
71-
* Prompt the user about how they would like to select a project.
72-
* @param options the Firebase CLI options object.
73-
* @return the project metadata, or undefined if no project was selected.
74-
*/
75-
async function projectChoicePrompt(options: any): Promise<FirebaseProjectMetadata | undefined> {
76-
const choices = [OPTION_USE_PROJECT, OPTION_NEW_PROJECT, OPTION_ADD_FIREBASE, OPTION_NO_PROJECT];
77-
const projectSetupOption: string = await prompt.select<(typeof choices)[number]>({
78-
message: "Please select an option:",
79-
choices,
80-
});
81-
82-
switch (projectSetupOption) {
83-
case OPTION_USE_PROJECT:
84-
return getOrPromptProject(options);
85-
case OPTION_NEW_PROJECT:
86-
return promptAndCreateNewProject(options);
87-
case OPTION_ADD_FIREBASE:
88-
return promptAndAddFirebaseToCloudProject();
89-
default:
90-
// Do nothing if user chooses NO_PROJECT
91-
return;
92-
}
93-
}
94-
9525
/**
9626
* Sets up the default project if provided and writes .firebaserc file.
9727
* @param setup A helper object to use for the rest of the init features.
@@ -106,52 +36,94 @@ export async function doSetup(setup: any, config: any, options: any): Promise<vo
10636
logger.info(
10737
`You can create multiple project aliases by running ${clc.bold("firebase use --add")}, `,
10838
);
109-
logger.info(`but for now we'll just set up a default project.`);
11039
logger.info();
11140

41+
if (options.project) {
42+
// If the user presented a project with `--project`, try to fetch that project.
43+
if (Constants.isDemoProject(options.project)) {
44+
logger.info(`Skipping Firebase project setup because a demo project is provided`);
45+
return;
46+
}
47+
await requireAuth(options);
48+
await usingProject(setup, config, options.project);
49+
return;
50+
}
11251
const projectFromRcFile = setup.rcfile?.projects?.default;
113-
if (projectFromRcFile && !options.project) {
114-
utils.logBullet(`.firebaserc already has a default project, using ${projectFromRcFile}.`);
115-
// we still need to get project info in case user wants to init firestore or storage, which
116-
// require a resource location:
117-
const rcProject: FirebaseProjectMetadata = await getFirebaseProject(projectFromRcFile);
118-
setup.projectId = rcProject.projectId;
119-
setup.projectLocation = rcProject?.resources?.locationId;
52+
if (projectFromRcFile) {
53+
await requireAuth(options);
54+
await usingProject(setup, config, projectFromRcFile as string, ".firebaserc");
12055
return;
12156
}
122-
123-
let projectMetaData;
124-
if (options.project) {
125-
// If the user presented a project with `--project`, try to fetch that project.
126-
logger.debug(`Using project from CLI flag: ${options.project}`);
127-
projectMetaData = await getFirebaseProject(options.project);
128-
} else {
129-
const projectEnvVar = utils.envOverride("FIREBASE_PROJECT", "");
57+
const projectEnvVar = utils.envOverride("FIREBASE_PROJECT", "");
58+
if (projectEnvVar) {
13059
// If env var $FIREBASE_PROJECT is set, try to fetch that project.
13160
// This is used in some shell scripts e.g. under https://firebase.tools/.
132-
if (projectEnvVar) {
133-
logger.debug(`Using project from $FIREBASE_PROJECT: ${projectEnvVar}`);
134-
projectMetaData = await getFirebaseProject(projectEnvVar);
135-
} else {
136-
if (options.nonInteractive) {
137-
logger.info(
138-
"No default project found. Continuing without a project in non interactive mode.",
139-
);
140-
return;
141-
}
142-
projectMetaData = await projectChoicePrompt(options);
143-
if (!projectMetaData) {
144-
return;
145-
}
61+
await requireAuth(options);
62+
await usingProject(setup, config, projectEnvVar, "$FIREBASE_PROJECT");
63+
return;
64+
}
65+
if (options.nonInteractive) {
66+
logger.info("No default project found. Continuing without a project in non interactive mode.");
67+
return;
68+
}
69+
70+
// Prompt users about how to setup a project.
71+
const choices = [OPTION_USE_PROJECT, OPTION_NEW_PROJECT, OPTION_ADD_FIREBASE, OPTION_NO_PROJECT];
72+
const projectSetupOption: string = await prompt.select<(typeof choices)[number]>({
73+
message: "Please select an option:",
74+
choices,
75+
});
76+
switch (projectSetupOption) {
77+
case OPTION_USE_PROJECT: {
78+
await requireAuth(options);
79+
const pm = await selectProjectInteractively();
80+
return await usingProjectMetadata(setup, config, pm);
81+
}
82+
case OPTION_NEW_PROJECT: {
83+
utils.logBullet(
84+
"If you want to create a project in a Google Cloud organization or folder, please use " +
85+
`"firebase projects:create" instead, and return to this command when you've created the project.`,
86+
);
87+
await requireAuth(options);
88+
const { projectId, displayName } = await promptProjectCreation(options);
89+
const pm = await createFirebaseProjectAndLog(projectId, { displayName });
90+
return await usingProjectMetadata(setup, config, pm);
91+
}
92+
case OPTION_ADD_FIREBASE: {
93+
await requireAuth(options);
94+
const pm = await addFirebaseToCloudProjectAndLog(await promptAvailableProjectId());
95+
return await usingProjectMetadata(setup, config, pm);
14696
}
97+
default:
98+
// Do nothing if user chooses NO_PROJECT
99+
return;
147100
}
101+
}
148102

149-
const projectInfo = toInitProjectInfo(projectMetaData);
150-
utils.logBullet(`Using project ${projectInfo.label}`);
103+
async function usingProject(
104+
setup: any,
105+
config: any,
106+
projectId: string,
107+
from: string = "",
108+
): Promise<void> {
109+
const pm = await getFirebaseProject(projectId);
110+
const label = `${pm.projectId}` + (pm.displayName ? ` (${pm.displayName})` : "");
111+
utils.logBullet(`Using project ${label} ${from ? "from ${from}" : ""}.`);
112+
await usingProjectMetadata(setup, config, pm);
113+
}
114+
115+
async function usingProjectMetadata(
116+
setup: any,
117+
config: any,
118+
pm: FirebaseProjectMetadata,
119+
): Promise<void> {
120+
if (!pm) {
121+
throw new FirebaseError("null FirebaseProjectMetadata");
122+
}
151123
// write "default" alias and activate it immediately
152-
_.set(setup.rcfile, "projects.default", projectInfo.id);
153-
setup.projectId = projectInfo.id;
154-
setup.instance = projectInfo.instance;
155-
setup.projectLocation = projectInfo.location;
156-
utils.makeActiveProject(config.projectDir, projectInfo.id);
124+
_.set(setup.rcfile, "projects.default", pm.projectId);
125+
setup.projectId = pm.projectId;
126+
setup.instance = pm.resources?.realtimeDatabaseInstance;
127+
setup.projectLocation = pm.resources?.locationId;
128+
utils.makeActiveProject(config.projectDir, pm.projectId);
157129
}

0 commit comments

Comments
 (0)