Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
2,423 changes: 2,040 additions & 383 deletions package-lock.json

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions packages/@apphosting/adapter-nextjs/buf.gen.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Learn more: https://buf.build/docs/configuration/v2/buf-gen-yaml
version: v2
inputs:
- module: buf.build/envoyproxy/envoy:main
types:
- "envoy.service.ext_proc.v3.ExternalProcessor"
- module: buf.build/cncf/xds:main
types:
- "udpa.annotations.VersioningAnnotation"
- "udpa.annotations.FileMigrateAnnotation"
- "udpa.annotations.StatusAnnotation"
- "xds.annotations.v3.StatusAnnotation"
- module: buf.build/envoyproxy/protoc-gen-validate:main
paths:
- "validate/validate.proto"
plugins:
- local: protoc-gen-es
opt: target=ts
out: src/protos
62 changes: 62 additions & 0 deletions packages/@apphosting/adapter-nextjs/clean_protos.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const fs = require('fs');
const path = require('path');

const TARGET_DIR = './src/protos';

// This regex finds lines with relative imports.
// It captures three groups:
// 1. The part before the path (e.g., `from '`)
// 2. The relative path itself (e.g., `./some/file`)
// 3. The closing quote (e.g., `'`)
const IMPORT_REGEX = /(from\s+['"])(\.\.?\/[^'"]+)(['"])/g;

/**
* Recursively walks a directory and applies a file processing function.
* @param {string} directory The directory to walk.
* @param {(filePath: string) => void} processFile The function to apply to each file.
*/
function walkDirectory(directory, processFile) {
const items = fs.readdirSync(directory);
for (const item of items) {
const fullPath = path.join(directory, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
walkDirectory(fullPath, processFile);
} else if (fullPath.endsWith('.ts')) {
processFile(fullPath);
}
}
}

/**
* Reads a TypeScript file, adds '.js' extensions to relative imports,
* and writes the changes back to the file.
* @param {string} filePath The path to the TypeScript file.
*/
function addJsExtensions(filePath) {
let content = fs.readFileSync(filePath, 'utf8');
let changesMade = false;

const newContent = content.replace(IMPORT_REGEX, (match, pre, importPath, post) => {
// Check if the path already has an extension.
// path.extname() returns the extension (e.g., '.js') or an empty string.
if (path.extname(importPath)) {
return match; // No change needed
}

changesMade = true;
const newImportPath = `${importPath}.js`;
return `${pre}${newImportPath}${post}`;
});

if (changesMade) {
fs.writeFileSync(filePath, newContent, 'utf8');
}
}

try {
walkDirectory(TARGET_DIR, addJsExtensions);
} catch (error) {
console.error(`Error processing files: ${error.message}`);
process.exit(1);
}
18 changes: 14 additions & 4 deletions packages/@apphosting/adapter-nextjs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@apphosting/adapter-nextjs",
"version": "14.0.18",
"version": "15.0.0",
"main": "dist/index.js",
"description": "Experimental addon to the Firebase CLI to add web framework support",
"repository": {
Expand All @@ -9,6 +9,7 @@
},
"bin": {
"apphosting-adapter-nextjs-build": "dist/bin/build.js",
"apphosting-adapter-nextjs-serve": "dist/bin/serve.js",
"apphosting-adapter-nextjs-create": "dist/bin/create.js"
},
"author": {
Expand All @@ -21,7 +22,8 @@
"type": "module",
"sideEffects": false,
"scripts": {
"build": "rm -rf dist && tsc && chmod +x ./dist/bin/*",
"build:protos": "npx buf generate && node ./clean_protos.cjs",
"build": "npm run build:protos && rm -rf dist && tsc && chmod +x ./dist/bin/* && npx -y esbuild ./dist/index.js --bundle --format=cjs --platform=node --outfile=./dist/index.cjs && npx -y esbuild ./src/bin/serve.ts --bundle --format=esm --platform=node --outfile=./dist/bin/serve.js --external:fastify --external:next",
"test": "npm run test:unit && npm run test:functional",
"test:unit": "ts-mocha -p tsconfig.json 'src/**/*.spec.ts' 'src/*.spec.ts'",
"test:functional": "node --loader ts-node/esm ./e2e/run-local.ts",
Expand All @@ -30,7 +32,7 @@
},
"exports": {
".": {
"node": "./dist/index.js",
"node": "./dist/index.cjs",
"default": null
},
"./dist/*": {
Expand All @@ -44,6 +46,7 @@
"license": "Apache-2.0",
"dependencies": {
"@apphosting/common": "*",
"fastify": "^5.6.1",
"fs-extra": "^11.1.1",
"yaml": "^2.3.4"
},
Expand All @@ -56,15 +59,22 @@
}
},
"devDependencies": {
"@connectrpc/connect": "^2.1.0",
"@bufbuild/buf": "^1.58.0",
"@bufbuild/protobuf": "^2.9.0",
"@bufbuild/protoc-gen-es": "^2.9.0",
"@connectrpc/connect-fastify": "^2.1.0",
"@types/fs-extra": "*",
"@types/mocha": "*",
"@types/tmp": "*",
"mocha": "*",
"next": "~14.0.0",
"next": "15.6.0-canary.54",
"protoc": "^32.1.0",
"semver": "*",
"tmp": "*",
"ts-mocha": "*",
"ts-node": "*",
"ts-proto": "^2.7.7",
"typescript": "*",
"verdaccio": "^5.30.3"
}
Expand Down
20 changes: 0 additions & 20 deletions packages/@apphosting/adapter-nextjs/src/bin/build.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,11 @@
}`,
};
generateTestFiles(tmpDir, files);
await generateBuildOutput(

Check failure on line 48 in packages/@apphosting/adapter-nextjs/src/bin/build.spec.ts

View workflow job for this annotation

GitHub Actions / Lint

Replace `⏎······tmpDir,⏎······tmpDir,⏎······outputBundleOptions,⏎······path.join(tmpDir,·".next"),⏎····` with `tmpDir,·tmpDir,·outputBundleOptions,·path.join(tmpDir,·".next")`
tmpDir,
tmpDir,
outputBundleOptions,
path.join(tmpDir, ".next"),
defaultNextVersion,
adapterMetadata,
);
await validateOutputDirectory(outputBundleOptions, path.join(tmpDir, ".next"));

Expand Down Expand Up @@ -116,8 +114,6 @@
serverFilePath: path.join(tmpDir, ".next", "standalone", "apps", "next-app", "server.js"),
},
path.join(tmpDir, ".next"),
defaultNextVersion,
adapterMetadata,
);

const expectedFiles = {
Expand Down Expand Up @@ -149,16 +145,11 @@
}`,
};
generateTestFiles(tmpDir, files);
await generateBuildOutput(

Check failure on line 148 in packages/@apphosting/adapter-nextjs/src/bin/build.spec.ts

View workflow job for this annotation

GitHub Actions / Lint

Replace `⏎······tmpDir,⏎······tmpDir,⏎······outputBundleOptions,⏎······path.join(tmpDir,·".next"),⏎····` with `tmpDir,·tmpDir,·outputBundleOptions,·path.join(tmpDir,·".next")`
tmpDir,
tmpDir,
outputBundleOptions,
path.join(tmpDir, ".next"),
defaultNextVersion,
{
adapterPackageName: "@apphosting/adapter-nextjs",
adapterVersion: "14.0.1",
},
);
assert.rejects(
async () => await validateOutputDirectory(outputBundleOptions, path.join(tmpDir, ".next")),
Expand All @@ -184,8 +175,6 @@
serverFilePath: path.join(standaloneAppPath, "server.js"),
},
path.join(tmpDir, ".next"),
defaultNextVersion,
adapterMetadata,
);

const expectedFiles = {
Expand All @@ -209,13 +198,11 @@
".next/static/staticfile": "",
};
generateTestFiles(tmpDir, files);
await generateBuildOutput(

Check failure on line 201 in packages/@apphosting/adapter-nextjs/src/bin/build.spec.ts

View workflow job for this annotation

GitHub Actions / Lint

Replace `⏎······tmpDir,⏎······tmpDir,⏎······outputBundleOptions,⏎······path.join(tmpDir,·".next"),⏎····` with `tmpDir,·tmpDir,·outputBundleOptions,·path.join(tmpDir,·".next")`
tmpDir,
tmpDir,
outputBundleOptions,
path.join(tmpDir, ".next"),
defaultNextVersion,
adapterMetadata,
);
await validateOutputDirectory(outputBundleOptions, path.join(tmpDir, ".next"));

Expand All @@ -235,16 +222,11 @@
".gitignore": "/.next/",
};
generateTestFiles(tmpDir, files);
await generateBuildOutput(

Check failure on line 225 in packages/@apphosting/adapter-nextjs/src/bin/build.spec.ts

View workflow job for this annotation

GitHub Actions / Lint

Replace `⏎······tmpDir,⏎······tmpDir,⏎······outputBundleOptions,⏎······path.join(tmpDir,·".next"),⏎····` with `tmpDir,·tmpDir,·outputBundleOptions,·path.join(tmpDir,·".next")`
tmpDir,
tmpDir,
outputBundleOptions,
path.join(tmpDir, ".next"),
defaultNextVersion,
{
adapterPackageName: "@apphosting/adapter-nextjs",
adapterVersion: "14.0.1",
},
);
await validateOutputDirectory(outputBundleOptions, path.join(tmpDir, ".next"));

Expand All @@ -271,13 +253,11 @@
}`,
};
generateTestFiles(tmpDir, files);
await generateBuildOutput(

Check failure on line 256 in packages/@apphosting/adapter-nextjs/src/bin/build.spec.ts

View workflow job for this annotation

GitHub Actions / Lint

Replace `⏎······tmpDir,⏎······tmpDir,⏎······outputBundleOptions,⏎······path.join(tmpDir,·".next"),⏎····` with `tmpDir,·tmpDir,·outputBundleOptions,·path.join(tmpDir,·".next")`
tmpDir,
tmpDir,
outputBundleOptions,
path.join(tmpDir, ".next"),
defaultNextVersion,
adapterMetadata,
);
await validateOutputDirectory(outputBundleOptions, path.join(tmpDir, ".next"));

Expand Down
74 changes: 13 additions & 61 deletions packages/@apphosting/adapter-nextjs/src/bin/build.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,30 @@
#! /usr/bin/env node
import {
loadConfig,
populateOutputBundleOptions,
generateBuildOutput,
validateOutputDirectory,
getAdapterMetadata,
exists,
} from "../utils.js";
import { join } from "path";
import { getBuildOptions, runBuild } from "@apphosting/common";
import {
addRouteOverrides,
overrideNextConfig,
restoreNextConfig,
validateNextConfigOverride,
} from "../overrides.js";
import { generateBuildOutput, loadConfig, populateOutputBundleOptions, validateOutputDirectory } from "../utils.js";

Check failure on line 3 in packages/@apphosting/adapter-nextjs/src/bin/build.ts

View workflow job for this annotation

GitHub Actions / Lint

Replace `·generateBuildOutput,·loadConfig,·populateOutputBundleOptions,·validateOutputDirectory·` with `⏎··generateBuildOutput,⏎··loadConfig,⏎··populateOutputBundleOptions,⏎··validateOutputDirectory,⏎`
import { join } from "node:path";

const root = process.cwd();
const opts = getBuildOptions();

// Set standalone mode
process.env.NEXT_PRIVATE_STANDALONE = "true";
// Opt-out sending telemetry to Vercel
process.env.NEXT_TELEMETRY_DISABLED = "1";

const nextConfig = await loadConfig(root, opts.projectDirectory);
await runBuild();

/**
* Override user's Next Config to optimize the app for Firebase App Hosting
* and validate that the override resulted in a valid config that Next.js can
* load.
*
* We restore the user's Next Config at the end of the build, after the config file has been
* copied over to the output directory, so that the user's original code is not modified.
*
* If the app does not have a next.config.[js|mjs|ts] file in the first place,
* then can skip config override.
*
* Note: loadConfig always returns a fileName (default: next.config.js) even if
* one does not exist in the app's root: https://github.com/vercel/next.js/blob/23681508ca34b66a6ef55965c5eac57de20eb67f/packages/next/src/server/config.ts#L1115
*/
const nextConfigPath = join(root, nextConfig.configFileName);
if (await exists(nextConfigPath)) {
await overrideNextConfig(root, nextConfig.configFileName);
await validateNextConfigOverride(root, opts.projectDirectory, nextConfig.configFileName);
}
const opts = getBuildOptions();
const root = process.cwd();

try {
await runBuild();
const nextConfig = await loadConfig(root, opts.projectDirectory);

const adapterMetadata = getAdapterMetadata();
const nextBuildDirectory = join(opts.projectDirectory, nextConfig.distDir);
const outputBundleOptions = populateOutputBundleOptions(
const nextBuildDirectory = join(opts.projectDirectory, nextConfig.distDir);
const outputBundleOptions = populateOutputBundleOptions(
root,

Check failure on line 18 in packages/@apphosting/adapter-nextjs/src/bin/build.ts

View workflow job for this annotation

GitHub Actions / Lint

Delete `··`
opts.projectDirectory,

Check failure on line 19 in packages/@apphosting/adapter-nextjs/src/bin/build.ts

View workflow job for this annotation

GitHub Actions / Lint

Delete `··`
nextBuildDirectory,

Check failure on line 20 in packages/@apphosting/adapter-nextjs/src/bin/build.ts

View workflow job for this annotation

GitHub Actions / Lint

Delete `··`
);
);

await addRouteOverrides(
outputBundleOptions.outputDirectoryAppPath,
nextConfig.distDir,
adapterMetadata,
);

const nextjsVersion = process.env.FRAMEWORK_VERSION || "unspecified";
await generateBuildOutput(
await generateBuildOutput(

Check failure on line 23 in packages/@apphosting/adapter-nextjs/src/bin/build.ts

View workflow job for this annotation

GitHub Actions / Lint

Replace `⏎····root,⏎····opts.projectDirectory,⏎····outputBundleOptions,⏎····nextBuildDirectory,⏎` with `root,·opts.projectDirectory,·outputBundleOptions,·nextBuildDirectory`
root,
opts.projectDirectory,
outputBundleOptions,
nextBuildDirectory,
nextjsVersion,
adapterMetadata,
);
await validateOutputDirectory(outputBundleOptions, nextBuildDirectory);
} finally {
await restoreNextConfig(root, nextConfig.configFileName);
}
);

await validateOutputDirectory(outputBundleOptions, nextBuildDirectory);
Loading
Loading