6
6
import { isIPv4 } from "net" ;
7
7
import * as clc from "colorette" ;
8
8
import { checkListenable } from "../portUtils" ;
9
- import { detectStartCommand } from "./developmentServer" ;
9
+ import { detectPackageManager , detectStartCommand } from "./developmentServer" ;
10
10
import { DEFAULT_HOST , DEFAULT_PORTS } from "../constants" ;
11
11
import { spawnWithCommandString } from "../../init/spawn" ;
12
12
import { logger } from "./developmentServer" ;
@@ -17,10 +17,17 @@ import { EmulatorRegistry } from "../registry";
17
17
import { setEnvVarsForEmulators } from "../env" ;
18
18
import { FirebaseError } from "../../error" ;
19
19
import * as secrets from "../../gcp/secretManager" ;
20
- import { logLabeledError } from "../../utils" ;
20
+ import { logLabeledError , logLabeledWarning } from "../../utils" ;
21
+ import * as apphosting from "../../gcp/apphosting" ;
22
+ import { Constants } from "../constants" ;
23
+ import { constructDefaultWebSetup , WebConfig } from "../../fetchWebSetup" ;
24
+ import { AppPlatform , getAppConfig } from "../../management/apps" ;
25
+ import { spawnSync } from "child_process" ;
26
+ import { gte as semverGte } from "semver" ;
21
27
22
28
interface StartOptions {
23
29
projectId ?: string ;
30
+ backendId ?: string ;
24
31
port ?: number ;
25
32
startCommand ?: string ;
26
33
rootDirectory ?: string ;
@@ -41,7 +48,13 @@ export async function start(options?: StartOptions): Promise<{ hostname: string;
41
48
port += 1 ;
42
49
}
43
50
44
- await serve ( options ?. projectId , port , options ?. startCommand , options ?. rootDirectory ) ;
51
+ await serve (
52
+ options ?. projectId ,
53
+ options ?. backendId ,
54
+ port ,
55
+ options ?. startCommand ,
56
+ options ?. rootDirectory ,
57
+ ) ;
45
58
46
59
return { hostname, port } ;
47
60
}
@@ -102,6 +115,7 @@ async function loadSecret(project: string | undefined, name: string): Promise<st
102
115
*/
103
116
async function serve (
104
117
projectId : string | undefined ,
118
+ backendId : string | undefined ,
105
119
port : number ,
106
120
startCommand ?: string ,
107
121
backendRelativeDir ?: string ,
@@ -115,11 +129,34 @@ async function serve(
115
129
value . value ? value . value : await loadSecret ( projectId , value . secret ! ) ,
116
130
] ) ;
117
131
118
- const environmentVariablesToInject = {
132
+ const environmentVariablesToInject : NodeJS . ProcessEnv = {
133
+ NODE_ENV : process . env . NODE_ENV ,
119
134
...getEmulatorEnvs ( ) ,
120
135
...Object . fromEntries ( await Promise . all ( resolveEnv ) ) ,
136
+ FIREBASE_APP_HOSTING : "1" ,
137
+ X_GOOGLE_TARGET_PLATFORM : "fah" ,
138
+ GCLOUD_PROJECT : projectId ,
139
+ PROJECT_ID : projectId ,
121
140
PORT : port . toString ( ) ,
122
141
} ;
142
+
143
+ const packageManager = await detectPackageManager ( backendRoot ) . catch ( ( ) => undefined ) ;
144
+ if ( packageManager === "pnpm" ) {
145
+ // TODO(jamesdaniels) look into pnpm support for autoinit
146
+ logLabeledWarning ( "apphosting" , `Firebase JS SDK autoinit does not currently support PNPM.` ) ;
147
+ } else {
148
+ const webappConfig = await getBackendAppConfig ( projectId , backendId ) ;
149
+ if ( webappConfig ) {
150
+ environmentVariablesToInject [ "FIREBASE_WEBAPP_CONFIG" ] ||= JSON . stringify ( webappConfig ) ;
151
+ environmentVariablesToInject [ "FIREBASE_CONFIG" ] ||= JSON . stringify ( {
152
+ databaseURL : webappConfig . databaseURL ,
153
+ storageBucket : webappConfig . storageBucket ,
154
+ projectId : webappConfig . projectId ,
155
+ } ) ;
156
+ }
157
+ await tripFirebasePostinstall ( backendRoot , environmentVariablesToInject ) ;
158
+ }
159
+
123
160
if ( startCommand ) {
124
161
logger . logLabeled (
125
162
"BULLET" ,
@@ -167,3 +204,89 @@ export function getEmulatorEnvs(): Record<string, string> {
167
204
168
205
return envs ;
169
206
}
207
+
208
+ type Dependency = {
209
+ name : string ;
210
+ version : string ;
211
+ path : string ;
212
+ dependencies ?: Record < string , Dependency > ;
213
+ } ;
214
+
215
+ async function tripFirebasePostinstall (
216
+ rootDirectory : string ,
217
+ env : NodeJS . ProcessEnv ,
218
+ ) : Promise < void > {
219
+ const npmLs = spawnSync ( "npm" , [ "ls" , "@firebase/util" , "--json" , "--long" ] , {
220
+ cwd : rootDirectory ,
221
+ shell : process . platform === "win32" ,
222
+ } ) ;
223
+ if ( ! npmLs . stdout ) {
224
+ return ;
225
+ }
226
+ const npmLsResults = JSON . parse ( npmLs . stdout . toString ( ) . trim ( ) ) ;
227
+ const dependenciesToSearch : Dependency [ ] = Object . values ( npmLsResults . dependencies ) ;
228
+ const firebaseUtilPaths : string [ ] = [ ] ;
229
+ for ( const dependency of dependenciesToSearch ) {
230
+ if (
231
+ dependency . name === "@firebase/util" &&
232
+ semverGte ( dependency . version , "1.11.0" ) &&
233
+ firebaseUtilPaths . indexOf ( dependency . path ) === - 1
234
+ ) {
235
+ firebaseUtilPaths . push ( dependency . path ) ;
236
+ }
237
+ if ( dependency . dependencies ) {
238
+ dependenciesToSearch . push ( ...Object . values ( dependency . dependencies ) ) ;
239
+ }
240
+ }
241
+
242
+ await Promise . all (
243
+ firebaseUtilPaths . map (
244
+ ( path ) =>
245
+ new Promise < void > ( ( resolve ) => {
246
+ spawnSync ( "npm" , [ "run" , "postinstall" ] , {
247
+ cwd : path ,
248
+ env,
249
+ stdio : "ignore" ,
250
+ shell : process . platform === "win32" ,
251
+ } ) ;
252
+ resolve ( ) ;
253
+ } ) ,
254
+ ) ,
255
+ ) ;
256
+ }
257
+
258
+ async function getBackendAppConfig (
259
+ projectId ?: string ,
260
+ backendId ?: string ,
261
+ ) : Promise < WebConfig | undefined > {
262
+ if ( ! projectId ) {
263
+ return undefined ;
264
+ }
265
+
266
+ if ( Constants . isDemoProject ( projectId ) ) {
267
+ return constructDefaultWebSetup ( projectId ) ;
268
+ }
269
+
270
+ if ( ! backendId ) {
271
+ return undefined ;
272
+ }
273
+
274
+ const backendsList = await apphosting . listBackends ( projectId , "-" ) . catch ( ( ) => undefined ) ;
275
+ const backend = backendsList ?. backends . find (
276
+ ( b ) => apphosting . parseBackendName ( b . name ) . id === backendId ,
277
+ ) ;
278
+
279
+ if ( ! backend ) {
280
+ logLabeledWarning (
281
+ "apphosting" ,
282
+ `Unable to lookup details for backend ${ backendId } . Firebase SDK autoinit will not be available.` ,
283
+ ) ;
284
+ return undefined ;
285
+ }
286
+
287
+ if ( ! backend . appId ) {
288
+ return undefined ;
289
+ }
290
+
291
+ return ( await getAppConfig ( backend . appId , AppPlatform . WEB ) ) as WebConfig ;
292
+ }
0 commit comments