firebase-tools 15.12.0 → 15.12.1-main.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.
@@ -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, path_2.basename)(path), path]));
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 apphostingBuildOutput = await (0, build_1.localBuild)(projectRoot, framework);
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 env = apphostingBuildOutput.runConfig.environmentVariables?.map(({ variable, value, availability }) => ({
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: 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
+ }
@@ -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
- return backendConfigs.filter((cfg) => backendIds.includes(cfg.backendId));
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
- hashes.push(runtimeConfigHashString);
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 hash = hashes.join(".");
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) {
@@ -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 = "POSTGRES_17";
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) {