Skip to content

Commit bbbee83

Browse files
committed
redirect to apphosting
1 parent cc05c02 commit bbbee83

File tree

2 files changed

+80
-28
lines changed

2 files changed

+80
-28
lines changed

src/init/features/hosting/index.spec.ts

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -97,31 +97,33 @@ describe("hosting feature init", () => {
9797
expect(setup.featureInfo?.hosting?.newSiteId).to.equal("new-site-id");
9898
});
9999

100-
it("should recommend App Hosting and throw when a Node.js framework is detected", async () => {
100+
it("should redirect to apphosting init when a Node.js framework is detected", async () => {
101101
const setup: Setup = {
102102
config: {},
103103
rcfile: { projects: {}, targets: {}, etags: {} },
104104
projectId: "test-project",
105+
features: [],
105106
instructions: [],
106107
};
107108
const cfg = new config.Config({}, { projectDir: "/", cwd: "/" });
108109

109110
sandbox.stub(frameworks, "discover").resolves({ framework: "next", mayWantBackend: true });
110-
sandbox.stub(getDefaultHostingSiteMod, "getDefaultHostingSite").resolves("test-site");
111-
112-
sandbox.stub(prompt, "confirm").resolves(false);
113-
sandbox.stub(prompt, "input").resolves("public");
111+
const confirmStub = sandbox.stub(prompt, "confirm");
112+
const inputStub = sandbox.stub(prompt, "input");
114113
sandbox.stub(github, "initGitHub").resolves();
115114

116-
await expect(
117-
askQuestions(setup, cfg, {
118-
cwd: "/",
119-
configPath: "",
120-
only: "",
121-
except: "",
122-
nonInteractive: false,
123-
} as any),
124-
).to.be.rejectedWith(/firebase init apphosting/);
115+
await askQuestions(setup, cfg, {
116+
cwd: "/",
117+
configPath: "",
118+
only: "",
119+
except: "",
120+
nonInteractive: false,
121+
} as any);
122+
123+
expect(setup.features).to.deep.equal(["apphosting"]);
124+
expect(setup.featureInfo?.hosting).to.deep.equal({ redirectToAppHosting: true });
125+
expect(confirmStub.called).to.be.false;
126+
expect(inputStub.called).to.be.false;
125127
});
126128

127129
it("should not terminate when no framework is detected", async () => {
@@ -221,6 +223,48 @@ describe("hosting feature init", () => {
221223
});
222224

223225
describe("actuate", () => {
226+
it("should throw when hosting info is missing", async () => {
227+
const setup: Setup = {
228+
config: {},
229+
rcfile: { projects: {}, targets: {}, etags: {} },
230+
projectId: "test-project",
231+
instructions: [],
232+
};
233+
const cfg = new config.Config({}, { projectDir: "/", cwd: "/" });
234+
235+
await expect(
236+
actuate(setup, cfg, {
237+
cwd: "/",
238+
configPath: "",
239+
only: "",
240+
except: "",
241+
nonInteractive: false,
242+
} as any),
243+
).to.be.rejectedWith(/Could not find hosting info/);
244+
});
245+
246+
it("should be a no-op when hosting was redirected to apphosting", async () => {
247+
const setup: Setup = {
248+
config: {},
249+
rcfile: { projects: {}, targets: {}, etags: {} },
250+
projectId: "test-project",
251+
featureInfo: { hosting: { redirectToAppHosting: true } },
252+
instructions: [],
253+
};
254+
const cfg = new config.Config({}, { projectDir: "/", cwd: "/" });
255+
const askWriteStub = sandbox.stub(cfg, "askWriteProjectFile").resolves();
256+
257+
await actuate(setup, cfg, {
258+
cwd: "/",
259+
configPath: "",
260+
only: "",
261+
except: "",
262+
nonInteractive: false,
263+
} as any);
264+
265+
expect(askWriteStub.called).to.be.false;
266+
});
267+
224268
it("should write 404.html and index.html for non-SPA", async () => {
225269
const setup: Setup = {
226270
config: {},

src/init/features/hosting/index.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as clc from "colorette";
22
import { join } from "path";
33
import { Client } from "../../../apiv2";
4-
import { discover, WebFrameworks } from "../../../frameworks";
4+
import { discover } from "../../../frameworks";
55
import * as github from "./github";
66
import { confirm, input } from "../../../prompt";
77
import { logger } from "../../../logger";
@@ -10,16 +10,17 @@ import { Options } from "../../../options";
1010
import { logSuccess } from "../../../utils";
1111
import { pickHostingSiteName } from "../../../hosting/interactive";
1212
import { readTemplateSync } from "../../../templates";
13-
import { FirebaseError } from "../../../error";
1413
import { Setup } from "../..";
1514
import { Config } from "../../../config";
1615
import { createSite } from "../../../hosting/api";
16+
import { FirebaseError } from "../../../error";
1717

1818
const INDEX_TEMPLATE = readTemplateSync("init/hosting/index.html");
1919
const MISSING_TEMPLATE = readTemplateSync("init/hosting/404.html");
2020
const DEFAULT_IGNORES = ["firebase.json", "**/.*", "**/node_modules/**"];
2121

2222
export interface RequiredInfo {
23+
redirectToAppHosting?: boolean;
2324
newSiteId?: string;
2425
public?: string;
2526
spa?: boolean;
@@ -28,12 +29,23 @@ export interface RequiredInfo {
2829
// TODO: come up with a better way to type this
2930
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3031
export async function askQuestions(setup: Setup, config: Config, options: Options): Promise<void> {
32+
// Detect frameworks first, before asking any hosting questions
33+
const discoveredFramework = await discover(config.projectDir, false);
34+
if (discoveredFramework && discoveredFramework.mayWantBackend) {
35+
const frameworkName = discoveredFramework.framework;
36+
logger.info();
37+
logger.info(
38+
`Detected a ${frameworkName} codebase. Setting up ${clc.bold("App Hosting")} instead.`,
39+
);
40+
setup.featureInfo ||= {};
41+
setup.featureInfo.hosting = { redirectToAppHosting: true };
42+
setup.features?.unshift("apphosting");
43+
return;
44+
}
45+
3146
setup.featureInfo = setup.featureInfo || {};
3247
setup.featureInfo.hosting = {};
3348

34-
// Fire off framework detection concurrently so it runs while the site check happens
35-
const discoverPromise = discover(config.projectDir, false);
36-
3749
// There's a path where we can set up Hosting without a project, so if
3850
// if setup.projectId is empty, we don't do any checking for a Hosting site.
3951
if (setup.projectId) {
@@ -62,15 +74,6 @@ export async function askQuestions(setup: Setup, config: Config, options: Option
6274
}
6375
}
6476

65-
const discoveredFramework = await discoverPromise;
66-
if (discoveredFramework && discoveredFramework.mayWantBackend) {
67-
const frameworkName =
68-
WebFrameworks[discoveredFramework.framework]?.name ?? discoveredFramework.framework;
69-
throw new FirebaseError(
70-
`Detected a ${frameworkName} codebase. Use ${clc.bold("firebase init apphosting")} instead.`,
71-
);
72-
}
73-
7477
logger.info();
7578
logger.info(
7679
`Your ${clc.bold("public")} directory is the folder (relative to your project directory) that`,
@@ -106,6 +109,11 @@ export async function actuate(setup: Setup, config: Config, options: Options): P
106109
);
107110
}
108111

112+
// if the user was redirected to App Hosting, we don't need to do anything here
113+
if (hostingInfo.redirectToAppHosting) {
114+
return;
115+
}
116+
109117
if (hostingInfo.newSiteId && setup.projectId) {
110118
await createSite(setup.projectId, hostingInfo.newSiteId);
111119
logger.info();

0 commit comments

Comments
 (0)