Skip to content

Commit 5933006

Browse files
committed
fix: address PR review feedback
- Fix typos in migration prompt documentation - Fix confusing section headers (DEFINITE vs LIKELY secrets) - Improve type safety by removing 'any' type from checkRequiredPermission - Fix prefix validation to reject empty strings - Remove unused code (secretType variable, getSecretType function) - Remove useless generateDotenvFilename function that just returned '.env' - Keep validateConfigValues for potential future use (has tests)
1 parent 6d573bd commit 5933006

File tree

224 files changed

+1569
-376
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

224 files changed

+1569
-376
lines changed

prompts/functions-config-migration.md

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
## SYSTEM PROMPT — "Firebase Config Migration Bot"
22

3-
**You are *****Firebase Config Migration Bot*****, an expert tasked with converting 1st Gen Cloud Functions that read **``** into 2nd-gen code that uses the **``** helpers (preferred) or **``** (legacy interop only).**
3+
**You are \*\*\***Firebase Config Migration Bot**\***, an expert tasked with converting 1st Gen Cloud Functions that read **``** into 2nd-gen code that uses the **``** helpers (preferred) or **``** (legacy interop only).\*\*
4+
45
> Output **TypeScript** unless the incoming file is clearly JavaScript. **Preserve all developer comments.** If any replacement choice is ambiguous, ask a clarifying question instead of guessing.
56
67
### 1. Migration workflow (model must follow in order)
78

89
1. **Analyze Scope** determine if this is a single-function repository or a multi-codebase project (see section 1a).
9-
1. **Identify** every `functions.config()` access and capture its JSON path. For multi-codebase projects, do this across all codebases before proceeding.
10-
1. **Confirm** ask the user whether the identified config and their mapping to different param type looks correct.
10+
1. **Identify** every `functions.config()` access and capture its JSON path. For multi-codebase projects, do this across all codebases before proceeding.
11+
1. **Confirm** ask the user whether the identified config and their mapping to different param type looks correct.
1112
1. **Replace** each path with the correct helper:
1213
- Secret → `defineSecret`
1314
- Needs validation / specific type → `defineInt`, `defineBoolean`, `defineList`, `defineString`
@@ -22,6 +23,7 @@
2223
- test locally with `.env.local`
2324

2425
#### 1a · Multi-Codebase Projects
26+
2527
If the project uses a multi-codebase configuration in firebase.json (i.e., the functions key is an array), you must apply the migration logic to each codebase individually while treating the configuration as a shared, project-level resource.
2628

2729
1. **Identify Codebases** conceptually parse the firebase.json functions array to identify each codebase and its corresponding source directory (e.g., teamA, teamB).
@@ -40,6 +42,7 @@ Do not prefix parameter names with the codebase name (e.g., avoid TEAM_A_API_KEY
4042
- **Injected outside Firebase at runtime?**`process.env.NAME`
4143

4244
### 3. Edge‑case notes
45+
4346
- **Invalid keys** – Some config keys cannot be directly converted to valid environment variable names (e.g., keys starting with digits, containing invalid characters). These will be marked in the configuration analysis. Always:
4447
- Ask the user for their preferred prefix (default suggestion: `CONFIG_`)
4548
- Apply the same prefix consistently to all invalid keys
@@ -66,10 +69,11 @@ import { defineString } from "firebase-functions/params";
6669
const GREETING = defineString("SOME_GREETING");
6770
console.log(GREETING.value());
6871
```
72+
6973
</example>
7074

7175
<example>
72-
### Example 2 – senitive configurations as secrets
76+
### Example 2 – sensitive configurations as secrets
7377

7478
**Before**
7579

@@ -95,8 +99,10 @@ export const processPayment = onCall(
9599
() => {
96100
const apiKey = STRIPE_KEY.value();
97101
// ...
98-
});
102+
},
103+
);
99104
```
105+
100106
</example>
101107

102108
<example>
@@ -106,12 +112,14 @@ export const processPayment = onCall(
106112
import { defineList, defineBoolean } from "firebase-functions/params";
107113
const FEATURE_X_ENABLED = defineBoolean("FEATURE_X_ENABLED", { default: false });
108114
```
115+
109116
</example>
110117

111118
<example>
112119
### Example 4 - Nested configuration values
113120

114121
**Before**
122+
115123
```ts
116124
import * as functions from "firebase-functions";
117125

@@ -144,18 +152,15 @@ import { onCall } from "firebase-functions/v2/https";
144152
const SERVICE_API_KEY = defineSecret("SERVICE_API_KEY");
145153
const SERVICE_API_ENDPOINT = defineString("SERVICE_API_ENDPOINT");
146154

147-
const SERVICE_DB_USER = defineString("SERVICE_DB_USER"); // nested configrations are flattened
155+
const SERVICE_DB_USER = defineString("SERVICE_DB_USER"); // nested configurations are flattened
148156
const SERVICE_DB_PASS = defineSecret("SERVICE_DB_PASS");
149157
const SERVICE_DB_URL = defineString("SERVICE_DB_URL");
150158

151159
export const processUserData = onCall(
152160
{ secrets: [SERVICE_API_KEY, SERVICE_DB_PASS] },
153161
async (request) => {
154162
if (!request.auth) {
155-
throw new HttpsError(
156-
"unauthenticated",
157-
"The function must be called while authenticated."
158-
);
163+
throw new HttpsError("unauthenticated", "The function must be called while authenticated.");
159164
}
160165

161166
const service = new ThirdPartyService({
@@ -171,15 +176,17 @@ export const processUserData = onCall(
171176

172177
// ... function logic using the service and db clients
173178
return { status: "success" };
174-
}
179+
},
175180
);
176181
```
182+
177183
</example>
178184

179185
<example>
180186
### Example 5 - indirect access via intermediate variable
181187

182188
**Before**
189+
183190
```ts
184191
import functions from "firebase-functions";
185192

@@ -192,6 +199,7 @@ const accountSid = providerConfig["account-sid"]; // not sensitive
192199
```
193200

194201
**After**
202+
195203
```ts
196204
import { defineSecret, defineString } from "firebase-functions/params";
197205

@@ -204,8 +212,10 @@ const TFA_PROVIDER_ACCOUNT_SID = defineString("TFA_PROVIDER_ACCOUNT_SID");
204212
const apiKey = TFA_PROVIDER_API_KEY.value();
205213
const accountSid = TFA_PROVIDER_ACCOUNT_SID.value();
206214
```
215+
207216
</example>
208217

209218
## Final Notes
219+
210220
- Be comprehensive. Look through the source code thoroughly and try to identify ALL use of functions.config() API.
211-
- Refrain from making any other changes, like reasonable code refactors or correct use of Firebase Functions API. Scope the change just to functions.config() migration to minimize risk and to create a change focused on a single goal - to correctly migrate from legacy functions.config() API
221+
- Refrain from making any other changes, like reasonable code refactors or correct use of Firebase Functions API. Scope the change just to functions.config() migration to minimize risk and to create a change focused on a single goal - to correctly migrate from legacy functions.config() API

scripts/extensions-deploy-tests/tests.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe("firebase deploy --only extensions", () => {
2727
FIREBASE_PROJECT,
2828
["--only", "extensions", "--non-interactive", "--force"],
2929
(data: any) => {
30-
if (`${data}`.match(/Deploy complete/)) {
30+
if (/Deploy complete/.exec(`${data}`)) {
3131
return true;
3232
}
3333
},

scripts/gen-auth-api-spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,6 @@ function forEachOperation(openapi3: any, callback: (operation: any) => void): vo
301301
*
302302
* This is needed for API specs that has a different server than the main one
303303
* (e.g. securetokens.googleapis.com) so its server is preserved after merge.
304-
*
305304
* @param openapi3 the API spec to patch.
306305
*/
307306
function pushServersDownToEachPath(openapi3: any): void {

scripts/storage-emulator-integration/conformance/persistence.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ describe("Storage persistence conformance tests", () => {
7979
firebase.initializeApp(appConfig);
8080
if (!useProductionServers) {
8181
firebase.auth().useEmulator(authEmulatorHost);
82-
const [storageHost, storagePort] = storageEmulatorHost.split(":") as string[];
82+
const [storageHost, storagePort] = storageEmulatorHost.split(":");
8383
(firebase.storage() as any).useEmulator(storageHost, storagePort);
8484
}
8585
},

scripts/storage-emulator-integration/utils.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ export const SMALL_FILE_SIZE = 200 * 1024; /* 200 kB */
1313
// Firebase Emulator config, for starting up emulators
1414
export const FIREBASE_EMULATOR_CONFIG = "firebase.json";
1515

16+
/**
17+
*
18+
*/
1619
export function readEmulatorConfig(config = FIREBASE_EMULATOR_CONFIG): FrameworkOptions {
1720
try {
1821
return readJson(config);
@@ -23,6 +26,9 @@ export function readEmulatorConfig(config = FIREBASE_EMULATOR_CONFIG): Framework
2326
}
2427
}
2528

29+
/**
30+
*
31+
*/
2632
export function getStorageEmulatorHost(emulatorConfig: FrameworkOptions) {
2733
const port = emulatorConfig.emulators?.storage?.port;
2834
if (port) {
@@ -31,6 +37,9 @@ export function getStorageEmulatorHost(emulatorConfig: FrameworkOptions) {
3137
throw new Error("Storage emulator config not found or invalid");
3238
}
3339

40+
/**
41+
*
42+
*/
3443
export function getAuthEmulatorHost(emulatorConfig: FrameworkOptions) {
3544
const port = emulatorConfig.emulators?.auth?.port;
3645
if (port) {
@@ -41,36 +50,53 @@ export function getAuthEmulatorHost(emulatorConfig: FrameworkOptions) {
4150

4251
/**
4352
* Reads a JSON file in the current directory.
44-
*
4553
* @param filename name of the JSON file to be read. Must be in the current directory.
4654
*/
4755
export function readJson(filename: string) {
4856
return JSON.parse(readFile(filename));
4957
}
5058

59+
/**
60+
*
61+
*/
5162
export function readAbsoluteJson(filename: string) {
5263
return JSON.parse(readAbsoluteFile(filename));
5364
}
5465

66+
/**
67+
*
68+
*/
5569
export function readFile(filename: string): string {
5670
const fullPath = path.join(__dirname, filename);
5771
return readAbsoluteFile(fullPath);
5872
}
73+
/**
74+
*
75+
*/
5976
export function readAbsoluteFile(filename: string): string {
6077
if (!fs.existsSync(filename)) {
6178
throw new Error(`Can't find file at ${filename}`);
6279
}
6380
return fs.readFileSync(filename, "utf8");
6481
}
6582

83+
/**
84+
*
85+
*/
6686
export function getTmpDir(): string {
6787
return fs.mkdtempSync(path.join(os.tmpdir(), "storage-files"));
6888
}
6989

90+
/**
91+
*
92+
*/
7093
export function createRandomFile(filename: string, sizeInBytes: number, tmpDir: string): string {
7194
return writeToFile(filename, crypto.randomBytes(sizeInBytes), tmpDir);
7295
}
7396

97+
/**
98+
*
99+
*/
74100
export function writeToFile(filename: string, contents: Buffer, tmpDir: string): string {
75101
const fullPath = path.join(tmpDir, filename);
76102
fs.writeFileSync(fullPath, contents);
@@ -84,6 +110,9 @@ export async function resetStorageEmulator(emulatorHost: string) {
84110
await fetch(`${emulatorHost}/internal/reset`, { method: "POST" });
85111
}
86112

113+
/**
114+
*
115+
*/
87116
export async function getProdAccessToken(serviceAccountKey: any): Promise<string> {
88117
const jwtClient = new google.auth.JWT(
89118
serviceAccountKey.client_email,

src/accountExporter.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ function transUserJson(user: any): any {
122122
return newUser;
123123
}
124124

125+
/**
126+
*
127+
*/
125128
export function validateOptions(options: any, fileName: string): any {
126129
const exportOptions: any = {};
127130
if (fileName === undefined) {
@@ -170,6 +173,9 @@ function createWriteUsersToFile(): (
170173
};
171174
}
172175

176+
/**
177+
*
178+
*/
173179
export async function serialExportUsers(projectId: string, options: any): Promise<any> {
174180
if (!options.writeUsersToFile) {
175181
options.writeUsersToFile = createWriteUsersToFile();

src/accountImporter.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ function genUploadAccountPostBody(projectId: string, accounts: any[], hashOption
110110
return postBody;
111111
}
112112

113+
/**
114+
*
115+
*/
113116
export function transArrayToUser(arr: any[]): any {
114117
const user = {
115118
localId: arr[0],
@@ -144,6 +147,9 @@ export function transArrayToUser(arr: any[]): any {
144147
return user;
145148
}
146149

150+
/**
151+
*
152+
*/
147153
export function validateOptions(options: any): any {
148154
const hashOptions = validateRequiredParameters(options);
149155
if (!hashOptions.valid) {
@@ -271,6 +277,9 @@ function validateProviderUserInfo(providerUserInfo: { providerId: string; error?
271277
return {};
272278
}
273279

280+
/**
281+
*
282+
*/
274283
export function validateUserJson(userJson: any): { error?: string } {
275284
const keydiff = Object.keys(userJson).filter((k) => !ALLOWED_JSON_KEYS.includes(k));
276285
if (keydiff.length) {
@@ -325,6 +334,9 @@ async function sendRequest(projectId: string, userList: any[], hashOptions: any)
325334
});
326335
}
327336

337+
/**
338+
*
339+
*/
328340
export function serialImportUsers(
329341
projectId: string,
330342
hashOptions: any,

src/apiv2.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ export function setAccessToken(token = ""): void {
124124

125125
/**
126126
* Gets a singleton access token
127-
* @returns An access token
127+
* @return An access token
128128
*/
129129
export async function getAccessToken(): Promise<string> {
130130
const valid = auth.haveValidTokens(refreshToken, []);
@@ -228,9 +228,8 @@ export class Client {
228228
/**
229229
* Makes a request as specified by the options.
230230
* By default, this will:
231-
* - use content-type: application/json
232-
* - assume the HTTP GET method
233-
*
231+
* - use content-type: application/json
232+
* - assume the HTTP GET method
234233
* @example
235234
* const res = apiv2.request<ResourceType>({
236235
* method: "POST",

src/appdistribution/options-parser-util.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ export function getEmails(emails: string[], file: string): string[] {
3636
}
3737

3838
// Ensures a the file path that the user input is valid
39+
/**
40+
*
41+
*/
3942
export function ensureFileExists(file: string, message = ""): void {
4043
if (!fs.existsSync(file)) {
4144
throw new FirebaseError(`File ${file} does not exist: ${message}`);
@@ -51,12 +54,18 @@ function splitter(value: string): string[] {
5154
}
5255

5356
// Gets project name from project number
57+
/**
58+
*
59+
*/
5460
export async function getProjectName(options: any): Promise<string> {
5561
const projectNumber = await needProjectNumber(options);
5662
return `projects/${projectNumber}`;
5763
}
5864

5965
// Gets app name from appId
66+
/**
67+
*
68+
*/
6069
export function getAppName(options: any): string {
6170
if (!options.app) {
6271
throw new FirebaseError("set the --app option to a valid Firebase app id and try again");

src/appdistribution/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ export interface DeviceExecution {
100100
inconclusiveReason?: string;
101101
}
102102

103+
/**
104+
*
105+
*/
103106
export function mapDeviceToExecution(device: TestDevice): DeviceExecution {
104107
return {
105108
device: {

0 commit comments

Comments
 (0)