Repository: firebase/firebase-tools (ts)

Issue:
Firebase init issues for new project without location
<!-- DO NOT DELETE
validate_template=true
template_path=.github/ISSUE_TEMPLATE/bug_report.md
-->

<!--
Thank you for contributing to the Firebase community!

Think you found a bug?
=======================
Yeah, we're definitely not perfect! Please use this template and include a minimal repro when opening the issue. If you know how to solve the issue, please create a Pull Request, and we'd be happy to review it!

Have a feature request?
========================
Great, we love hearing how we can improve our products! However, do not use this template to submit a feature request. Please submit your feature requests to: https://firebase.google.com/support/contact/bugs-features/

Have a usage question?
=======================
We get lots of those and we love helping you, but GitHub is not the best place for them and they will be closed. Please take a look at the guide first: https://firebase.google.com/docs/cli/

If the official documentation doesn't help, try asking through our official support channel: https://firebase.google.com/support/

Additional locations to check for solutions or assistance from the community:
- Stack Overflow: https://stackoverflow.com/
- Firebase Slack Community: https://firebase.community/

*Please avoid duplicate posting across multiple channels!*
-->

### [REQUIRED] Environment info

<!-- What version of the Firebase CLI (`firebase-tools`) are you using? Note that your issue may already be fixed in the latest versions. The latest version can be found at https://github.com/firebase/firebase-tools/releases -->

firebase --version
8.3.0

**firebase-tools:**

Windows 10

**Platform:**

### [REQUIRED] Test case

firebase init fails with GCP location setting. 

### [REQUIRED] Steps to reproduce

firebase login
firebase init

### [REQUIRED] Expected behavior

firebase CLI tool should not fail
firebase CLI tool should not require user to stop mid process, google, figure out some settings in web console are missing (location) and restart whole proces

### [REQUIRED] Actual behavior

[error] Error: Failed to create Google Cloud project. See firebase-debug.log for more info.

Error: Cloud resource location is not set for this project but the operation you are attempting to perform in Cloud Firestore requires it. Please see this documentation for more details: https://firebase.google.com/docs/projects/locations

And its not documented 
https://github.com/firebase/firebase-tools/issues/1808#issuecomment-560403943
(https://firebase.google.com/docs/projects/locations), otherwise firebase init always fail for us with : Error: Cloud resource location is not set for this project but the operation you are attempting to perform in Cloud Firestore requires it. Please see this documentation for more details: https://firebase.google.com/docs/projects/locations

Pull request:
Improve error messages
<!--

Thank you for contributing to the Firebase community! Please fill out the form below.

Run the linter and test suite
==============================
Run `npm test` to make sure your changes compile properly and the tests all pass on your local machine. We've hooked up this repo with continuous integration to double check those things for you.

-->

### Description

Fixes #2266 

@ev45ive curious what you think about this

### Scenarios Tested

#### Create new project

**Before**

```
$ firebase init
=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add, but for now we'll just set up a default project.

? Please select an option: Create a new project
i  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.
? Please specify a unique project id (warning: cannot be modified afterward) [6-30 characters]: fir-dumpster
? What would you like to call your project? (defaults to your project ID) fir-dumpster
✖ Creating Google Cloud Platform project

Error: Failed to create Google Cloud project. See firebase-debug.log for more info.
```

**After**

```
$ firebase init
=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add, but for now we'll just set up a default project.

? Please select an option: Create a new project
i  If you want to create a project in a Google Cloud organization or folder, please use "firebase pr
ojects:create" instead, and return to this command when you've created the project.
? Please specify a unique project id (warning: can not be modified afterward) [6-30 characters]: fir-dumpster
? What would you like to call your project? (defaults to your project ID) fir-dumpster
✖ Creating Google Cloud Platform project

Error: Failed to create Google Cloud project because there is already a project with ID fir-dumpster. Please try again with a unique Project ID.
```

#### init storage / firestore

**Before**

```
$ firebase --project=totally-empty-project init storage 
=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add, 
but for now we'll just set up a default project.

i  Using project totally-empty-project (totally-empty-project)

=== Storage Setup

Error: Cloud resource location is not set for this project but the operation you are attempting to perform in Cloud Storage requires it. Please see this documentation for more details: https://firebase.google.com/docs/projects/locations
```

**After**

```
samstern@samstern-macbookpro3 tmp.v19v6K4v 
$ firebase --project=totally-empty-project init storage 
=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add, 
but for now we'll just set up a default project.

i  Using project totally-empty-project (totally-empty-project)

=== Storage Setup

Error: It looks like you haven't used Cloud Storage in this project before. Go to https://console.firebase.google.com/project/totally-empty-project/storage to create your Storage bucket.

Having trouble? Try firebase [command] --help
```

### Sample Commands

N/A

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index df5e53910..b6c957670 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,3 +2,4 @@
 - Fixes issue where Emulator UI was never downloaded during `firebase init emulators`.
 - Skip app selection in `firebase apps:sdkconfig` if there is a single matching app.
 - Fixes incorrect warning about emulator configuration (#2290).
+- Improves error messages for `firebase init` with new projects (#2266).
diff --git a/src/init/features/firestore/index.ts b/src/init/features/firestore/index.ts
index cd7b18dab..ce246dd79 100644
--- a/src/init/features/firestore/index.ts
+++ b/src/init/features/firestore/index.ts
@@ -1,11 +1,29 @@
+import * as apiEnabled from "../../../ensureApiEnabled";
 import { ensureLocationSet } from "../../../ensureCloudResourceLocation";
 import { requirePermissions } from "../../../requirePermissions";
 import * as rules from "./rules";
 import * as indexes from "./indexes";
+import { FirebaseError } from "../../../error";
+
+import * as clc from "cli-color";
 
 export async function doSetup(setup: any, config: any): Promise<void> {
-  setup.config.firestore = {};
+  const isFirestoreEnabled = await apiEnabled.check(
+    setup.projectId,
+    "firestore.googleapis.com",
+    "",
+    true
+  );
+  if (!isFirestoreEnabled) {
+    throw new FirebaseError(
+      `It looks like you haven't used Cloud Firestore in this project before. Go to ${clc.bold.underline(
+        `https://console.firebase.google.com/project/${setup.projectId}/database`
+      )} to create your Cloud Firestore database.`,
+      { exit: 1 }
+    );
+  }
 
+  setup.config.firestore = {};
   ensureLocationSet(setup.projectLocation, "Cloud Firestore");
   await requirePermissions({ project: setup.projectId });
   await rules.initRules(setup, config);
diff --git a/src/init/features/storage.ts b/src/init/features/storage.ts
index e9c5914f8..6b16f6c82 100644
--- a/src/init/features/storage.ts
+++ b/src/init/features/storage.ts
@@ -1,9 +1,11 @@
 import * as clc from "cli-color";
 import * as fs from "fs";
 
+import * as apiEnabled from "../../ensureApiEnabled";
 import * as logger from "../../logger";
 import { promptOnce } from "../../prompt";
 import { ensureLocationSet } from "../../ensureCloudResourceLocation";
+import { FirebaseError } from "../../error";
 
 const RULES_TEMPLATE = fs.readFileSync(
   __dirname + "/../../../templates/init/storage/storage.rules",
@@ -11,6 +13,21 @@ const RULES_TEMPLATE = fs.readFileSync(
 );
 
 export async function doSetup(setup: any, config: any): Promise<void> {
+  const isStorageInabled = await apiEnabled.check(
+    setup.projectId,
+    "firebasestorage.googleapis.com",
+    "",
+    true
+  );
+  if (!isStorageInabled) {
+    throw new FirebaseError(
+      `It looks like you haven't used Cloud Storage in this project before. Go to ${clc.bold.underline(
+        `https://console.firebase.google.com/project/${setup.projectId}/storage`
+      )} to create your Storage bucket.`,
+      { exit: 1 }
+    );
+  }
+
   setup.config.storage = {};
   ensureLocationSet(setup.projectLocation, "Cloud Storage");
 
diff --git a/src/management/projects.ts b/src/management/projects.ts
index ebf146f3b..5db66cfa6 100644
--- a/src/management/projects.ts
+++ b/src/management/projects.ts
@@ -264,11 +264,22 @@ export async function createCloudProject(
     });
     return projectInfo;
   } catch (err) {
-    logger.debug(err.message);
-    throw new FirebaseError(
-      "Failed to create Google Cloud project. See firebase-debug.log for more info.",
-      { exit: 2, original: err }
-    );
+    if (err.status === 409) {
+      throw new FirebaseError(
+        `Failed to create project because there is already a project with ID ${clc.bold(
+          projectId
+        )}. Please try again with a unique project ID.`,
+        {
+          exit: 2,
+          original: err,
+        }
+      );
+    } else {
+      throw new FirebaseError("Failed to create project. See firebase-debug.log for more info.", {
+        exit: 2,
+        original: err,
+      });
+    }
   }
 }
 


Test patch:
diff --git a/src/test/init/features/firestore.spec.ts b/src/test/init/features/firestore.spec.ts
index 9862721bf..848ae684d 100644
--- a/src/test/init/features/firestore.spec.ts
+++ b/src/test/init/features/firestore.spec.ts
@@ -7,9 +7,15 @@ import * as firestore from "../../../init/features/firestore";
 import * as indexes from "../../../init/features/firestore/indexes";
 import * as rules from "../../../init/features/firestore/rules";
 import * as requirePermissions from "../../../requirePermissions";
+import * as apiEnabled from "../../../ensureApiEnabled";
 
 describe("firestore", () => {
   const sandbox: sinon.SinonSandbox = sinon.createSandbox();
+  let checkApiStub: sinon.SinonStub;
+
+  beforeEach(() => {
+    checkApiStub = sandbox.stub(apiEnabled, "check");
+  });
 
   afterEach(() => {
     sandbox.restore();
@@ -22,6 +28,8 @@ describe("firestore", () => {
         .resolves();
       const initIndexesStub = sandbox.stub(indexes, "initIndexes").resolves();
       const initRulesStub = sandbox.stub(rules, "initRules").resolves();
+      checkApiStub.returns(true);
+
       const setup = { config: {}, projectId: "my-project-123", projectLocation: "us-central1" };
 
       await firestore.doSetup(setup, {});
@@ -33,6 +41,8 @@ describe("firestore", () => {
     });
 
     it("should error when cloud resource location is not set", async () => {
+      checkApiStub.returns(true);
+
       const setup = { config: {}, projectId: "my-project-123" };
 
       expect(firestore.doSetup(setup, {})).to.eventually.be.rejectedWith(
@@ -40,5 +50,16 @@ describe("firestore", () => {
         "Cloud resource location is not set"
       );
     });
+
+    it("should error when the firestore API is not enabled", async () => {
+      checkApiStub.returns(false);
+
+      const setup = { config: {}, projectId: "my-project-123" };
+
+      expect(firestore.doSetup(setup, {})).to.eventually.be.rejectedWith(
+        FirebaseError,
+        "It looks like you haven't used Cloud Firestore"
+      );
+    });
   });
 });
diff --git a/src/test/init/features/storage.spec.ts b/src/test/init/features/storage.spec.ts
index 472ffd794..decc0bb37 100644
--- a/src/test/init/features/storage.spec.ts
+++ b/src/test/init/features/storage.spec.ts
@@ -6,15 +6,18 @@ import { FirebaseError } from "../../../error";
 import * as Config from "../../../config";
 import { doSetup } from "../../../init/features/storage";
 import * as prompt from "../../../prompt";
+import * as apiEnabled from "../../../ensureApiEnabled";
 
 describe("storage", () => {
   const sandbox: sinon.SinonSandbox = sinon.createSandbox();
   let writeProjectFileStub: sinon.SinonStub;
   let promptStub: sinon.SinonStub;
+  let checkApiStub: sinon.SinonStub;
 
   beforeEach(() => {
     writeProjectFileStub = sandbox.stub(Config.prototype, "writeProjectFile");
     promptStub = sandbox.stub(prompt, "promptOnce");
+    checkApiStub = sandbox.stub(apiEnabled, "check");
   });
 
   afterEach(() => {
@@ -29,6 +32,7 @@ describe("storage", () => {
         projectId: "my-project-123",
         projectLocation: "us-central",
       };
+      checkApiStub.returns(true);
       promptStub.returns("storage.rules");
       writeProjectFileStub.resolves();
 
@@ -43,11 +47,26 @@ describe("storage", () => {
         rcfile: {},
         projectId: "my-project-123",
       };
+      checkApiStub.returns(true);
 
       expect(doSetup(setup, new Config("/path/to/src", {}))).to.eventually.be.rejectedWith(
         FirebaseError,
         "Cloud resource location is not set"
       );
     });
+
+    it("should error when the Cloud Storage API is not enabled", async () => {
+      const setup = {
+        config: {},
+        rcfile: {},
+        projectId: "my-project-123",
+      };
+      checkApiStub.returns(false);
+
+      expect(doSetup(setup, new Config("/path/to/src", {}))).to.eventually.be.rejectedWith(
+        FirebaseError,
+        "It looks like you haven't used Cloud Storage"
+      );
+    });
   });
 });
diff --git a/src/test/management/projects.spec.ts b/src/test/management/projects.spec.ts
index 9761a19cf..c8b639809 100644
--- a/src/test/management/projects.spec.ts
+++ b/src/test/management/projects.spec.ts
@@ -286,7 +286,7 @@ describe("Project management", () => {
         }
 
         expect(err.message).to.equal(
-          "Failed to create Google Cloud project. See firebase-debug.log for more info."
+          "Failed to create project. See firebase-debug.log for more info."
         );
         expect(err.original).to.equal(expectedError);
         expect(apiRequestStub).to.be.calledOnceWith("POST", "/v1/projects", {
@@ -314,7 +314,7 @@ describe("Project management", () => {
         }
 
         expect(err.message).to.equal(
-          "Failed to create Google Cloud project. See firebase-debug.log for more info."
+          "Failed to create project. See firebase-debug.log for more info."
         );
         expect(err.original).to.equal(expectedError);
         expect(apiRequestStub).to.be.calledOnceWith("POST", "/v1/projects", {