Repository: firebase/firebase-tools (ts)

Issue:
firebase deploy crashes due to next.js image optimization check, version 12.4.5
<!-- 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 -->

<!-- Output of `firebase --version` -->

**firebase-tools:** 12.4.5

The issue seems to have been introduced with commit 7d2a90e6063937daaef9c9a882339991570584e2

<!-- e.g. macOS, Windows, Ubuntu -->

**Platform:** ubuntu-lastest (FirebaseExtended/action-hosting-deploy@v0)

### [REQUIRED] Test case

<!-- Provide a minimal, complete, and verifiable example (http://stackoverflow.com/help/mcve) -->

Proprietary source code using "next/image" (image optimization with Node.js), hosted on firebase using Firebase Hosting and Firebase Functions. The code is built using GitHub Actions and deployed using FirebaseExtended/action-hosting-deploy@v0, which uses the latest firebase-tools version. The GitHub Actions job config is below.

![Screenshot 2023-07-20 005700](https://github.com/firebase/firebase-tools/assets/8747511/a2d7ad0d-3a0b-45ca-b893-036a826fcc82)

### [REQUIRED] Steps to reproduce

<!-- Provide the steps needed to reproduce the issue with the above test case. -->

1. Run the FirebaseExtended/action-hosting-deploy@v0 GitHub action, which pulls the latest firebase-tools version, in this case 12.4.5
2. action-hosting-deploy runs firebase deploy
3. firebase deploy crashes in **_isUsingNextImageInAppDirectory_**


### [REQUIRED] Expected behavior

<!-- What is the expected behavior? -->

firebase deploy should run successfully and identify Next.js image optimization occurences.

**Note:** even with the previous version (12.4.4), firebase-tools does not identify the usage of the image optimization in my private repository and I have to create an empty server action for the image optimization to work, otherwise it does not identify the image optimization usage and does not generate a firebase function. If I create an empty server action, then a function is generated and image optimization works as a result of the function being generated for a different purpose

### [REQUIRED] Actual behavior

<!-- Run the command with --debug flag, and include the logs below. -->

firebase deploy crashes with the stack trace shown below 

![Screenshot 2023-07-20 005207](https://github.com/firebase/firebase-tools/assets/8747511/0b473b8e-a5fc-4eda-b92b-afa614c12346)


Pull request:
Next.js/glob fix
<!--

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 #6163

The usage of `Glob` caused problems in the CLI build. This PR replaces `Glob` with `sync`. 

In other parts of the frameworks code `glob` is being used as `import * as glob from "glob"`: https://github.com/firebase/firebase-tools/blob/29747a824caa5589a4e04851a4ca0fa92491f03e/src/frameworks/index.ts#L8

 but I could not find a way to stub it with Sinon for the unit tests, that's why `Glob` was used.

@joehan can you help confirming `sync` won't cause problems? Maybe because the CLI `glob` version is old? The CLI uses `7.1.2` but the latest is `10.3.3`.


<!-- Are you fixing a bug? Implementing a new feature? Make sure we have the context around your change. Link to other relevant issues or pull requests. -->

### Scenarios Tested

<!-- Write a list of all the user journeys and edge cases you've tested. Instructions for manual testing can be found at https://github.com/firebase/firebase-tools/blob/master/.github/CONTRIBUTING.md#development-setup -->

`deploy`

### Sample Commands

<!-- Proposing a change to commands or flags? Provide examples of how they will be used. -->


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e69de29bb..814f93031 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -0,0 +1,1 @@
+- Fixed `glob` usage in Next.js utility function to detect images in `app` directory (#6166)
diff --git a/src/frameworks/next/utils.ts b/src/frameworks/next/utils.ts
index f9e1bde3c..d625c105a 100644
--- a/src/frameworks/next/utils.ts
+++ b/src/frameworks/next/utils.ts
@@ -2,7 +2,7 @@ import { existsSync } from "fs";
 import { pathExists } from "fs-extra";
 import { basename, extname, join, posix } from "path";
 import { readFile } from "fs/promises";
-import { Glob } from "glob";
+import { sync as globSync } from "glob";
 import type { PagesManifest } from "next/dist/build/webpack/plugins/pages-manifest-plugin";
 import { coerce } from "semver";
 
@@ -245,11 +245,11 @@ export async function isUsingNextImageInAppDirectory(
   projectDir: string,
   nextDir: string
 ): Promise<boolean> {
-  const { found: files } = new Glob(
+  const files = globSync(
     join(projectDir, nextDir, "server", "**", "*client-reference-manifest.js")
   );
 
-  for await (const filepath of files) {
+  for (const filepath of files) {
     const fileContents = await readFile(filepath);
 
     // Return true when the first file containing the next/image component is found


Test patch:
diff --git a/scripts/webframeworks-deploy-tests/nextjs/app/app/image/page.tsx b/scripts/webframeworks-deploy-tests/nextjs/app/app/image/page.tsx
new file mode 100644
index 000000000..efe619104
--- /dev/null
+++ b/scripts/webframeworks-deploy-tests/nextjs/app/app/image/page.tsx
@@ -0,0 +1,10 @@
+import Image from 'next/image'
+
+export default function PageWithImage() {
+  return <Image 
+      src="https://www.google.com/logos/doodles/2015/googles-new-logo-5078286822539264.3-hp2x.gif"
+      alt=""
+      width={300} 
+      height={300}
+    />;
+}
\ No newline at end of file
diff --git a/scripts/webframeworks-deploy-tests/nextjs/next.config.js b/scripts/webframeworks-deploy-tests/nextjs/next.config.js
index 021cc97aa..11d04a8c2 100644
--- a/scripts/webframeworks-deploy-tests/nextjs/next.config.js
+++ b/scripts/webframeworks-deploy-tests/nextjs/next.config.js
@@ -10,6 +10,9 @@ const nextConfig = {
     locales: ['en', 'fr'],
     defaultLocale: 'en',
   },
+  images: {
+    domains: ['google.com'],
+  },
   rewrites: () => [{
     source: '/about',
     destination: '/',
diff --git a/scripts/webframeworks-deploy-tests/tests.ts b/scripts/webframeworks-deploy-tests/tests.ts
index 58cc1bf3f..4e58ada00 100644
--- a/scripts/webframeworks-deploy-tests/tests.ts
+++ b/scripts/webframeworks-deploy-tests/tests.ts
@@ -233,6 +233,12 @@ describe("webframeworks", function (this) {
         expect(apiDynamicResponse.headers.get("cache-control")).to.eql("private");
         expect(await apiDynamicResponse.json()).to.eql([1, 2, 3]);
       });
+
+      it("should have working image", async () => {
+        const response = await fetch(`${NEXTJS_HOST}/app/image`);
+        expect(response.ok).to.be.true;
+        expect(await response.text()).to.include("<body><img");
+      });
     });
 
     describe("pages directory", () => {
@@ -299,6 +305,7 @@ describe("webframeworks", function (this) {
                 `/${NEXT_BASE_PATH}/_next/static/${buildId}/_buildManifest.js`,
                 `/${NEXT_BASE_PATH}/_next/static/${buildId}/_ssgManifest.js`,
                 `/${NEXT_BASE_PATH}/app/api/static`,
+                `/${NEXT_BASE_PATH}/app/image.html`,
                 `/${NEXT_BASE_PATH}/app/ssg.html`,
               ]),
           `/${I18N_BASE}/${locale}/${NEXT_BASE_PATH}/pages/fallback/1.html`,
diff --git a/src/test/frameworks/next/utils.spec.ts b/src/test/frameworks/next/utils.spec.ts
index feeaf4a8c..0e5fa2163 100644
--- a/src/test/frameworks/next/utils.spec.ts
+++ b/src/test/frameworks/next/utils.spec.ts
@@ -282,8 +282,8 @@ describe("Next.js utils", () => {
 
       it("should return true when using next/image in the app directory", async () => {
         sandbox
-          .stub(glob, "Glob")
-          .returns({ found: ["/path-to-app/.next/server/app/page_client-reference-manifest.js"] });
+          .stub(glob, "sync")
+          .returns(["/path-to-app/.next/server/app/page_client-reference-manifest.js"]);
         sandbox.stub(fsPromises, "readFile").resolves(pageClientReferenceManifestWithImage);
 
         expect(await isUsingNextImageInAppDirectory("", "")).to.be.true;
@@ -292,13 +292,13 @@ describe("Next.js utils", () => {
       it("should return false when not using next/image in the app directory", async () => {
         sandbox.stub(fsPromises, "readFile").resolves(pageClientReferenceManifestWithoutImage);
         const globStub = sandbox
-          .stub(glob, "Glob")
-          .returns({ found: ["/path-to-app/.next/server/app/page_client-reference-manifest.js"] });
+          .stub(glob, "sync")
+          .returns(["/path-to-app/.next/server/app/page_client-reference-manifest.js"]);
 
         expect(await isUsingNextImageInAppDirectory("", "")).to.be.false;
 
         globStub.restore();
-        sandbox.stub(glob, "Glob").returns({ found: [] });
+        sandbox.stub(glob, "sync").returns([]);
 
         expect(await isUsingNextImageInAppDirectory("", "")).to.be.false;
       });
@@ -312,15 +312,15 @@ describe("Next.js utils", () => {
       it("should return true when using next/image in the app directory", async () => {
         sandbox.stub(fsPromises, "readFile").resolves(clientReferenceManifestWithImage);
         sandbox
-          .stub(glob, "Glob")
-          .returns({ found: ["/path-to-app/.next/server/client-reference-manifest.js"] });
+          .stub(glob, "sync")
+          .returns(["/path-to-app/.next/server/client-reference-manifest.js"]);
 
         expect(await isUsingNextImageInAppDirectory("", "")).to.be.true;
       });
 
       it("should return false when not using next/image in the app directory", async () => {
         sandbox.stub(fsPromises, "readFile").resolves(clientReferenceManifestWithoutImage);
-        sandbox.stub(glob, "Glob").returns({ found: [] });
+        sandbox.stub(glob, "sync").returns([]);
 
         expect(await isUsingNextImageInAppDirectory("", "")).to.be.false;
       });