Skip to content

Commit c180607

Browse files
committed
move auth after project options
1 parent 589be1e commit c180607

File tree

2 files changed

+89
-119
lines changed

2 files changed

+89
-119
lines changed

src/init/features/project.ts

Lines changed: 76 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,26 @@
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";
18-
import { EmulatorHub } from "../../emulator/hub";
1916
import { requireAuth } from "../../requireAuth";
17+
import { Constants } from "../../emulator/constants";
2018

2119
const OPTION_NO_PROJECT = "Don't set up a default project";
2220
const OPTION_USE_PROJECT = "Use an existing project";
2321
const OPTION_NEW_PROJECT = "Create a new project";
2422
const OPTION_ADD_FIREBASE = "Add Firebase to an existing Google Cloud Platform project";
2523

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

11133
logger.info();
11234
logger.info(`First, let's associate this project directory with a Firebase project.`);
11335
logger.info(
11436
`You can create multiple project aliases by running ${clc.bold("firebase use --add")}, `,
11537
);
116-
logger.info(`but for now we'll just set up a default project.`);
11738
logger.info();
11839

40+
if (options.project) {
41+
// If the user presented a project with `--project`, try to fetch that project.
42+
if (Constants.isDemoProject(options.project)) {
43+
logger.info(`Skipping Firebase project setup because a demo project is provided`);
44+
return;
45+
}
46+
await requireAuth(options);
47+
await usingProject(setup, config, options.project, "--project flag");
48+
return;
49+
}
11950
const projectFromRcFile = setup.rcfile?.projects?.default;
120-
if (projectFromRcFile && !options.project) {
121-
utils.logBullet(`.firebaserc already has a default project, using ${projectFromRcFile}.`);
122-
// we still need to get project info in case user wants to init firestore or storage, which
123-
// require a resource location:
124-
const rcProject: FirebaseProjectMetadata = await getFirebaseProject(projectFromRcFile);
125-
setup.projectId = rcProject.projectId;
126-
setup.projectLocation = rcProject?.resources?.locationId;
51+
if (projectFromRcFile) {
52+
await usingProject(setup, config, projectFromRcFile as string, ".firebaserc");
12753
return;
12854
}
129-
130-
let projectMetaData;
131-
if (options.project) {
132-
// If the user presented a project with `--project`, try to fetch that project.
133-
logger.debug(`Using project from CLI flag: ${options.project}`);
134-
projectMetaData = await getFirebaseProject(options.project);
135-
} else {
136-
const projectEnvVar = utils.envOverride("FIREBASE_PROJECT", "");
55+
const projectEnvVar = utils.envOverride("FIREBASE_PROJECT", "");
56+
if (projectEnvVar) {
13757
// If env var $FIREBASE_PROJECT is set, try to fetch that project.
13858
// This is used in some shell scripts e.g. under https://firebase.tools/.
139-
if (projectEnvVar) {
140-
logger.debug(`Using project from $FIREBASE_PROJECT: ${projectEnvVar}`);
141-
projectMetaData = await getFirebaseProject(projectEnvVar);
142-
} else {
143-
if (options.nonInteractive) {
144-
logger.info(
145-
"No default project found. Continuing without a project in non interactive mode.",
146-
);
147-
return;
148-
}
149-
projectMetaData = await projectChoicePrompt(options);
150-
if (!projectMetaData) {
151-
return;
152-
}
59+
await usingProject(setup, config, projectEnvVar, "$FIREBASE_PROJECT");
60+
return;
61+
}
62+
if (options.nonInteractive) {
63+
logger.info("No default project found. Continuing without a project in non interactive mode.");
64+
return;
65+
}
66+
67+
// Prompt users about how to setup a project.
68+
const choices = [OPTION_USE_PROJECT, OPTION_NEW_PROJECT, OPTION_ADD_FIREBASE, OPTION_NO_PROJECT];
69+
const projectSetupOption: string = await prompt.select<(typeof choices)[number]>({
70+
message: "Please select an option:",
71+
choices,
72+
});
73+
switch (projectSetupOption) {
74+
case OPTION_USE_PROJECT: {
75+
await requireAuth(options);
76+
const pm = await selectProjectInteractively();
77+
return await usingProjectMetadata(setup, config, pm);
78+
}
79+
case OPTION_NEW_PROJECT: {
80+
utils.logBullet(
81+
"If you want to create a project in a Google Cloud organization or folder, please use " +
82+
`"firebase projects:create" instead, and return to this command when you've created the project.`,
83+
);
84+
await requireAuth(options);
85+
const { projectId, displayName } = await promptProjectCreation(options);
86+
const pm = await createFirebaseProjectAndLog(projectId, { displayName });
87+
return await usingProjectMetadata(setup, config, pm);
15388
}
89+
case OPTION_ADD_FIREBASE: {
90+
await requireAuth(options);
91+
const pm = await addFirebaseToCloudProjectAndLog(await promptAvailableProjectId());
92+
return await usingProjectMetadata(setup, config, pm);
93+
}
94+
default:
95+
// Do nothing if user chooses NO_PROJECT
96+
return;
15497
}
98+
}
99+
100+
async function usingProject(
101+
setup: any,
102+
config: any,
103+
projectId: string,
104+
from: string,
105+
): Promise<void> {
106+
const pm = await getFirebaseProject(projectId);
107+
const label = `${pm.projectId}` + (pm.displayName ? ` (${pm.displayName})` : "");
108+
utils.logBullet(`Using project ${label} from ${from}.`);
109+
await usingProjectMetadata(setup, config, pm);
110+
}
155111

156-
const projectInfo = toInitProjectInfo(projectMetaData);
157-
utils.logBullet(`Using project ${projectInfo.label}`);
112+
async function usingProjectMetadata(
113+
setup: any,
114+
config: any,
115+
pm: FirebaseProjectMetadata,
116+
): Promise<void> {
158117
// write "default" alias and activate it immediately
159-
_.set(setup.rcfile, "projects.default", projectInfo.id);
160-
setup.projectId = projectInfo.id;
161-
setup.instance = projectInfo.instance;
162-
setup.projectLocation = projectInfo.location;
163-
utils.makeActiveProject(config.projectDir, projectInfo.id);
118+
_.set(setup.rcfile, "projects.default", pm.projectId);
119+
setup.projectId = pm.projectId;
120+
setup.instance = pm.resources?.realtimeDatabaseInstance;
121+
setup.projectLocation = pm.resources?.locationId;
122+
utils.makeActiveProject(config.projectDir, pm.projectId);
164123
}

src/management/projects.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as utils from "../utils";
1111
import { FirebaseProjectMetadata, CloudProjectInfo, ProjectPage } from "../types/project";
1212
import { bestEffortEnsure } from "../ensureApiEnabled";
1313
import { Options } from "../options";
14+
import { Constants } from "../emulator/constants";
1415

1516
const TIMEOUT_MILLIS = 30000;
1617
const MAXIMUM_PROMPT_LIST = 100;
@@ -46,6 +47,9 @@ export async function promptProjectCreation(
4647
} else if (projectId.length > 30) {
4748
return "Project ID cannot be longer than 30 characters";
4849
}
50+
if (Constants.isDemoProject(projectId)) {
51+
return "Project ID cannot starts with demo-";
52+
}
4953

5054
try {
5155
// Best effort. We should still allow project creation even if this fails.
@@ -172,7 +176,7 @@ export async function getOrPromptProject(
172176
return selectProjectInteractively();
173177
}
174178

175-
async function selectProjectInteractively(
179+
export async function selectProjectInteractively(
176180
pageSize: number = MAXIMUM_PROMPT_LIST,
177181
): Promise<FirebaseProjectMetadata> {
178182
const { projects, nextPageToken } = await getFirebaseProjectPage(pageSize);
@@ -252,9 +256,16 @@ export async function promptAvailableProjectId(): Promise<string> {
252256

253257
if (nextPageToken) {
254258
// Prompt for project ID if we can't list all projects in 1 page
255-
return await prompt.input(
259+
const projectId = await prompt.input(
256260
"Please input the ID of the Google Cloud Project you would like to add Firebase:",
257261
);
262+
if (!projectId) {
263+
throw new FirebaseError("Project ID cannot be empty");
264+
}
265+
if (Constants.isDemoProject(projectId)) {
266+
throw new FirebaseError("Project ID cannot starts with demo-");
267+
}
268+
return projectId;
258269
} else {
259270
const choices = projects
260271
.filter((p: CloudProjectInfo) => !!p)

0 commit comments

Comments
 (0)