22
22
import * as path from "path" ;
23
23
import * as url from "url" ;
24
24
25
- import { ManifestEndpoint , ManifestRequiredAPI , ManifestStack } from "./manifest" ;
25
+ import {
26
+ ManifestEndpoint ,
27
+ ManifestExtension ,
28
+ ManifestRequiredAPI ,
29
+ ManifestStack ,
30
+ } from "./manifest" ;
26
31
27
32
import * as params from "../params" ;
28
33
@@ -59,6 +64,7 @@ export function extractStack(
59
64
module ,
60
65
endpoints : Record < string , ManifestEndpoint > ,
61
66
requiredAPIs : ManifestRequiredAPI [ ] ,
67
+ extensions : Record < string , ManifestExtension > ,
62
68
prefix = ""
63
69
) {
64
70
for ( const [ name , valAsUnknown ] of Object . entries ( module ) ) {
@@ -73,12 +79,96 @@ export function extractStack(
73
79
if ( val . __requiredAPIs && Array . isArray ( val . __requiredAPIs ) ) {
74
80
requiredAPIs . push ( ...val . __requiredAPIs ) ;
75
81
}
76
- } else if ( typeof val === "object" && val !== null ) {
77
- extractStack ( val , endpoints , requiredAPIs , prefix + name + "-" ) ;
82
+ } else if ( isFirebaseRefExtension ( val ) ) {
83
+ extensions [ val . instanceId ] = {
84
+ params : convertExtensionParams ( val . params ) ,
85
+ ref : val . FIREBASE_EXTENSION_REFERENCE ,
86
+ events : val . events ,
87
+ } ;
88
+ } else if ( isFirebaseLocalExtension ( val ) ) {
89
+ extensions [ val . instanceId ] = {
90
+ params : convertExtensionParams ( val . params ) ,
91
+ localPath : val . FIREBASE_EXTENSION_LOCAL_PATH ,
92
+ events : val . events ,
93
+ } ;
94
+ } else if ( isObject ( val ) ) {
95
+ extractStack ( val , endpoints , requiredAPIs , extensions , prefix + name + "-" ) ;
96
+ }
97
+ }
98
+ }
99
+
100
+ function toTitleCase ( txt : string ) : string {
101
+ return txt . charAt ( 0 ) . toUpperCase ( ) + txt . substring ( 1 ) . toLowerCase ( ) ;
102
+ }
103
+
104
+ function snakeToCamelCase ( txt : string ) : string {
105
+ let ret = txt . toLowerCase ( ) ;
106
+ ret = ret . replace ( / _ / g, " " ) ;
107
+ ret = ret . replace ( / \w \S * / g, toTitleCase ) ;
108
+ ret = ret . charAt ( 0 ) . toLowerCase ( ) + ret . substring ( 1 ) ;
109
+ return ret ;
110
+ }
111
+
112
+ function convertExtensionParams ( params : object ) : Record < string , string > {
113
+ const systemPrefixes : Record < string , string > = {
114
+ FUNCTION : "firebaseextensions.v1beta.function" ,
115
+ V2FUNCTION : "firebaseextensions.v1beta.v2function" ,
116
+ } ;
117
+ const converted : Record < string , string > = { } ;
118
+ for ( const [ rawKey , paramVal ] of Object . entries ( params ) ) {
119
+ let key = rawKey ;
120
+ if ( rawKey . startsWith ( "_" ) && rawKey !== "_EVENT_ARC_REGION" ) {
121
+ const prefix = rawKey . substring ( 1 ) . split ( "_" ) [ 0 ] ;
122
+ const suffix = rawKey . substring ( 2 + prefix . length ) ; // 2 for underscores
123
+ key = `${ systemPrefixes [ prefix ] } /${ snakeToCamelCase ( suffix ) } ` ;
124
+ }
125
+ if ( Array . isArray ( paramVal ) ) {
126
+ converted [ key ] = paramVal . join ( "," ) ;
127
+ } else {
128
+ converted [ key ] = paramVal as string ;
78
129
}
79
130
}
131
+ return converted ;
132
+ }
133
+
134
+ function isObject ( value : unknown ) : value is Record < string , unknown > {
135
+ return typeof value === "object" && value !== null ;
136
+ }
137
+
138
+ interface FirebaseLocalExtension {
139
+ FIREBASE_EXTENSION_LOCAL_PATH : string ;
140
+ instanceId : string ;
141
+ params : Record < string , unknown > ;
142
+ events : string [ ] ;
80
143
}
81
144
145
+ const isFirebaseLocalExtension = ( val : unknown ) : val is FirebaseLocalExtension => {
146
+ return (
147
+ isObject ( val ) &&
148
+ typeof val . FIREBASE_EXTENSION_LOCAL_PATH === "string" &&
149
+ typeof val . instanceId === "string" &&
150
+ isObject ( val . params ) &&
151
+ Array . isArray ( val . events )
152
+ ) ;
153
+ } ;
154
+
155
+ interface FirebaseRefExtension {
156
+ FIREBASE_EXTENSION_REFERENCE : string ;
157
+ instanceId : string ;
158
+ params : Record < string , unknown > ;
159
+ events : string [ ] ;
160
+ }
161
+
162
+ const isFirebaseRefExtension = ( val : unknown ) : val is FirebaseRefExtension => {
163
+ return (
164
+ isObject ( val ) &&
165
+ typeof val . FIREBASE_EXTENSION_REFERENCE === "string" &&
166
+ typeof val . instanceId === "string" &&
167
+ isObject ( val . params ) &&
168
+ Array . isArray ( val . events )
169
+ ) ;
170
+ } ;
171
+
82
172
/* @internal */
83
173
export function mergeRequiredAPIs ( requiredAPIs : ManifestRequiredAPI [ ] ) : ManifestRequiredAPI [ ] {
84
174
const apiToReasons : Record < string , Set < string > > = { } ;
@@ -99,14 +189,16 @@ export function mergeRequiredAPIs(requiredAPIs: ManifestRequiredAPI[]): Manifest
99
189
export async function loadStack ( functionsDir : string ) : Promise < ManifestStack > {
100
190
const endpoints : Record < string , ManifestEndpoint > = { } ;
101
191
const requiredAPIs : ManifestRequiredAPI [ ] = [ ] ;
192
+ const extensions : Record < string , ManifestExtension > = { } ;
102
193
const mod = await loadModule ( functionsDir ) ;
103
194
104
- extractStack ( mod , endpoints , requiredAPIs ) ;
195
+ extractStack ( mod , endpoints , requiredAPIs , extensions ) ;
105
196
106
197
const stack : ManifestStack = {
107
198
endpoints,
108
199
specVersion : "v1alpha1" ,
109
200
requiredAPIs : mergeRequiredAPIs ( requiredAPIs ) ,
201
+ extensions,
110
202
} ;
111
203
if ( params . declaredParams . length > 0 ) {
112
204
stack . params = params . declaredParams . map ( ( p ) => p . toSpec ( ) ) ;
0 commit comments