Skip to content

Commit 8fdad4c

Browse files
committed
response to comments
1 parent fe55bb4 commit 8fdad4c

File tree

1 file changed

+92
-38
lines changed

1 file changed

+92
-38
lines changed

src/commands/functions-config-export.ts

Lines changed: 92 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import * as clc from "colorette";
2+
import * as semver from "semver";
23

34
import * as functionsConfig from "../functionsConfig";
45
import { Command } from "../command";
56
import { FirebaseError } from "../error";
6-
import { input } from "../prompt";
7+
import { input, confirm } from "../prompt";
78
import { requirePermissions } from "../requirePermissions";
8-
import { logBullet, logWarning, logSuccess } from "../utils";
9+
import { logBullet, logSuccess } from "../utils";
910
import { requireConfig } from "../requireConfig";
1011
import { ensureValidKey, ensureSecret } from "../functions/secrets";
11-
import { addVersion, toSecretVersionResourceName } from "../gcp/secretManager";
12+
import { addVersion, listSecretVersions, toSecretVersionResourceName } from "../gcp/secretManager";
1213
import { needProjectId } from "../projectUtils";
1314
import { requireAuth } from "../requireAuth";
1415
import { ensureApi } from "../gcp/secretManager";
@@ -29,6 +30,17 @@ const SECRET_MANAGER_PERMISSIONS = [
2930
"secretmanager.versions.add",
3031
];
3132

33+
function maskConfigValues(obj: any): any {
34+
if (typeof obj === "object" && obj !== null && !Array.isArray(obj)) {
35+
const masked: Record<string, any> = {};
36+
for (const [key, value] of Object.entries(obj)) {
37+
masked[key] = maskConfigValues(value);
38+
}
39+
return masked;
40+
}
41+
return "******";
42+
}
43+
3244
export const command = new Command("functions:config:export")
3345
.description("export environment config as a JSON secret to store in Cloud Secret Manager")
3446
.option("--secret <name>", "name of the secret to create (default: RUNTIME_CONFIG)")
@@ -72,26 +84,48 @@ export const command = new Command("functions:config:export")
7284
// Display config in interactive mode
7385
if (!options.nonInteractive) {
7486
logBullet(clc.bold("Configuration to be exported:"));
75-
logWarning("This may contain sensitive data. Do not share this output.");
76-
console.log("");
77-
console.log(JSON.stringify(configJson, null, 2));
87+
console.log(JSON.stringify(maskConfigValues(configJson), null, 2));
7888
console.log("");
7989
}
8090

81-
const defaultSecretName = "RUNTIME_CONFIG";
91+
const defaultSecretName = "FUNCTIONS_CONFIG_EXPORT";
8292
let secretName = options.secret as string;
8393
if (!secretName) {
84-
secretName = await input({
85-
message: "What would you like to name the new secret for your configuration?",
86-
default: defaultSecretName,
87-
nonInteractive: options.nonInteractive,
88-
force: options.force,
89-
});
94+
if (options.force) {
95+
secretName = defaultSecretName;
96+
} else {
97+
secretName = await input({
98+
message: "What would you like to name the new secret for your configuration?",
99+
default: defaultSecretName,
100+
nonInteractive: options.nonInteractive,
101+
});
102+
}
90103
}
91104

92105
const key = await ensureValidKey(secretName, options);
93106
await ensureSecret(projectId, key, options);
94107

108+
const versions = await listSecretVersions(projectId, key);
109+
const enabledVersions = versions.filter((v) => v.state === "ENABLED");
110+
enabledVersions.sort((a, b) => (b.createTime || "").localeCompare(a.createTime || ""));
111+
const latest = enabledVersions[0];
112+
113+
if (latest) {
114+
logBullet(
115+
`Secret ${clc.bold(key)} already exists (latest version: ${clc.bold(latest.versionId)}, created: ${latest.createTime}).`,
116+
);
117+
const proceed = await confirm({
118+
message: "Do you want to add a new version to this secret?",
119+
default: false,
120+
nonInteractive: options.nonInteractive,
121+
force: options.force,
122+
});
123+
if (!proceed) {
124+
return;
125+
}
126+
console.log("");
127+
}
128+
95129
const secretValue = JSON.stringify(configJson, null, 2);
96130

97131
// Check size limit (64KB)
@@ -111,32 +145,52 @@ export const command = new Command("functions:config:export")
111145
console.log("");
112146
logBullet(clc.bold("To complete the migration, update your code:"));
113147
console.log("");
114-
console.log(clc.gray(" // Before:"));
115-
console.log(clc.gray(` const functions = require('firebase-functions');`));
116-
console.log(clc.gray(` `));
117-
console.log(clc.gray(` exports.myFunction = functions.https.onRequest((req, res) => {`));
118-
console.log(clc.gray(` const apiKey = functions.config().service.key;`));
119-
console.log(clc.gray(` // ...`));
120-
console.log(clc.gray(` });`));
121-
console.log("");
122-
console.log(clc.gray(" // After:"));
123-
console.log(clc.gray(` const functions = require('firebase-functions');`));
124-
console.log(clc.gray(` const { defineJsonSecret } = require('firebase-functions/params');`));
125-
console.log(clc.gray(` `));
126-
console.log(clc.gray(` const config = defineJsonSecret("${key}");`));
127-
console.log(clc.gray(` `));
128-
console.log(clc.gray(` exports.myFunction = functions`));
129-
console.log(clc.gray(` .runWith({ secrets: [config] }) // Bind secret here`));
130-
console.log(clc.gray(` .https.onRequest((req, res) => {`));
131-
console.log(clc.gray(` const apiKey = config.value().service.key;`));
132-
console.log(clc.gray(` // ...`));
133-
console.log(clc.gray(` });`));
134-
console.log("");
135-
logBullet(
136-
clc.bold("Note: ") +
137-
"defineJsonSecret requires firebase-functions v6.6.0 or later. " +
138-
"Update your package.json if needed.",
148+
console.log(
149+
clc.gray(` // Before:
150+
const functions = require('firebase-functions');
151+
152+
exports.myFunction = functions.https.onRequest((req, res) => {
153+
const apiKey = functions.config().service.key;
154+
// ...
155+
});
156+
157+
// After:
158+
const functions = require('firebase-functions');
159+
const { defineJsonSecret } = require('firebase-functions/params');
160+
161+
const config = defineJsonSecret("${key}");
162+
163+
exports.myFunction = functions
164+
.runWith({ secrets: [config] }) // Bind secret here
165+
.https.onRequest((req, res) => {
166+
const apiKey = config.value().service.key;
167+
// ...
168+
});`),
139169
);
170+
console.log("");
171+
172+
// Try to detect the firebase-functions version to see if we need to warn about defineJsonSecret
173+
let sdkVersion: string | undefined;
174+
try {
175+
const functionsConfig = options.config.get("functions");
176+
const source = Array.isArray(functionsConfig)
177+
? functionsConfig[0]?.source
178+
: functionsConfig?.source;
179+
if (source) {
180+
const sourceDir = options.config.path(source);
181+
sdkVersion = getFunctionsSDKVersion(sourceDir);
182+
}
183+
} catch (e) {
184+
// ignore error, just show the warning if we can't detect the version
185+
}
186+
187+
if (!sdkVersion || semver.lt(sdkVersion, "6.6.0")) {
188+
logBullet(
189+
clc.bold("Note: ") +
190+
"defineJsonSecret requires firebase-functions v6.6.0 or later. " +
191+
"Update your package.json if needed.",
192+
);
193+
}
140194
logBullet("Then deploy your functions:\n " + clc.bold("firebase deploy --only functions"));
141195

142196
return secretName;

0 commit comments

Comments
 (0)