1
+ import { spawnSync } from 'child_process' ;
1
2
import { existsSync } from 'fs' ;
2
- import { rm } from 'fs/promises' ;
3
+ import { copyFile , rm , stat , writeFile } from 'fs/promises' ;
4
+ import { basename , join , relative } from 'path' ;
3
5
4
- import { DeployConfig , PathFactory } from '../utils' ;
6
+ import {
7
+ DEFAULT_REGION ,
8
+ COOKIE_VERSION ,
9
+ FIREBASE_ADMIN_VERSION ,
10
+ FIREBASE_FRAMEWORKS_VERSION ,
11
+ FIREBASE_FUNCTIONS_VERSION ,
12
+ LRU_CACHE_VERSION
13
+ } from '../constants' ;
14
+ import { DeployConfig , findDependency , PathFactory } from '../utils' ;
15
+
16
+ const NODE_VERSION = parseInt ( process . versions . node , 10 ) . toString ( ) ;
5
17
6
18
const dynamicImport = ( getProjectPath : PathFactory ) => {
7
19
const exists = ( ...files : string [ ] ) => files . some ( file => existsSync ( getProjectPath ( file ) ) ) ;
@@ -14,5 +26,82 @@ const dynamicImport = (getProjectPath: PathFactory) => {
14
26
export const build = async ( config : DeployConfig | Required < DeployConfig > , getProjectPath : PathFactory ) => {
15
27
const command = await dynamicImport ( getProjectPath ) ;
16
28
await rm ( config . dist , { recursive : true , force : true } ) ;
17
- return command . build ( config , getProjectPath ) ;
29
+ const results = await command . build ( config , getProjectPath ) ;
30
+ const { usingCloudFunctions, packageJson, framework, bootstrapScript, rewrites, redirects, headers } = results ;
31
+ let usesFirebaseConfig = false ;
32
+ if ( usingCloudFunctions ) {
33
+ const firebaseAuthDependency = findDependency ( '@firebase/auth' , getProjectPath ( ) ) ;
34
+ usesFirebaseConfig = ! ! firebaseAuthDependency ;
35
+
36
+ packageJson . main = 'server.js' ;
37
+ delete packageJson . devDependencies ;
38
+ packageJson . dependencies ||= { } ;
39
+ packageJson . dependencies [ 'firebase-frameworks' ] = FIREBASE_FRAMEWORKS_VERSION ;
40
+ const functionsDist = join ( config . dist , 'functions' ) ;
41
+ for ( const [ name , version ] of Object . entries ( packageJson . dependencies as Record < string , string > ) ) {
42
+ if ( version . startsWith ( 'file:' ) ) {
43
+ const path = version . split ( ':' ) [ 1 ] ;
44
+ const stats = await stat ( path ) ;
45
+ if ( stats . isDirectory ( ) ) {
46
+ const result = spawnSync ( 'npm' , [ 'pack' , relative ( functionsDist , path ) ] , { cwd : functionsDist } ) ;
47
+ if ( ! result . stdout ) continue ;
48
+ const filename = result . stdout . toString ( ) . trim ( ) ;
49
+ packageJson . dependencies [ name ] = `file:${ filename } ` ;
50
+ } else {
51
+ const filename = basename ( path ) ;
52
+ await copyFile ( path , join ( functionsDist , filename ) ) ;
53
+ packageJson . dependencies [ name ] = `file:${ filename } ` ;
54
+ }
55
+ }
56
+ }
57
+
58
+ // TODO(jamesdaniels) test these with semver, error if already set out of range
59
+ packageJson . dependencies [ 'firebase-admin' ] ||= FIREBASE_ADMIN_VERSION ;
60
+ packageJson . dependencies [ 'firebase-functions' ] ||= FIREBASE_FUNCTIONS_VERSION ;
61
+ if ( usesFirebaseConfig ) {
62
+ packageJson . dependencies [ 'cookie' ] ||= COOKIE_VERSION ;
63
+ packageJson . dependencies [ 'lru-cache' ] ||= LRU_CACHE_VERSION ;
64
+ }
65
+ packageJson . engines ||= { } ;
66
+ packageJson . engines . node ||= NODE_VERSION ;
67
+
68
+ await writeFile ( join ( functionsDist , 'package.json' ) , JSON . stringify ( packageJson , null , 2 ) ) ;
69
+
70
+ await copyFile ( getProjectPath ( 'package-lock.json' ) , join ( functionsDist , 'package-lock.json' ) ) . catch ( ( ) => { } ) ;
71
+
72
+ const npmInstall = spawnSync ( 'npm' , [ 'i' , '--only' , 'production' , '--no-audit' , '--silent' ] , { cwd : functionsDist } ) ;
73
+ if ( npmInstall . status ) {
74
+ console . error ( npmInstall . output . toString ( ) ) ;
75
+ }
76
+
77
+ // TODO(jamesdaniels) allow configuration of the Cloud Function
78
+ await writeFile ( join ( functionsDist , 'settings.js' ) , `exports.HTTPS_OPTIONS = {};
79
+ exports.FRAMEWORK = '${ framework } ';
80
+ ` ) ;
81
+
82
+ if ( bootstrapScript ) {
83
+ await writeFile ( join ( functionsDist , 'bootstrap.js' ) , bootstrapScript ) ;
84
+ }
85
+ if ( usesFirebaseConfig ) {
86
+ await writeFile ( join ( functionsDist , 'server.js' ) , "exports.ssr = require('firebase-frameworks/server/firebase-aware').ssr;\n" ) ;
87
+ } else {
88
+ await writeFile ( join ( functionsDist , 'server.js' ) , "exports.ssr = require('firebase-frameworks/server').ssr;\n" ) ;
89
+ }
90
+
91
+ await writeFile ( join ( functionsDist , 'functions.yaml' ) , JSON . stringify ( {
92
+ endpoints : {
93
+ [ config . function ! . name ] : {
94
+ platform : 'gcfv2' ,
95
+ region : [ DEFAULT_REGION ] ,
96
+ labels : { } ,
97
+ httpsTrigger : { } ,
98
+ entryPoint : 'ssr'
99
+ }
100
+ } ,
101
+ specVersion : 'v1alpha1' ,
102
+ // TODO(jamesdaniels) add persistent disk if needed
103
+ requiredAPIs : [ ]
104
+ } , null , 2 ) ) ;
105
+ }
106
+ return { usingCloudFunctions, rewrites, redirects, headers, usesFirebaseConfig } ;
18
107
} ;
0 commit comments