Skip to content

Commit 267ceeb

Browse files
author
Ingrid Fielker
committed
Parse Firebase extensions out of code
1 parent 5145e18 commit 267ceeb

File tree

3 files changed

+115
-8
lines changed

3 files changed

+115
-8
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,8 @@
247247
"yargs": "^15.3.1"
248248
},
249249
"peerDependencies": {
250-
"firebase-admin": "^11.10.0 || ^12.0.0"
250+
"firebase-admin": "^11.10.0 || ^12.0.0",
251+
"firebase-tools": "^13.15.1"
251252
},
252253
"engines": {
253254
"node": ">=14.10.0"

src/runtime/loader.ts

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@
2222
import * as path from "path";
2323
import * as url from "url";
2424

25-
import { ManifestEndpoint, ManifestRequiredAPI, ManifestStack } from "./manifest";
25+
import {
26+
ManifestEndpoint,
27+
ManifestExtension,
28+
ManifestRequiredAPI,
29+
ManifestStack,
30+
} from "./manifest";
2631

2732
import * as params from "../params";
2833

@@ -59,6 +64,7 @@ export function extractStack(
5964
module,
6065
endpoints: Record<string, ManifestEndpoint>,
6166
requiredAPIs: ManifestRequiredAPI[],
67+
extensions: Record<string, ManifestExtension>,
6268
prefix = ""
6369
) {
6470
for (const [name, valAsUnknown] of Object.entries(module)) {
@@ -73,12 +79,96 @@ export function extractStack(
7379
if (val.__requiredAPIs && Array.isArray(val.__requiredAPIs)) {
7480
requiredAPIs.push(...val.__requiredAPIs);
7581
}
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;
78129
}
79130
}
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[];
80143
}
81144

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+
82172
/* @internal */
83173
export function mergeRequiredAPIs(requiredAPIs: ManifestRequiredAPI[]): ManifestRequiredAPI[] {
84174
const apiToReasons: Record<string, Set<string>> = {};
@@ -99,14 +189,16 @@ export function mergeRequiredAPIs(requiredAPIs: ManifestRequiredAPI[]): Manifest
99189
export async function loadStack(functionsDir: string): Promise<ManifestStack> {
100190
const endpoints: Record<string, ManifestEndpoint> = {};
101191
const requiredAPIs: ManifestRequiredAPI[] = [];
192+
const extensions: Record<string, ManifestExtension> = {};
102193
const mod = await loadModule(functionsDir);
103194

104-
extractStack(mod, endpoints, requiredAPIs);
195+
extractStack(mod, endpoints, requiredAPIs, extensions);
105196

106197
const stack: ManifestStack = {
107198
endpoints,
108199
specVersion: "v1alpha1",
109200
requiredAPIs: mergeRequiredAPIs(requiredAPIs),
201+
extensions,
110202
};
111203
if (params.declaredParams.length > 0) {
112204
stack.params = params.declaredParams.map((p) => p.toSpec());

src/runtime/manifest.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,22 @@
2222

2323
import { RESET_VALUE, ResettableKeys, ResetValue } from "../common/options";
2424
import { Expression } from "../params";
25-
import { WireParamSpec } from "../params/types";
25+
import { WireParamSpec, SecretParam } from "../params/types";
2626

2727
/**
28-
* An definition of a function as appears in the Manifest.
28+
* A definition of an extension as appears in the Manifest.
29+
*
30+
* @alpha
31+
*/
32+
export interface ManifestExtension {
33+
params: Record<string, string | SecretParam>;
34+
ref?: string;
35+
localPath?: string;
36+
events: string[];
37+
}
38+
39+
/**
40+
* A definition of a function as appears in the Manifest.
2941
*
3042
* @alpha
3143
*/
@@ -113,14 +125,16 @@ export interface ManifestRequiredAPI {
113125
}
114126

115127
/**
116-
* An definition of a function deployment as appears in the Manifest.
128+
* A definition of a function/extension deployment as appears in the Manifest.
129+
*
117130
* @alpha
118131
*/
119132
export interface ManifestStack {
120133
specVersion: "v1alpha1";
121134
params?: WireParamSpec<any>[];
122135
requiredAPIs: ManifestRequiredAPI[];
123136
endpoints: Record<string, ManifestEndpoint>;
137+
extensions: Record<string, ManifestExtension>;
124138
}
125139

126140
/**

0 commit comments

Comments
 (0)