Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
125 changes: 125 additions & 0 deletions src/init/features/hosting/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as config from "../../../config";
import * as getDefaultHostingSiteMod from "../../../getDefaultHostingSite";
import * as hostingInteractive from "../../../hosting/interactive";
import * as hostingApi from "../../../hosting/api";
import * as frameworks from "../../../frameworks";
import { Client } from "../../../apiv2";
import { askQuestions, actuate } from "./index";
import { Setup } from "../..";
Expand All @@ -31,6 +32,7 @@ describe("hosting feature init", () => {
};
const cfg = new config.Config({}, { projectDir: "/", cwd: "/" });

sandbox.stub(frameworks, "discover").resolves(undefined);
// Mock existing site check
sandbox.stub(getDefaultHostingSiteMod, "getDefaultHostingSite").resolves("test-site");

Expand Down Expand Up @@ -72,6 +74,7 @@ describe("hosting feature init", () => {
};
const cfg = new config.Config({}, { projectDir: "/", cwd: "/" });

sandbox.stub(frameworks, "discover").resolves(undefined);
sandbox
.stub(getDefaultHostingSiteMod, "getDefaultHostingSite")
.rejects(getDefaultHostingSiteMod.errNoDefaultSite);
Expand All @@ -93,6 +96,128 @@ describe("hosting feature init", () => {
expect(pickSiteStub.called).to.be.true;
expect(setup.featureInfo?.hosting?.newSiteId).to.equal("new-site-id");
});

it("should recommend App Hosting and throw when a Node.js framework is detected", async () => {
const setup: Setup = {
config: {},
rcfile: { projects: {}, targets: {}, etags: {} },
projectId: "test-project",
instructions: [],
};
const cfg = new config.Config({}, { projectDir: "/", cwd: "/" });

sandbox.stub(frameworks, "discover").resolves({ framework: "next", mayWantBackend: true });
sandbox.stub(getDefaultHostingSiteMod, "getDefaultHostingSite").resolves("test-site");

sandbox.stub(prompt, "confirm").resolves(false);
sandbox.stub(prompt, "input").resolves("public");
sandbox.stub(github, "initGitHub").resolves();

await expect(
askQuestions(setup, cfg, {
cwd: "/",
configPath: "",
only: "",
except: "",
nonInteractive: false,
} as any),
).to.be.rejectedWith(/firebase init apphosting/);
});

it("should not terminate when no framework is detected", async () => {
const setup: Setup = {
config: {},
rcfile: { projects: {}, targets: {}, etags: {} },
projectId: "test-project",
instructions: [],
};
const cfg = new config.Config({}, { projectDir: "/", cwd: "/" });

sandbox.stub(frameworks, "discover").resolves(undefined);
sandbox.stub(getDefaultHostingSiteMod, "getDefaultHostingSite").resolves("test-site");

sandbox.stub(prompt, "confirm").resolves(false);
const inputStub = sandbox.stub(prompt, "input").resolves("public");
sandbox.stub(github, "initGitHub").resolves();

await askQuestions(setup, cfg, {
cwd: "/",
configPath: "",
only: "",
except: "",
nonInteractive: false,
} as any);

expect(
inputStub.calledWith(
sinon.match({ message: "What do you want to use as your public directory?" }),
),
).to.be.true;
});

it("should not terminate for static frameworks like Flutter", async () => {
const setup: Setup = {
config: {},
rcfile: { projects: {}, targets: {}, etags: {} },
projectId: "test-project",
instructions: [],
};
const cfg = new config.Config({}, { projectDir: "/", cwd: "/" });

sandbox
.stub(frameworks, "discover")
.resolves({ framework: "flutter", mayWantBackend: false });
sandbox.stub(getDefaultHostingSiteMod, "getDefaultHostingSite").resolves("test-site");

sandbox.stub(prompt, "confirm").resolves(false);
const inputStub = sandbox.stub(prompt, "input").resolves("public");
sandbox.stub(github, "initGitHub").resolves();

await askQuestions(setup, cfg, {
cwd: "/",
configPath: "",
only: "",
except: "",
nonInteractive: false,
} as any);

expect(
inputStub.calledWith(
sinon.match({ message: "What do you want to use as your public directory?" }),
),
).to.be.true;
});

it("should not terminate when a framework without backend is detected", async () => {
const setup: Setup = {
config: {},
rcfile: { projects: {}, targets: {}, etags: {} },
projectId: "test-project",
instructions: [],
};
const cfg = new config.Config({}, { projectDir: "/", cwd: "/" });

sandbox.stub(frameworks, "discover").resolves({ framework: "nextjs", mayWantBackend: false });
sandbox.stub(getDefaultHostingSiteMod, "getDefaultHostingSite").resolves("test-site");

sandbox.stub(prompt, "confirm").resolves(false);
const inputStub = sandbox.stub(prompt, "input").resolves("public");
sandbox.stub(github, "initGitHub").resolves();

await askQuestions(setup, cfg, {
cwd: "/",
configPath: "",
only: "",
except: "",
nonInteractive: false,
} as any);

expect(
inputStub.calledWith(
sinon.match({ message: "What do you want to use as your public directory?" }),
),
).to.be.true;
});
});

describe("actuate", () => {
Expand Down
13 changes: 13 additions & 0 deletions src/init/features/hosting/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as clc from "colorette";
import { join } from "path";
import { Client } from "../../../apiv2";
import { discover, WebFrameworks } from "../../../frameworks";
import * as github from "./github";
import { confirm, input } from "../../../prompt";
import { logger } from "../../../logger";
Expand Down Expand Up @@ -30,6 +31,9 @@ export async function askQuestions(setup: Setup, config: Config, options: Option
setup.featureInfo = setup.featureInfo || {};
setup.featureInfo.hosting = {};

// Fire off framework detection concurrently so it runs while the site check happens
const discoverPromise = discover(config.projectDir, false);

// There's a path where we can set up Hosting without a project, so if
// if setup.projectId is empty, we don't do any checking for a Hosting site.
if (setup.projectId) {
Expand Down Expand Up @@ -58,6 +62,15 @@ export async function askQuestions(setup: Setup, config: Config, options: Option
}
}

const discoveredFramework = await discoverPromise;
if (discoveredFramework && discoveredFramework.mayWantBackend) {
const frameworkName =
WebFrameworks[discoveredFramework.framework]?.name ?? discoveredFramework.framework;
throw new FirebaseError(
`Detected a ${frameworkName} codebase. Use ${clc.bold("firebase init apphosting")} instead.`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively - why not just print a message then kick off the init apphosting flow?

I'd also push this code block higher up in the file before we ask all the questions - it seems annoying to answer a bunch of questions that turn out to be irrelevant

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, thanks. Just pushed this change

);
}

logger.info();
logger.info(
`Your ${clc.bold("public")} directory is the folder (relative to your project directory) that`,
Expand Down
Loading