firebase-tools 15.12.0 → 15.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/apphosting/backend.js +12 -12
- package/lib/apphosting/config.js +19 -2
- package/lib/apphosting/localbuilds.js +25 -4
- package/lib/apphosting/prompts.js +35 -0
- package/lib/commands/apphosting-backends-list.js +5 -1
- package/lib/commands/ext-info.js +5 -1
- package/lib/deploy/apphosting/prepare.js +67 -3
- package/lib/deploy/functions/prepare.js +1 -0
- package/lib/deploy/functions/prepareFunctionsUpload.js +5 -2
- package/lib/extensions/listExtensions.js +2 -0
- package/lib/gcp/apphosting.js +6 -0
- package/lib/gcp/cloudsql/cloudsqladmin.js +1 -1
- package/lib/init/features/apphosting.js +11 -2
- package/lib/init/features/functions/index.js +7 -4
- package/lib/mcp/index.js +1 -1
- package/lib/mcp/tools/core/read_resources.js +1 -1
- package/lib/tsconfig.compile.tsbuildinfo +1 -0
- package/lib/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/templates/extensions/typescript/package.lint.json +1 -1
- package/templates/extensions/typescript/package.nolint.json +1 -1
- package/templates/extensions/typescript/tsconfig.json +1 -0
- package/templates/init/functions/typescript/package-ongraphrequest.lint.json +1 -1
- package/templates/init/functions/typescript/package-ongraphrequest.nolint.json +1 -1
- package/templates/init/functions/typescript/package.lint.json +1 -1
- package/templates/init/functions/typescript/package.nolint.json +1 -1
- package/templates/init/functions/typescript/tsconfig.json +1 -0
|
@@ -35,7 +35,7 @@ const node_fetch_1 = require("node-fetch");
|
|
|
35
35
|
const rollout_1 = require("./rollout");
|
|
36
36
|
const fuzzy = require("fuzzy");
|
|
37
37
|
const experiments_1 = require("../experiments");
|
|
38
|
-
const
|
|
38
|
+
const prompts_1 = require("./prompts");
|
|
39
39
|
const DEFAULT_COMPUTE_SERVICE_ACCOUNT_NAME = "firebase-app-hosting-compute";
|
|
40
40
|
const apphostingPollerOptions = {
|
|
41
41
|
apiOrigin: (0, api_1.apphostingOrigin)(),
|
|
@@ -101,17 +101,15 @@ async function doSetup(projectId, nonInteractive, webAppName, backendId, service
|
|
|
101
101
|
}
|
|
102
102
|
if (runtime === undefined && (0, experiments_1.isEnabled)("abiu")) {
|
|
103
103
|
if (nonInteractive) {
|
|
104
|
-
runtime = DEFAULT_RUNTIME;
|
|
104
|
+
runtime = prompts_1.DEFAULT_RUNTIME;
|
|
105
105
|
}
|
|
106
106
|
else {
|
|
107
|
-
runtime = await (0,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
default: DEFAULT_RUNTIME,
|
|
114
|
-
});
|
|
107
|
+
runtime = await (0, prompts_1.promptRuntime)(projectId, location);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (automaticBaseImageUpdatesDisabled === undefined && (0, experiments_1.isEnabled)("abiu")) {
|
|
111
|
+
if (!nonInteractive) {
|
|
112
|
+
automaticBaseImageUpdatesDisabled = !(await (0, prompts_1.promptAutomaticBaseImageUpdates)());
|
|
115
113
|
}
|
|
116
114
|
}
|
|
117
115
|
const webApp = await app_1.webApps.getOrCreateWebApp(projectId, webAppName ? webAppName : null, backendId);
|
|
@@ -257,9 +255,11 @@ async function createBackend(projectId, location, backendId, serviceAccount, rep
|
|
|
257
255
|
labels: deploymentTool.labels(),
|
|
258
256
|
serviceAccount: serviceAccount || defaultServiceAccount,
|
|
259
257
|
appId: webAppId,
|
|
260
|
-
runtime: { value: runtime ?? "" },
|
|
261
|
-
automaticBaseImageUpdatesDisabled,
|
|
262
258
|
};
|
|
259
|
+
if ((0, experiments_1.isEnabled)("abiu")) {
|
|
260
|
+
backendReqBody.runtime = { value: runtime ?? "" };
|
|
261
|
+
backendReqBody.automaticBaseImageUpdatesDisabled = automaticBaseImageUpdatesDisabled;
|
|
262
|
+
}
|
|
263
263
|
async function createBackendAndPoll() {
|
|
264
264
|
const op = await apphosting.createBackend(projectId, location, backendReqBody, backendId);
|
|
265
265
|
return await poller.pollOperation({
|
package/lib/apphosting/config.js
CHANGED
|
@@ -12,6 +12,7 @@ exports.maybeAddSecretToYaml = maybeAddSecretToYaml;
|
|
|
12
12
|
exports.maybeGenerateEmulatorYaml = maybeGenerateEmulatorYaml;
|
|
13
13
|
exports.overrideChosenEnv = overrideChosenEnv;
|
|
14
14
|
exports.suggestedTestKeyName = suggestedTestKeyName;
|
|
15
|
+
exports.splitEnvVars = splitEnvVars;
|
|
15
16
|
const path_1 = require("path");
|
|
16
17
|
const fs_1 = require("fs");
|
|
17
18
|
const yaml = require("yaml");
|
|
@@ -23,7 +24,6 @@ const yaml_1 = require("./yaml");
|
|
|
23
24
|
const logger_1 = require("../logger");
|
|
24
25
|
const csm = require("../gcp/secretManager");
|
|
25
26
|
const error_1 = require("../error");
|
|
26
|
-
const path_2 = require("path");
|
|
27
27
|
exports.APPHOSTING_BASE_YAML_FILE = "apphosting.yaml";
|
|
28
28
|
exports.APPHOSTING_EMULATORS_YAML_FILE = "apphosting.emulator.yaml";
|
|
29
29
|
exports.APPHOSTING_LOCAL_YAML_FILE = "apphosting.local.yaml";
|
|
@@ -69,7 +69,7 @@ function load(yamlPath) {
|
|
|
69
69
|
const dynamicDispatch = exports;
|
|
70
70
|
async function getAppHostingConfiguration(backendDir) {
|
|
71
71
|
const appHostingConfigPaths = dynamicDispatch.listAppHostingFilesInPath(backendDir);
|
|
72
|
-
const fileNameToPathMap = Object.fromEntries(appHostingConfigPaths.map((path) => [(0,
|
|
72
|
+
const fileNameToPathMap = Object.fromEntries(appHostingConfigPaths.map((path) => [(0, path_1.basename)(path), path]));
|
|
73
73
|
const output = yaml_1.AppHostingYamlConfig.empty();
|
|
74
74
|
const baseFilePath = fileNameToPathMap[exports.APPHOSTING_BASE_YAML_FILE];
|
|
75
75
|
const emulatorsFilePath = fileNameToPathMap[exports.APPHOSTING_EMULATORS_YAML_FILE];
|
|
@@ -246,3 +246,20 @@ async function overrideChosenEnv(projectId, env) {
|
|
|
246
246
|
function suggestedTestKeyName(variable) {
|
|
247
247
|
return "test-" + variable.replace(/_/g, "-").toLowerCase();
|
|
248
248
|
}
|
|
249
|
+
function splitEnvVars(env) {
|
|
250
|
+
const build = {};
|
|
251
|
+
const runtime = {};
|
|
252
|
+
for (const [key, val] of Object.entries(env)) {
|
|
253
|
+
const envVal = { ...val };
|
|
254
|
+
if (envVal.value !== undefined) {
|
|
255
|
+
envVal.value = String(envVal.value);
|
|
256
|
+
}
|
|
257
|
+
if (val.availability?.includes("BUILD") || !val.availability) {
|
|
258
|
+
build[key] = envVal;
|
|
259
|
+
}
|
|
260
|
+
if (val.availability?.includes("RUNTIME") || !val.availability) {
|
|
261
|
+
runtime[key] = envVal;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return { build, runtime };
|
|
265
|
+
}
|
|
@@ -2,10 +2,28 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.localBuild = localBuild;
|
|
4
4
|
const build_1 = require("@apphosting/build");
|
|
5
|
-
async function localBuild(projectRoot, framework) {
|
|
6
|
-
const
|
|
5
|
+
async function localBuild(projectRoot, framework, env = {}) {
|
|
6
|
+
const originalEnv = { ...process.env };
|
|
7
|
+
const addedEnv = toProcessEnv(env);
|
|
8
|
+
for (const [key, value] of Object.entries(addedEnv)) {
|
|
9
|
+
process.env[key] = value;
|
|
10
|
+
}
|
|
11
|
+
let apphostingBuildOutput;
|
|
12
|
+
try {
|
|
13
|
+
apphostingBuildOutput = await (0, build_1.localBuild)(projectRoot, framework);
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
for (const key in process.env) {
|
|
17
|
+
if (!(key in originalEnv)) {
|
|
18
|
+
delete process.env[key];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
for (const [key, value] of Object.entries(originalEnv)) {
|
|
22
|
+
process.env[key] = value;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
7
25
|
const annotations = Object.fromEntries(Object.entries(apphostingBuildOutput.metadata).map(([key, value]) => [key, String(value)]));
|
|
8
|
-
const
|
|
26
|
+
const discoveredEnv = apphostingBuildOutput.runConfig.environmentVariables?.map(({ variable, value, availability }) => ({
|
|
9
27
|
variable,
|
|
10
28
|
value,
|
|
11
29
|
availability,
|
|
@@ -15,7 +33,10 @@ async function localBuild(projectRoot, framework) {
|
|
|
15
33
|
annotations,
|
|
16
34
|
buildConfig: {
|
|
17
35
|
runCommand: apphostingBuildOutput.runConfig.runCommand,
|
|
18
|
-
env:
|
|
36
|
+
env: discoveredEnv ?? [],
|
|
19
37
|
},
|
|
20
38
|
};
|
|
21
39
|
}
|
|
40
|
+
function toProcessEnv(env) {
|
|
41
|
+
return Object.fromEntries(Object.entries(env).map(([key, value]) => [key, value.value || ""]));
|
|
42
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_RUNTIME = void 0;
|
|
4
|
+
exports.promptRuntime = promptRuntime;
|
|
5
|
+
exports.promptAutomaticBaseImageUpdates = promptAutomaticBaseImageUpdates;
|
|
6
|
+
const prompt_1 = require("../prompt");
|
|
7
|
+
const utils_1 = require("../utils");
|
|
8
|
+
const apphosting = require("../gcp/apphosting");
|
|
9
|
+
exports.DEFAULT_RUNTIME = "nodejs";
|
|
10
|
+
async function promptRuntime(projectId, location) {
|
|
11
|
+
const choices = [{ name: "Node.js (default)", value: exports.DEFAULT_RUNTIME }];
|
|
12
|
+
try {
|
|
13
|
+
const supportedRuntimes = await apphosting.listSupportedRuntimes(projectId, location);
|
|
14
|
+
for (const r of supportedRuntimes) {
|
|
15
|
+
if (r.runtimeId !== exports.DEFAULT_RUNTIME) {
|
|
16
|
+
choices.push({ name: r.runtimeId, value: r.runtimeId });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
(0, utils_1.logWarning)("Failed to list supported runtimes. Falling back to hardcoded list.");
|
|
22
|
+
choices.push({ name: "nodejs22", value: "nodejs22" });
|
|
23
|
+
}
|
|
24
|
+
return await (0, prompt_1.select)({
|
|
25
|
+
message: "Which runtime do you want to use?",
|
|
26
|
+
choices: choices,
|
|
27
|
+
default: exports.DEFAULT_RUNTIME,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async function promptAutomaticBaseImageUpdates() {
|
|
31
|
+
return await (0, prompt_1.confirm)({
|
|
32
|
+
message: "Would you like to enable Automatic Base Image Updates (ABIU)?",
|
|
33
|
+
default: true,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -50,7 +50,11 @@ function printBackendsTable(backends) {
|
|
|
50
50
|
];
|
|
51
51
|
if (abiuEnabled) {
|
|
52
52
|
let abiuStatus = "N/A";
|
|
53
|
-
|
|
53
|
+
const runtimeValue = backend.runtime?.value ?? "";
|
|
54
|
+
if (runtimeValue === "" || runtimeValue === "nodejs") {
|
|
55
|
+
abiuStatus = "Disabled";
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
54
58
|
abiuStatus = backend.automaticBaseImageUpdatesDisabled ? "Disabled" : "Enabled";
|
|
55
59
|
}
|
|
56
60
|
row.push(abiuStatus);
|
package/lib/commands/ext-info.js
CHANGED
|
@@ -19,6 +19,7 @@ exports.command = new command_1.Command("ext:info <extensionName>")
|
|
|
19
19
|
.before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
|
|
20
20
|
.action(async (extensionName, options) => {
|
|
21
21
|
let spec;
|
|
22
|
+
let version;
|
|
22
23
|
if ((0, localHelper_1.isLocalExtension)(extensionName)) {
|
|
23
24
|
if (!options.markdown) {
|
|
24
25
|
utils.logLabeledBullet(extensionsHelper_1.logPrefix, `reading extension from directory: ${extensionName}`);
|
|
@@ -39,7 +40,7 @@ exports.command = new command_1.Command("ext:info <extensionName>")
|
|
|
39
40
|
const [name, version] = extensionName.split("@");
|
|
40
41
|
extensionName = `firebase/${name}@${version || "latest"}`;
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
+
version = await extensionsApi.getExtensionVersion(extensionName);
|
|
43
44
|
spec = version.spec;
|
|
44
45
|
}
|
|
45
46
|
if (!options.markdown) {
|
|
@@ -56,6 +57,9 @@ exports.command = new command_1.Command("ext:info <extensionName>")
|
|
|
56
57
|
const url = spec.author?.url;
|
|
57
58
|
const urlMarkdown = url ? `(**[${url}](${url})**)` : "";
|
|
58
59
|
lines.push(`**Author**: ${authorName} ${urlMarkdown}`);
|
|
60
|
+
if (version?.sourceDownloadUri) {
|
|
61
|
+
lines.push(`**Download URL**: ${version.sourceDownloadUri}`);
|
|
62
|
+
}
|
|
59
63
|
if (spec.description) {
|
|
60
64
|
lines.push(`**Description**: ${spec.description}`);
|
|
61
65
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = default_1;
|
|
4
|
+
exports.injectEnvVarsFromApphostingConfig = injectEnvVarsFromApphostingConfig;
|
|
5
|
+
exports.injectAutoInitEnvVars = injectAutoInitEnvVars;
|
|
4
6
|
exports.getBackendConfigs = getBackendConfigs;
|
|
5
7
|
const path = require("path");
|
|
6
8
|
const backend_1 = require("../../apphosting/backend");
|
|
7
9
|
const apphosting_1 = require("../../gcp/apphosting");
|
|
10
|
+
const yaml_1 = require("../../apphosting/yaml");
|
|
11
|
+
const config_1 = require("../../apphosting/config");
|
|
8
12
|
const devConnect_1 = require("../../gcp/devConnect");
|
|
9
13
|
const resourceManager_1 = require("../../gcp/resourceManager");
|
|
10
14
|
const projectUtils_1 = require("../../projectUtils");
|
|
@@ -13,6 +17,8 @@ const prompt_1 = require("../../prompt");
|
|
|
13
17
|
const utils_1 = require("../../utils");
|
|
14
18
|
const localbuilds_1 = require("../../apphosting/localbuilds");
|
|
15
19
|
const error_1 = require("../../error");
|
|
20
|
+
const managementApps = require("../../management/apps");
|
|
21
|
+
const utils_2 = require("../../apphosting/utils");
|
|
16
22
|
const experiments = require("../../experiments");
|
|
17
23
|
const logger_1 = require("../../logger");
|
|
18
24
|
async function default_1(context, options) {
|
|
@@ -116,20 +122,27 @@ async function default_1(context, options) {
|
|
|
116
122
|
if (skippedBackends.length > 0) {
|
|
117
123
|
(0, utils_1.logLabeledWarning)("apphosting", `Skipping deployment of backend(s) ${skippedBackends.map((cfg) => cfg.backendId).join(", ")}.`);
|
|
118
124
|
}
|
|
125
|
+
const buildEnv = {};
|
|
126
|
+
const runtimeEnv = {};
|
|
119
127
|
for (const cfg of Object.values(context.backendConfigs)) {
|
|
120
128
|
if (!cfg.localBuild) {
|
|
121
129
|
continue;
|
|
122
130
|
}
|
|
123
131
|
experiments.assertEnabled("apphostinglocalbuilds", "locally build App Hosting backends");
|
|
124
132
|
(0, utils_1.logLabeledBullet)("apphosting", `Starting local build for backend ${cfg.backendId}`);
|
|
133
|
+
await injectEnvVarsFromApphostingConfig(configs.filter((c) => c.backendId === cfg.backendId), options, buildEnv, runtimeEnv);
|
|
134
|
+
await injectAutoInitEnvVars(cfg, backends, buildEnv, runtimeEnv);
|
|
125
135
|
try {
|
|
126
|
-
const { outputFiles, annotations, buildConfig } = await (0, localbuilds_1.localBuild)(options.projectRoot || "./", "nextjs");
|
|
136
|
+
const { outputFiles, annotations, buildConfig } = await (0, localbuilds_1.localBuild)(options.projectRoot || "./", "nextjs", buildEnv[cfg.backendId] || {});
|
|
127
137
|
if (outputFiles.length !== 1) {
|
|
128
138
|
throw new error_1.FirebaseError(`Local build for backend ${cfg.backendId} failed: No output files found.`);
|
|
129
139
|
}
|
|
130
140
|
context.backendLocalBuilds[cfg.backendId] = {
|
|
131
141
|
buildDir: outputFiles[0],
|
|
132
|
-
buildConfig
|
|
142
|
+
buildConfig: {
|
|
143
|
+
...buildConfig,
|
|
144
|
+
env: mergeEnvVars(buildConfig.env || [], runtimeEnv[cfg.backendId] || {}),
|
|
145
|
+
},
|
|
133
146
|
annotations,
|
|
134
147
|
};
|
|
135
148
|
}
|
|
@@ -139,6 +152,39 @@ async function default_1(context, options) {
|
|
|
139
152
|
}
|
|
140
153
|
}
|
|
141
154
|
}
|
|
155
|
+
async function injectEnvVarsFromApphostingConfig(configs, options, buildEnv, runtimeEnv) {
|
|
156
|
+
for (const cfg of configs) {
|
|
157
|
+
const rootDir = options.projectRoot || process.cwd();
|
|
158
|
+
const appDir = path.join(rootDir, cfg.rootDir || "");
|
|
159
|
+
let yamlConfig = yaml_1.AppHostingYamlConfig.empty();
|
|
160
|
+
try {
|
|
161
|
+
yamlConfig = await (0, config_1.getAppHostingConfiguration)(appDir);
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
(0, utils_1.logLabeledWarning)("apphosting", `Failed to read apphosting.yaml, may be missing environment variables and other configs`);
|
|
165
|
+
}
|
|
166
|
+
const { build, runtime } = (0, config_1.splitEnvVars)(yamlConfig.env);
|
|
167
|
+
buildEnv[cfg.backendId] = { ...buildEnv[cfg.backendId], ...build };
|
|
168
|
+
runtimeEnv[cfg.backendId] = { ...runtimeEnv[cfg.backendId], ...runtime };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function injectAutoInitEnvVars(cfg, backends, buildEnv, runtimeEnv) {
|
|
172
|
+
var _a, _b;
|
|
173
|
+
const backend = backends.find((b) => (0, apphosting_1.parseBackendName)(b.name).id === cfg.backendId);
|
|
174
|
+
if (backend?.appId) {
|
|
175
|
+
try {
|
|
176
|
+
const webappConfig = (await managementApps.getAppConfig(backend.appId, managementApps.AppPlatform.WEB));
|
|
177
|
+
const autoinitVars = (0, utils_2.getAutoinitEnvVars)(webappConfig);
|
|
178
|
+
for (const [envVarName, envVarValue] of Object.entries(autoinitVars)) {
|
|
179
|
+
(_a = buildEnv[cfg.backendId])[envVarName] ?? (_a[envVarName] = { value: envVarValue });
|
|
180
|
+
(_b = runtimeEnv[cfg.backendId])[envVarName] ?? (_b[envVarName] = { value: envVarValue });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
(0, utils_1.logLabeledWarning)("apphosting", `Unable to lookup details for backend ${cfg.backendId}. Firebase SDK autoinit will not be available.`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
142
188
|
function getBackendConfigs(options) {
|
|
143
189
|
if (!options.config.src.apphosting) {
|
|
144
190
|
return [];
|
|
@@ -165,7 +211,25 @@ function getBackendConfigs(options) {
|
|
|
165
211
|
if (backendIds.length === 0) {
|
|
166
212
|
return [];
|
|
167
213
|
}
|
|
168
|
-
|
|
214
|
+
const filteredConfigs = backendConfigs.filter((cfg) => backendIds.includes(cfg.backendId));
|
|
215
|
+
const foundIds = filteredConfigs.map((cfg) => cfg.backendId);
|
|
216
|
+
const missingIds = backendIds.filter((id) => !foundIds.includes(id));
|
|
217
|
+
if (missingIds.length > 0) {
|
|
218
|
+
throw new error_1.FirebaseError(`App Hosting backend IDs ${missingIds.join(",")} not detected in firebase.json`);
|
|
219
|
+
}
|
|
220
|
+
return filteredConfigs;
|
|
221
|
+
}
|
|
222
|
+
function mergeEnvVars(base, overrides) {
|
|
223
|
+
const merged = new Map();
|
|
224
|
+
for (const env of base) {
|
|
225
|
+
if (env.variable) {
|
|
226
|
+
merged.set(env.variable, env);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (const [envVarName, envVarConfig] of Object.entries(overrides)) {
|
|
230
|
+
merged.set(envVarName, { ...envVarConfig, variable: envVarName });
|
|
231
|
+
}
|
|
232
|
+
return Array.from(merged.values());
|
|
169
233
|
}
|
|
170
234
|
async function ensureAppHostingServiceAgentRoles(projectId, projectNumber) {
|
|
171
235
|
const p4saEmail = (0, apphosting_1.serviceAgentEmail)(projectNumber);
|
|
@@ -318,6 +318,7 @@ async function loadCodebases(config, options, firebaseConfig, runtimeConfig, fil
|
|
|
318
318
|
throw new error_1.FirebaseError(`Functions codebase ${codebase} has invalid runtime ` +
|
|
319
319
|
`${firebaseJsonRuntime} specified in firebase.json. Valid values are: \n` +
|
|
320
320
|
Object.keys(supported.RUNTIMES)
|
|
321
|
+
.filter((runtime) => !supported.isDecommissioned(runtime))
|
|
321
322
|
.map((s) => `- ${s}`)
|
|
322
323
|
.join("\n"));
|
|
323
324
|
}
|
|
@@ -9,6 +9,7 @@ const filesize = require("filesize");
|
|
|
9
9
|
const fs = require("fs");
|
|
10
10
|
const path = require("path");
|
|
11
11
|
const tmp = require("tmp");
|
|
12
|
+
const crypto = require("crypto");
|
|
12
13
|
const error_1 = require("../../error");
|
|
13
14
|
const logger_1 = require("../../logger");
|
|
14
15
|
const hash_1 = require("./cache/hash");
|
|
@@ -54,6 +55,7 @@ async function packageSource(projectDir, sourceDir, config, additionalSources, r
|
|
|
54
55
|
});
|
|
55
56
|
const archive = exportType === "tar.gz" ? archiver("tar", { gzip: true }) : archiver("zip");
|
|
56
57
|
const hashes = [];
|
|
58
|
+
let configHash = "";
|
|
57
59
|
const ignore = config.ignore || ["node_modules", ".git"];
|
|
58
60
|
ignore.push("firebase-debug.log", "firebase-debug.*.log", CONFIG_DEST_FILE);
|
|
59
61
|
try {
|
|
@@ -82,7 +84,7 @@ async function packageSource(projectDir, sourceDir, config, additionalSources, r
|
|
|
82
84
|
}
|
|
83
85
|
if (typeof runtimeConfig !== "undefined") {
|
|
84
86
|
const runtimeConfigHashString = JSON.stringify(convertToSortedKeyValueArray(runtimeConfig));
|
|
85
|
-
|
|
87
|
+
configHash = crypto.createHash("sha1").update(runtimeConfigHashString).digest("hex");
|
|
86
88
|
const runtimeConfigString = JSON.stringify(runtimeConfig, null, 2);
|
|
87
89
|
archive.append(runtimeConfigString, {
|
|
88
90
|
name: CONFIG_DEST_FILE,
|
|
@@ -109,7 +111,8 @@ async function packageSource(projectDir, sourceDir, config, additionalSources, r
|
|
|
109
111
|
" (" +
|
|
110
112
|
filesize(archive.pointer()) +
|
|
111
113
|
") for uploading");
|
|
112
|
-
const
|
|
114
|
+
const sourceHash = crypto.createHash("sha1").update(hashes.sort().join("")).digest("hex");
|
|
115
|
+
const hash = configHash ? `${sourceHash}.${configHash}` : sourceHash;
|
|
113
116
|
return { pathToSource: tmpFile, hash };
|
|
114
117
|
}
|
|
115
118
|
async function prepareFunctionsUpload(projectDir, sourceDir, config, additionalSources, runtimeConfig, options) {
|
|
@@ -43,6 +43,8 @@ async function listExtensions(projectId) {
|
|
|
43
43
|
state,
|
|
44
44
|
version,
|
|
45
45
|
updateTime,
|
|
46
|
+
params: instance.config.params,
|
|
47
|
+
systemParams: instance.config.systemParams,
|
|
46
48
|
});
|
|
47
49
|
});
|
|
48
50
|
(0, utils_1.logLabeledBullet)(extensionsHelper_1.logPrefix, `list of extensions installed in ${clc.bold(projectId)}:`);
|
package/lib/gcp/apphosting.js
CHANGED
|
@@ -16,6 +16,7 @@ exports.createRollout = createRollout;
|
|
|
16
16
|
exports.listRollouts = listRollouts;
|
|
17
17
|
exports.updateTraffic = updateTraffic;
|
|
18
18
|
exports.listLocations = listLocations;
|
|
19
|
+
exports.listSupportedRuntimes = listSupportedRuntimes;
|
|
19
20
|
exports.ensureApiEnabled = ensureApiEnabled;
|
|
20
21
|
exports.getNextRolloutId = getNextRolloutId;
|
|
21
22
|
const proto = require("../gcp/proto");
|
|
@@ -176,6 +177,11 @@ async function listLocations(projectId) {
|
|
|
176
177
|
} while (pageToken);
|
|
177
178
|
return locations;
|
|
178
179
|
}
|
|
180
|
+
async function listSupportedRuntimes(projectId, location) {
|
|
181
|
+
const name = `projects/${projectId}/locations/${location}/supportedRuntimes`;
|
|
182
|
+
const res = await exports.client.get(name);
|
|
183
|
+
return res.body.supportedRuntimes || [];
|
|
184
|
+
}
|
|
179
185
|
async function ensureApiEnabled(options) {
|
|
180
186
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
181
187
|
return await (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.apphostingOrigin)(), "app hosting", true);
|
|
@@ -61,7 +61,7 @@ async function getInstance(projectId, instanceId) {
|
|
|
61
61
|
function instanceConsoleLink(projectId, instanceId) {
|
|
62
62
|
return `https://console.cloud.google.com/sql/instances/${instanceId}/overview?project=${projectId}`;
|
|
63
63
|
}
|
|
64
|
-
exports.DEFAULT_DATABASE_VERSION = "
|
|
64
|
+
exports.DEFAULT_DATABASE_VERSION = "POSTGRES_18";
|
|
65
65
|
async function createInstance(args) {
|
|
66
66
|
const databaseFlags = [{ name: "cloudsql.iam_authentication", value: "on" }];
|
|
67
67
|
if (args.enableGoogleMlIntegration) {
|
|
@@ -16,7 +16,8 @@ const templates_1 = require("../../templates");
|
|
|
16
16
|
const utils = require("../../utils");
|
|
17
17
|
const utils_1 = require("../../utils");
|
|
18
18
|
const APPHOSTING_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/apphosting/apphosting.yaml");
|
|
19
|
-
async function doSetup(setup, config) {
|
|
19
|
+
async function doSetup(setup, config, options) {
|
|
20
|
+
const { dynamicImport } = eval("require")("../../dynamicImport");
|
|
20
21
|
const projectId = setup.projectId;
|
|
21
22
|
if (!(await (0, cloudbilling_1.isBillingEnabled)(setup))) {
|
|
22
23
|
throw new error_1.FirebaseError(`Firebase App Hosting requires billing to be enabled on your project. To upgrade, visit the following URL: https://console.firebase.google.com/project/${projectId}/usage/details`);
|
|
@@ -51,8 +52,16 @@ async function doSetup(setup, config) {
|
|
|
51
52
|
if (!webApp) {
|
|
52
53
|
utils.logWarning(`Firebase web app not set`);
|
|
53
54
|
}
|
|
55
|
+
const experiments = await dynamicImport("./experiments");
|
|
56
|
+
const prompts = await dynamicImport("./apphosting/prompts");
|
|
57
|
+
let runtime = experiments.isEnabled("abiu") ? prompts.DEFAULT_RUNTIME : undefined;
|
|
58
|
+
let automaticBaseImageUpdatesDisabled = experiments.isEnabled("abiu") ? false : undefined;
|
|
59
|
+
if (experiments.isEnabled("abiu") && !options.nonInteractive) {
|
|
60
|
+
runtime = await prompts.promptRuntime(projectId, location);
|
|
61
|
+
automaticBaseImageUpdatesDisabled = !(await prompts.promptAutomaticBaseImageUpdates());
|
|
62
|
+
}
|
|
54
63
|
const createBackendSpinner = ora("Creating your new backend...").start();
|
|
55
|
-
const backend = await (0, backend_1.createBackend)(projectId, location, backendId, null, undefined, webApp?.id);
|
|
64
|
+
const backend = await (0, backend_1.createBackend)(projectId, location, backendId, null, undefined, webApp?.id, "/", runtime, automaticBaseImageUpdatesDisabled);
|
|
56
65
|
createBackendSpinner.succeed(`Successfully created backend!\n\t${backend.name}\n`);
|
|
57
66
|
}
|
|
58
67
|
(0, utils_1.logBullet)(`${clc.yellow("===")} Deploy local source setup`);
|
|
@@ -11,6 +11,7 @@ const projectConfig_1 = require("../../../functions/projectConfig");
|
|
|
11
11
|
const error_1 = require("../../../error");
|
|
12
12
|
const api_1 = require("../../../api");
|
|
13
13
|
const supported = require("../../../deploy/functions/runtimes/supported");
|
|
14
|
+
const experiments = require("../../../experiments");
|
|
14
15
|
const MAX_ATTEMPTS = 5;
|
|
15
16
|
async function askQuestions(setup, config, options) {
|
|
16
17
|
const projectId = setup?.rcfile?.projects?.default;
|
|
@@ -144,10 +145,12 @@ async function languageSetup(setup) {
|
|
|
144
145
|
value: "python",
|
|
145
146
|
});
|
|
146
147
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
if (experiments.isEnabled("functionsrunapionly")) {
|
|
149
|
+
choices.push({
|
|
150
|
+
name: "Dart",
|
|
151
|
+
value: "dart",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
151
154
|
const language = await (0, prompt_1.select)({
|
|
152
155
|
message: "What language would you like to use to write Cloud Functions?",
|
|
153
156
|
default: "javascript",
|
package/lib/mcp/index.js
CHANGED
|
@@ -347,7 +347,7 @@ class FirebaseMcpServer {
|
|
|
347
347
|
}
|
|
348
348
|
}
|
|
349
349
|
async mcpListResources() {
|
|
350
|
-
await
|
|
350
|
+
await this.trackGA4("mcp_list_resources", { resource_name: "__list__" });
|
|
351
351
|
return {
|
|
352
352
|
resources: resources_1.resources.map((r) => r.mcp),
|
|
353
353
|
};
|
|
@@ -22,7 +22,7 @@ exports.read_resources = (0, tool_1.tool)("core", {
|
|
|
22
22
|
}),
|
|
23
23
|
}, async ({ uris }, ctx) => {
|
|
24
24
|
if (!uris?.length) {
|
|
25
|
-
void (0, track_1.trackGA4)("
|
|
25
|
+
void (0, track_1.trackGA4)("mcp_list_resources", { resource_name: "__list__" });
|
|
26
26
|
return (0, util_1.toContent)(resources_1.resources
|
|
27
27
|
.map((r) => `Available resources:\n\n- [${r.mcp.title || r.mcp.name}](${r.mcp.uri}): ${r.mcp.description}`)
|
|
28
28
|
.join("\n"));
|