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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
- Added validation to check if project ID exists during project creation. (#5233)
- Add `generate_dataconnect_schema`, `dataconnect_generate_operation`, `firebase_consult_assistant` MCP tools. (#8647)
- `firebase init dataconnect` is now integrated with Gemini in Firebase API to generate Schema based on description. (#8596)
54 changes: 51 additions & 3 deletions src/management/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
const MAXIMUM_PROMPT_LIST = 100;
const PROJECT_LIST_PAGE_SIZE = 1000;
const CREATE_PROJECT_API_REQUEST_TIMEOUT_MILLIS = 15000;
const CHECK_PROJECT_ID_API_REQUEST_TIMEOUT_MILLIS = 15000;

export enum ProjectParentResourceType {
ORGANIZATION = "organization",
Expand All @@ -39,14 +40,26 @@
message:
"Please specify a unique project id " +
`(${clc.yellow("warning")}: cannot be modified afterward) [6-30 characters]:\n`,
validate: (projectId: string) => {
validate: async (projectId: string) => {
if (projectId.length < 6) {
return "Project ID must be at least 6 characters long";
} else if (projectId.length > 30) {
return "Project ID cannot be longer than 30 characters";
} else {
return true;
}

try {
// Best effort. We should still allow project creation even if this fails.
const { isAvailable, suggestedProjectId } = await checkAndRecommendProjectId(projectId);
if (!isAvailable && suggestedProjectId) {
return `Project ID is taken or unavailable. Try ${clc.bold(suggestedProjectId)}.`;
}
} catch (error: any) {

Check warning on line 56 in src/management/projects.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
logger.debug(
`Couldn't check if project ID ${projectId} is available. Original error: ${error}`,

Check warning on line 58 in src/management/projects.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Invalid type "any" of template literal expression
);
}

return true;
},
}));

Expand Down Expand Up @@ -75,6 +88,12 @@
apiVersion: "v1beta1",
});

const firebaseV1APIClient = new Client({
urlPrefix: api.firebaseApiOrigin(),
auth: true,
apiVersion: "v1",
});

const resourceManagerClient = new Client({
urlPrefix: api.resourceManagerOrigin(),
apiVersion: "v1",
Expand All @@ -92,7 +111,7 @@
try {
await createCloudProject(projectId, options);
spinner.succeed();
} catch (err: any) {

Check warning on line 114 in src/management/projects.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
spinner.fail();
throw err;
}
Expand All @@ -111,7 +130,7 @@

try {
projectInfo = await addFirebaseToCloudProject(projectId);
} catch (err: any) {

Check warning on line 133 in src/management/projects.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
spinner.fail();
throw err;
}
Expand Down Expand Up @@ -262,27 +281,27 @@
export async function createCloudProject(
projectId: string,
options: { displayName?: string; parentResource?: ProjectParentResource },
): Promise<any> {

Check warning on line 284 in src/management/projects.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
try {
const data = {
projectId,
name: options.displayName || projectId,
parent: options.parentResource,
};
const response = await resourceManagerClient.request<any, { name: string }>({

Check warning on line 291 in src/management/projects.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
method: "POST",
path: "/projects",
body: data,
timeout: CREATE_PROJECT_API_REQUEST_TIMEOUT_MILLIS,
});
const projectInfo = await pollOperation<any>({

Check warning on line 297 in src/management/projects.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type

Check warning on line 297 in src/management/projects.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe assignment of an `any` value
pollerName: "Project Creation Poller",
apiOrigin: api.resourceManagerOrigin(),
apiVersion: "v1",
operationResourceName: response.body.name /* LRO resource name */,
});
return projectInfo;

Check warning on line 303 in src/management/projects.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe return of an `any` typed value
} catch (err: any) {

Check warning on line 304 in src/management/projects.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
if (err.status === 409) {
throw new FirebaseError(
`Failed to create project because there is already a project with ID ${clc.bold(
Expand Down Expand Up @@ -432,6 +451,35 @@
return projects;
}

export async function checkAndRecommendProjectId(
projectId: String,
): Promise<{ isAvailable: boolean; suggestedProjectId?: string }> {
try {
const res = await firebaseV1APIClient.request<
any,
{ projectIdStatus: string; suggestedProjectId?: string }
>({
method: "POST",
path: "/projects:checkProjectId",
body: {
proposedId: projectId,
},
timeout: CHECK_PROJECT_ID_API_REQUEST_TIMEOUT_MILLIS,
});

const { projectIdStatus, suggestedProjectId } = res.body;
return {
isAvailable: projectIdStatus === "PROJECT_ID_AVAILABLE",
suggestedProjectId,
};
} catch (err: any) {
throw new FirebaseError(
"Failed to check if project ID is available. See firebase-debug.log for more info.",
{ exit: 2, original: err },
);
}
}

/**
* Gets the Firebase project information identified by the specified project ID
*/
Expand Down
Loading