Skip to content

Commit 28c03d4

Browse files
authored
Merge branch 'main' into feat/support-non-existent-next-configs
2 parents 849b09c + 126f46c commit 28c03d4

File tree

9 files changed

+124
-64
lines changed

9 files changed

+124
-64
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ information on using pull requests.
2525
## Community Guidelines
2626

2727
This project follows [Google's Open Source Community
28-
Guidelines](https://opensource.google/conduct/).
28+
Guidelines](https://opensource.google/conduct/).

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@apphosting/adapter-angular/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@apphosting/adapter-angular",
3-
"version": "17.2.13",
3+
"version": "17.2.14",
44
"main": "dist/index.js",
55
"description": "Experimental addon to the Firebase CLI to add web framework support",
66
"repository": {

packages/@apphosting/adapter-angular/src/utils.ts

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -50,40 +50,75 @@ export async function checkBuildConditions(opts: BuildOptions): Promise<void> {
5050
return;
5151
}
5252

53+
let angularBuilder = "";
5354
// dynamically load Angular so this can be used in an NPX context
5455
const angularCorePath = require.resolve("@angular/core", { paths: [process.cwd()] });
55-
const { NodeJsAsyncHost }: typeof import("@angular-devkit/core/node") = await import(
56-
require.resolve("@angular-devkit/core/node", {
57-
paths: [process.cwd(), angularCorePath],
58-
})
59-
);
60-
const { workspaces }: typeof import("@angular-devkit/core") = await import(
61-
require.resolve("@angular-devkit/core", {
62-
paths: [process.cwd(), angularCorePath],
63-
})
64-
);
65-
const host = workspaces.createWorkspaceHost(new NodeJsAsyncHost());
66-
const { workspace } = await workspaces.readWorkspace(opts.projectDirectory, host);
67-
68-
const apps: string[] = [];
69-
workspace.projects.forEach((value, key) => {
70-
if (value.extensions.projectType === "application") apps.push(key);
71-
});
72-
const project = apps[0];
73-
if (apps.length > 1 || !project) throw new Error("Unable to determine the application to deploy");
74-
75-
const workspaceProject = workspace.projects.get(project);
76-
if (!workspaceProject) throw new Error(`No project ${project} found.`);
77-
78-
const target = "build";
79-
if (!workspaceProject.targets.has(target)) throw new Error("Could not find build target.");
80-
81-
const { builder } = workspaceProject.targets.get(target)!;
82-
if (!ALLOWED_BUILDERS.includes(builder)) {
83-
throw new Error(
84-
`Currently, only the following builders are supported: ${ALLOWED_BUILDERS.join(",")}.`,
56+
try {
57+
// Note: we assume that the user's app has @angular-devkit/core in their node_modules/
58+
// because we expect them to have @angular-devkit/build-angular as a dependency which
59+
// pulls in @angular-devkit/core as a dependency. However this assumption may not hold
60+
// due to tree shaking.
61+
const { NodeJsAsyncHost }: typeof import("@angular-devkit/core/node") = await import(
62+
require.resolve("@angular-devkit/core/node", {
63+
paths: [process.cwd(), angularCorePath],
64+
})
8565
);
66+
const { workspaces }: typeof import("@angular-devkit/core") = await import(
67+
require.resolve("@angular-devkit/core", {
68+
paths: [process.cwd(), angularCorePath],
69+
})
70+
);
71+
const host = workspaces.createWorkspaceHost(new NodeJsAsyncHost());
72+
const { workspace } = await workspaces.readWorkspace(opts.projectDirectory, host);
73+
74+
const apps: string[] = [];
75+
workspace.projects.forEach((value, key) => {
76+
if (value.extensions.projectType === "application") apps.push(key);
77+
});
78+
const project = apps[0];
79+
if (apps.length > 1 || !project) {
80+
throw new Error("Unable to determine the application to deploy");
81+
}
82+
83+
const workspaceProject = workspace.projects.get(project);
84+
if (!workspaceProject) {
85+
throw new Error(`No project ${project} found.`);
86+
}
87+
88+
const target = "build";
89+
if (!workspaceProject.targets.has(target)) throw new Error("Could not find build target.");
90+
91+
const { builder } = workspaceProject.targets.get(target)!;
92+
angularBuilder = builder;
93+
} catch (error) {
94+
logger.debug("failed to determine angular builder from the workspace api: ", error);
95+
try {
96+
const root = process.cwd();
97+
const angularJSON = JSON.parse(readFileSync(join(root, "angular.json")).toString());
98+
const apps: string[] = [];
99+
Object.keys(angularJSON.projects).forEach((projectName) => {
100+
const project = angularJSON.projects[projectName];
101+
if (project["projectType"] === "application") apps.push(projectName);
102+
});
103+
const project = apps[0];
104+
if (apps.length > 1 || !project)
105+
throw new Error("Unable to determine the application to deploy");
106+
angularBuilder = angularJSON.projects[project].architect.build.builder;
107+
} catch (error) {
108+
logger.debug("failed to determine angular builder from parsing angular.json: ", error);
109+
}
110+
}
111+
112+
if (angularBuilder !== "") {
113+
if (!ALLOWED_BUILDERS.includes(angularBuilder)) {
114+
throw new Error(
115+
`Currently, only the following builders are supported: ${ALLOWED_BUILDERS.join(",")}.`,
116+
);
117+
}
86118
}
119+
// This is just a validation step and our methods for validation are flakey. If we failed to extract
120+
// the angular builder for validation, the build will continue. It may fail further down the line
121+
// but the failure reason should be non-ambigious.
87122
}
88123

89124
// Populate file or directory paths we need for generating output directory

packages/@apphosting/adapter-nextjs/e2e/middleware.spec.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ before(() => {
1515
});
1616

1717
describe("middleware", () => {
18-
it("should have x-fah-adapter header and x-fah-middleware header on all routes", async () => {
18+
it("should have x-fah-adapter header on all routes", async () => {
1919
const routes = [
2020
"/",
2121
"/ssg",
@@ -33,6 +33,15 @@ describe("middleware", () => {
3333
`nextjs-${adapterVersion}`,
3434
`Route ${route} missing x-fah-adapter header`,
3535
);
36+
}
37+
});
38+
39+
it("should have x-fah-middleware header on middleware routes", async () => {
40+
// Middleware is configured to run on these routes via run-local.ts with-middleware setup function
41+
const routes = ["/ssg", "/ssr"];
42+
43+
for (const route of routes) {
44+
const response = await fetch(posix.join(host, route));
3645
assert.equal(
3746
response.headers.get("x-fah-middleware"),
3847
"true",

packages/@apphosting/adapter-nextjs/e2e/run-local.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ const scenarios: Scenario[] = [
3535
setup: async (cwd: string) => {
3636
// Create a middleware.ts file
3737
const middlewareContent = `
38-
import type { NextRequest } from 'next/server'
38+
import type { NextRequest } from "next/server";
3939
4040
export function middleware(request: NextRequest) {
4141
// This is a simple middleware that doesn't modify the request
42-
console.log('Middleware executed', request.nextUrl.pathname);
42+
console.log("Middleware executed", request.nextUrl.pathname);
4343
}
4444
4545
export const config = {
46-
matcher: '/((?!api|_next/static|_next/image|favicon.ico).*)',
46+
matcher: ["/ssg", "/ssr"],
4747
};
4848
`;
4949

packages/@apphosting/adapter-nextjs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@apphosting/adapter-nextjs",
3-
"version": "14.0.12",
3+
"version": "14.0.13",
44
"main": "dist/index.js",
55
"description": "Experimental addon to the Firebase CLI to add web framework support",
66
"repository": {

packages/@apphosting/adapter-nextjs/src/overrides.spec.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ describe("route overrides", () => {
8585
assert.deepStrictEqual(updatedManifest, expectedManifest);
8686
});
8787

88-
it("should add middleware header when middleware exists", async () => {
88+
it("should add middleware header only to routes for which middleware is enabled", async () => {
8989
const { addRouteOverrides } = await importOverrides;
9090
const initialManifest: RoutesManifest = {
9191
version: 3,
@@ -109,8 +109,8 @@ describe("route overrides", () => {
109109
page: "/",
110110
matchers: [
111111
{
112-
regexp: "^/.*$",
113-
originalSource: "/:path*",
112+
regexp: "/hello",
113+
originalSource: "/hello",
114114
},
115115
],
116116
},
@@ -130,7 +130,7 @@ describe("route overrides", () => {
130130
fs.readFileSync(routesManifestPath, "utf-8"),
131131
) as RoutesManifest;
132132

133-
assert.strictEqual(updatedManifest.headers.length, 1);
133+
assert.strictEqual(updatedManifest.headers.length, 2);
134134

135135
const expectedManifest: RoutesManifest = {
136136
version: 3,
@@ -150,9 +150,13 @@ describe("route overrides", () => {
150150
key: "x-fah-adapter",
151151
value: "nextjs-1.0.0",
152152
},
153-
{ key: "x-fah-middleware", value: "true" },
154153
],
155154
},
155+
{
156+
source: "/hello",
157+
regex: "/hello",
158+
headers: [{ key: "x-fah-middleware", value: "true" }],
159+
},
156160
],
157161
};
158162

packages/@apphosting/adapter-nextjs/src/overrides.ts

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AdapterMetadata, MiddlewareManifest } from "./interfaces.js";
1+
import { AdapterMetadata } from "./interfaces.js";
22
import {
33
loadRouteManifest,
44
writeRouteManifest,
@@ -175,9 +175,12 @@ export async function validateNextConfigOverride(
175175
* Modifies the app's route manifest (routes-manifest.json) to add Firebase App Hosting
176176
* specific overrides (i.e headers).
177177
*
178-
* This function adds the following headers to all routes:
178+
* It adds the following headers to all routes:
179179
* - x-fah-adapter: The Firebase App Hosting adapter version used to build the app.
180+
*
181+
* It also adds the following headers to all routes for which middleware is enabled:
180182
* - x-fah-middleware: When middleware is enabled.
183+
*
181184
* @param appPath The path to the app directory.
182185
* @param distDir The path to the dist directory.
183186
* @param adapterMetadata The adapter metadata.
@@ -187,38 +190,47 @@ export async function addRouteOverrides(
187190
distDir: string,
188191
adapterMetadata: AdapterMetadata,
189192
) {
190-
const middlewareManifest = loadMiddlewareManifest(appPath, distDir);
191193
const routeManifest = loadRouteManifest(appPath, distDir);
194+
195+
// Add the adapter version to all routes
192196
routeManifest.headers.push({
193197
source: "/:path*",
194198
headers: [
195199
{
196200
key: "x-fah-adapter",
197201
value: `nextjs-${adapterMetadata.adapterVersion}`,
198202
},
199-
...(middlewareExists(middlewareManifest)
200-
? [
201-
{
202-
key: "x-fah-middleware",
203-
value: "true",
204-
},
205-
]
206-
: []),
207203
],
208204
/*
209-
NextJs converts the source string to a regex using path-to-regexp (https://github.com/pillarjs/path-to-regexp) at
210-
build time: https://github.com/vercel/next.js/blob/canary/packages/next/src/build/index.ts#L1273.
211-
This regex is then used to match the route against the request path.
205+
NextJs converts the source string to a regex using path-to-regexp (https://github.com/pillarjs/path-to-regexp) at
206+
build time: https://github.com/vercel/next.js/blob/canary/packages/next/src/build/index.ts#L1273.
207+
This regex is then used to match the route against the request path.
212208
213-
This regex was generated by building a sample NextJs app with the source string `/:path*` and then inspecting the
214-
routes-manifest.json file.
215-
*/
209+
This regex was generated by building a sample NextJs app with the source string `/:path*` and then inspecting the
210+
routes-manifest.json file.
211+
*/
216212
regex: "^(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))?(?:/)?$",
217213
});
218214

219-
await writeRouteManifest(appPath, distDir, routeManifest);
220-
}
215+
// Add the middleware header to all routes for which middleware is enabled
216+
const middlewareManifest = loadMiddlewareManifest(appPath, distDir);
217+
const rootMiddleware = middlewareManifest.middleware["/"];
218+
if (rootMiddleware?.matchers) {
219+
console.log("Middleware detected, adding middleware headers to matching routes");
220+
221+
rootMiddleware.matchers.forEach((matcher) => {
222+
routeManifest.headers.push({
223+
source: matcher.originalSource,
224+
headers: [
225+
{
226+
key: "x-fah-middleware",
227+
value: "true",
228+
},
229+
],
230+
regex: matcher.regexp,
231+
});
232+
});
233+
}
221234

222-
function middlewareExists(middlewareManifest: MiddlewareManifest) {
223-
return Object.keys(middlewareManifest.middleware).length > 0;
235+
await writeRouteManifest(appPath, distDir, routeManifest);
224236
}

0 commit comments

Comments
 (0)