11import fsExtra from "fs-extra" ;
22import { join , dirname , relative , normalize } from "path" ;
33import { stringify as yamlStringify } from "yaml" ;
4+ import { createRequire } from "node:module" ;
45
56import {
67 OutputBundleOptions ,
78 AdapterMetadata ,
9+ RoutesManifest ,
810} from "./interfaces.js" ;
911import { OutputBundleConfig , updateOrCreateGitignore } from "@apphosting/common" ;
1012import { fileURLToPath } from "url" ;
1113
14+ import { PHASE_PRODUCTION_BUILD , ROUTES_MANIFEST } from "./constants.js" ;
15+ import type { NextConfigComplete } from "next/dist/server/config-shared.js" ;
16+
1217// fs-extra is CJS, readJson can't be imported using shorthand
1318export const { copy, exists, writeFile, readJson, readdir, readFileSync, existsSync, ensureDir } =
1419 fsExtra ;
@@ -19,6 +24,27 @@ export const isMain = (meta: ImportMeta): boolean => {
1924 return process . argv [ 1 ] === fileURLToPath ( meta . url ) ;
2025} ;
2126
27+ // Loads the user's next.config.js file.
28+ export async function loadConfig ( root : string , projectRoot : string ) : Promise < NextConfigComplete > {
29+ // createRequire() gives us access to Node's CommonJS implementation of require.resolve()
30+ // (https://nodejs.org/api/module.html#modulecreaterequirefilename).
31+ // We use the require.resolve() resolution algorithm to get the path to the next config module,
32+ // which may reside in the node_modules folder at a higher level in the directory structure
33+ // (e.g. for monorepo projects).
34+ // Note that ESM has an equivalent (https://nodejs.org/api/esm.html#importmetaresolvespecifier),
35+ // but the feature is still experimental.
36+ const require = createRequire ( import . meta. url ) ;
37+ const configPath = require . resolve ( "next/dist/server/config.js" , { paths : [ projectRoot ] } ) ;
38+ // dynamically load NextJS so this can be used in an NPX context
39+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
40+ const { default : nextServerConfig } : { default : typeof import ( "next/dist/server/config.js" ) } =
41+ await import ( configPath ) ;
42+
43+ const loadConfig = nextServerConfig . default ;
44+ return await loadConfig ( PHASE_PRODUCTION_BUILD , root ) ;
45+ }
46+
47+
2248/**
2349 * Provides the paths in the output bundle for the built artifacts.
2450 * @param rootDir The root directory of the uploaded source code.
@@ -48,6 +74,33 @@ export function populateOutputBundleOptions(
4874 } ;
4975}
5076
77+ /**
78+ * Loads the route manifest from the standalone directory.
79+ * @param standalonePath The path to the standalone directory.
80+ * @param distDir The path to the dist directory.
81+ * @return The route manifest.
82+ */
83+ export function loadRouteManifest ( distDir : string ) : RoutesManifest {
84+ const manifestPath = join ( distDir , ROUTES_MANIFEST ) ;
85+ const json = readFileSync ( manifestPath , "utf-8" ) ;
86+ return JSON . parse ( json ) as RoutesManifest ;
87+ }
88+
89+
90+ /**
91+ * Writes the route manifest to the standalone directory.
92+ * @param standalonePath The path to the standalone directory.
93+ * @param distDir The path to the dist directory.
94+ * @param customManifest The route manifest to write.
95+ */
96+ export async function writeRouteManifest (
97+ distDir : string ,
98+ customManifest : RoutesManifest ,
99+ ) : Promise < void > {
100+ const manifestPath = join ( distDir , ROUTES_MANIFEST ) ;
101+ await writeFile ( manifestPath , JSON . stringify ( customManifest ) ) ;
102+ }
103+
51104/**
52105 * Copy static assets and other resources into the standlone directory, also generates the bundle.yaml
53106 * @param rootDir The root directory of the uploaded source code.
@@ -59,14 +112,11 @@ export async function generateBuildOutput(
59112 appDir : string ,
60113 opts : OutputBundleOptions ,
61114 nextBuildDirectory : string ,
62- nextVersion : string ,
63- adapterMetadata : AdapterMetadata ,
64115) : Promise < void > {
65116 const staticDirectory = join ( nextBuildDirectory , "static" ) ;
66117 await Promise . all ( [
67118 copy ( staticDirectory , opts . outputStaticDirectoryPath , { overwrite : true } ) ,
68119 copyResources ( appDir , opts . outputDirectoryAppPath , opts . bundleYamlPath ) ,
69- generateBundleYaml ( opts , rootDir , nextVersion , adapterMetadata ) ,
70120 ] ) ;
71121 // generateBundleYaml creates the output directory (if it does not already exist).
72122 // We need to make sure it is gitignored.
@@ -111,7 +161,7 @@ export function getAdapterMetadata(): AdapterMetadata {
111161}
112162
113163// generate bundle.yaml
114- async function generateBundleYaml (
164+ export async function generateBundleYaml (
115165 opts : OutputBundleOptions ,
116166 cwd : string ,
117167 nextVersion : string ,
0 commit comments