1+ import * as childProcess from "child_process" ;
2+ import * as fs from "fs" ;
3+ import * as path from "path" ;
14import { BuildConfig , Env } from "../gcp/apphosting" ;
25import { localBuild as localAppHostingBuild } from "@apphosting/build" ;
36import { EnvMap } from "./yaml" ;
47import { loadSecret } from "./secrets" ;
58import { confirm } from "../prompt" ;
69import { FirebaseError } from "../error" ;
10+ import * as experiments from "../experiments" ;
11+
12+ interface UniversalMakerOutput {
13+ command : string ;
14+ args : string [ ] ;
15+ language : string ;
16+ runtime : string ;
17+ envVars ?: Record < string , string | number | boolean > ;
18+ }
19+
20+ /**
21+ * Runs the Universal Maker binary to build the project.
22+ */
23+ export function runUniversalMaker ( projectRoot : string , framework ?: string ) : AppHostingBuildOutput {
24+ if ( ! process . env . UNIVERSAL_MAKER_BINARY ) {
25+ throw new FirebaseError (
26+ "Please specify the path to your Universal Maker binary by establishing the UNIVERSAL_MAKER_BINARY environment variable." ,
27+ ) ;
28+ }
29+
30+ try {
31+ childProcess . spawnSync (
32+ process . env . UNIVERSAL_MAKER_BINARY ,
33+ [ "-application_dir" , projectRoot , "-output_dir" , projectRoot , "-output_format" , "json" ] ,
34+ {
35+ env : {
36+ ...process . env ,
37+ X_GOOGLE_TARGET_PLATFORM : "fah" ,
38+ FIREBASE_OUTPUT_BUNDLE_DIR : ".apphosting" ,
39+ NPM_CONFIG_REGISTRY : "https://registry.npmjs.org/" ,
40+ } ,
41+ stdio : "inherit" ,
42+ } ,
43+ ) ;
44+ } catch ( e ) {
45+ if ( e && typeof e === "object" && "code" in e && e . code === "EACCES" ) {
46+ throw new FirebaseError (
47+ "Failed to execute the Universal Maker binary due to permission constraints. Please assure you have set chmod +x on your file." ,
48+ ) ;
49+ }
50+ throw e ;
51+ }
52+
53+ const outputFilePath = path . join ( projectRoot , "build_output.json" ) ;
54+ if ( ! fs . existsSync ( outputFilePath ) ) {
55+ throw new FirebaseError (
56+ `Universal Maker did not produce the expected output file at ${ outputFilePath } ` ,
57+ ) ;
58+ }
59+
60+ const outputRaw = fs . readFileSync ( outputFilePath , "utf-8" ) ;
61+ let umOutput : UniversalMakerOutput ;
62+ try {
63+ umOutput = JSON . parse ( outputRaw ) as UniversalMakerOutput ;
64+ } catch ( e ) {
65+ throw new FirebaseError ( `Failed to parse build_output.json: ${ ( e as Error ) . message } ` ) ;
66+ }
67+
68+ return {
69+ metadata : {
70+ language : umOutput . language ,
71+ runtime : umOutput . runtime ,
72+ framework : framework || "nextjs" ,
73+ } ,
74+ runConfig : {
75+ runCommand : `${ umOutput . command } ${ umOutput . args . join ( " " ) } ` ,
76+ environmentVariables : Object . entries ( umOutput . envVars || { } ) . map ( ( [ k , v ] ) => ( {
77+ variable : k ,
78+ value : String ( v ) ,
79+ availability : [ "RUNTIME" ] ,
80+ } ) ) ,
81+ } ,
82+ outputFiles : {
83+ serverApp : {
84+ include : [ ".apphosting" ] ,
85+ } ,
86+ } ,
87+ } ;
88+ }
89+
90+ export interface AppHostingBuildOutput {
91+ metadata : Record < string , string | number | boolean > ;
92+
93+ runConfig : {
94+ runCommand ?: string ;
95+ environmentVariables ?: Array < {
96+ variable : string ;
97+ value : string ;
98+ availability : string [ ] ;
99+ } > ;
100+ } ;
101+ outputFiles ?: {
102+ serverApp : {
103+ include : string [ ] ;
104+ } ;
105+ } ;
106+ }
7107
8108/**
9109 * Triggers a local build of your App Hosting codebase.
10110 *
11111 * This function orchestrates the build process using the App Hosting build adapter.
112+ *
12113 * It detects the framework (though currently defaults/assumes 'nextjs' in some contexts),
13114 * generates the necessary build artifacts, and returns metadata about the build.
14115 * @param projectId - The project ID to use for resolving secrets.
@@ -62,9 +163,16 @@ export async function localBuild(
62163 process . env [ key ] = value ;
63164 }
64165
65- let apphostingBuildOutput ;
166+ let apphostingBuildOutput : AppHostingBuildOutput ;
66167 try {
67- apphostingBuildOutput = await localAppHostingBuild ( projectRoot , framework ) ;
168+ if ( experiments . isEnabled ( "universalMaker" ) ) {
169+ apphostingBuildOutput = runUniversalMaker ( projectRoot , framework ) ;
170+ } else {
171+ apphostingBuildOutput = ( await localAppHostingBuild (
172+ projectRoot ,
173+ framework ,
174+ ) ) as unknown as AppHostingBuildOutput ;
175+ }
68176 } finally {
69177 for ( const key in process . env ) {
70178 if ( ! ( key in originalEnv ) ) {
@@ -87,7 +195,7 @@ export async function localBuild(
87195 value,
88196 availability,
89197 } ) ,
90- ) ;
198+ ) as unknown as Env [ ] | undefined ;
91199
92200 return {
93201 outputFiles : apphostingBuildOutput . outputFiles ?. serverApp . include ?? [ ] ,
0 commit comments