firebase-tools 14.20.0 → 14.22.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.
Files changed (93) hide show
  1. package/lib/appUtils.js +2 -1
  2. package/lib/command.js +5 -9
  3. package/lib/commands/dataconnect-sdk-generate.js +66 -11
  4. package/lib/commands/deploy.js +6 -4
  5. package/lib/commands/firestore-databases-clone.js +99 -0
  6. package/lib/commands/functions-secrets-set.js +19 -1
  7. package/lib/commands/hosting-sites-create.js +4 -3
  8. package/lib/commands/index.js +1 -0
  9. package/lib/commands/init.js +12 -8
  10. package/lib/commands/internaltesting-functions-discover.js +1 -3
  11. package/lib/dataconnect/provisionCloudSql.js +3 -2
  12. package/lib/deploy/extensions/prepare.js +3 -1
  13. package/lib/deploy/functions/checkIam.js +1 -1
  14. package/lib/deploy/functions/functionsDeployHelper.js +8 -7
  15. package/lib/deploy/functions/params.js +15 -5
  16. package/lib/deploy/functions/prepare.js +9 -6
  17. package/lib/detectProjectRoot.js +1 -1
  18. package/lib/emulator/downloadableEmulatorInfo.json +18 -18
  19. package/lib/emulator/hubExport.js +5 -0
  20. package/lib/experiments.js +0 -7
  21. package/lib/firestore/api.js +15 -0
  22. package/lib/firestore/util.js +22 -1
  23. package/lib/frameworks/angular/index.js +1 -1
  24. package/lib/frameworks/flutter/index.js +1 -1
  25. package/lib/frameworks/next/index.js +1 -1
  26. package/lib/frameworks/nuxt/index.js +1 -1
  27. package/lib/frameworks/vite/index.js +5 -2
  28. package/lib/functions/projectConfig.js +5 -1
  29. package/lib/functions/secrets.js +14 -1
  30. package/lib/hosting/interactive.js +14 -19
  31. package/lib/init/features/dataconnect/index.js +21 -20
  32. package/lib/init/features/dataconnect/sdk.js +44 -21
  33. package/lib/init/features/functions/index.js +1 -0
  34. package/lib/init/features/hosting/index.js +96 -93
  35. package/lib/init/features/index.js +3 -2
  36. package/lib/init/index.js +7 -3
  37. package/lib/mcp/index.js +46 -18
  38. package/lib/mcp/prompt.js +4 -1
  39. package/lib/mcp/prompts/core/consult.js +1 -1
  40. package/lib/mcp/prompts/core/deploy.js +1 -1
  41. package/lib/mcp/prompts/core/init.js +1 -1
  42. package/lib/mcp/prompts/crashlytics/connect.js +1 -1
  43. package/lib/mcp/prompts/dataconnect/schema.js +1 -1
  44. package/lib/mcp/prompts/index.js +20 -10
  45. package/lib/mcp/resources/guides/init_backend.js +3 -26
  46. package/lib/mcp/resources/guides/init_hosting.js +15 -10
  47. package/lib/mcp/tool.js +17 -2
  48. package/lib/mcp/tools/apphosting/fetch_logs.js +1 -1
  49. package/lib/mcp/tools/apphosting/list_backends.js +1 -1
  50. package/lib/mcp/tools/auth/get_users.js +1 -1
  51. package/lib/mcp/tools/auth/set_sms_region_policy.js +1 -1
  52. package/lib/mcp/tools/auth/update_user.js +1 -1
  53. package/lib/mcp/tools/core/create_android_sha.js +1 -1
  54. package/lib/mcp/tools/core/create_app.js +1 -1
  55. package/lib/mcp/tools/core/create_project.js +1 -1
  56. package/lib/mcp/tools/core/get_environment.js +1 -1
  57. package/lib/mcp/tools/core/get_project.js +1 -1
  58. package/lib/mcp/tools/core/get_sdk_config.js +1 -1
  59. package/lib/mcp/tools/core/get_security_rules.js +1 -1
  60. package/lib/mcp/tools/core/init.js +30 -2
  61. package/lib/mcp/tools/core/list_apps.js +1 -1
  62. package/lib/mcp/tools/core/list_projects.js +1 -1
  63. package/lib/mcp/tools/core/login.js +1 -1
  64. package/lib/mcp/tools/core/logout.js +1 -1
  65. package/lib/mcp/tools/core/read_resources.js +1 -1
  66. package/lib/mcp/tools/core/update_environment.js +1 -1
  67. package/lib/mcp/tools/core/validate_security_rules.js +15 -1
  68. package/lib/mcp/tools/crashlytics/events.js +3 -3
  69. package/lib/mcp/tools/crashlytics/issues.js +3 -3
  70. package/lib/mcp/tools/crashlytics/notes.js +4 -4
  71. package/lib/mcp/tools/crashlytics/reports.js +6 -6
  72. package/lib/mcp/tools/dataconnect/compile.js +1 -1
  73. package/lib/mcp/tools/dataconnect/execute.js +1 -1
  74. package/lib/mcp/tools/dataconnect/generate_operation.js +1 -1
  75. package/lib/mcp/tools/dataconnect/generate_schema.js +1 -1
  76. package/lib/mcp/tools/dataconnect/list_services.js +1 -1
  77. package/lib/mcp/tools/firestore/delete_document.js +1 -1
  78. package/lib/mcp/tools/firestore/get_documents.js +1 -1
  79. package/lib/mcp/tools/firestore/list_collections.js +1 -1
  80. package/lib/mcp/tools/firestore/query_collection.js +1 -1
  81. package/lib/mcp/tools/functions/get_logs.js +1 -1
  82. package/lib/mcp/tools/index.js +14 -4
  83. package/lib/mcp/tools/messaging/send_message.js +1 -1
  84. package/lib/mcp/tools/realtime_database/get_data.js +1 -1
  85. package/lib/mcp/tools/realtime_database/set_data.js +1 -1
  86. package/lib/mcp/tools/remoteconfig/get_template.js +1 -1
  87. package/lib/mcp/tools/remoteconfig/update_template.js +1 -1
  88. package/lib/mcp/tools/storage/get_download_url.js +1 -1
  89. package/lib/mcp/util/availability.js +22 -0
  90. package/lib/mcp/util/crashlytics/availability.js +81 -0
  91. package/lib/mcp/util.js +26 -6
  92. package/package.json +1 -1
  93. package/schema/firebase-config.json +3 -0
@@ -54,28 +54,28 @@
54
54
  },
55
55
  "dataconnect": {
56
56
  "darwin": {
57
- "version": "2.15.0",
58
- "expectedSize": 29610848,
59
- "expectedChecksum": "182ddca17f4974ec081fd5a769f8eeac",
60
- "expectedChecksumSHA256": "3bcdc39b7f149f8c2d96f397188ca003daf1b43a1ce845780146b79217970a52",
61
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.15.0",
62
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.0"
57
+ "version": "2.15.1",
58
+ "expectedSize": 29946720,
59
+ "expectedChecksum": "cc8d5dd053cc71adad0f640ac2627018",
60
+ "expectedChecksumSHA256": "a3749396507678bc546987ef047a9d0c25145064503a9adc777229f1bc9b8c6f",
61
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.15.1",
62
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.1"
63
63
  },
64
64
  "win32": {
65
- "version": "2.15.0",
66
- "expectedSize": 30099456,
67
- "expectedChecksum": "5dd4efd368c3987925c41784e21c7af6",
68
- "expectedChecksumSHA256": "a664f29cd21f826dd4936f8b95f0673e0ea603ab0d85a426d8231f1aaa8947f9",
69
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.15.0",
70
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.0.exe"
65
+ "version": "2.15.1",
66
+ "expectedSize": 30440960,
67
+ "expectedChecksum": "57390c392101a13952ff2aea74b62787",
68
+ "expectedChecksumSHA256": "905a9ab2200189dc31f23b5454d21803b0c672442f0ee4bb7db8286d159a86e0",
69
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.15.1",
70
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.1.exe"
71
71
  },
72
72
  "linux": {
73
- "version": "2.15.0",
74
- "expectedSize": 29532344,
75
- "expectedChecksum": "38baf423fbfc9c67a89975a27f0a3974",
76
- "expectedChecksumSHA256": "d3c5b26470639fedce9e3ac667948fa07473e77f6c2d0b361d78c5f2e856a1ec",
77
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.15.0",
78
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.0"
73
+ "version": "2.15.1",
74
+ "expectedSize": 29868216,
75
+ "expectedChecksum": "cc6ef676d8c5c3a3f74c0bf037083cf0",
76
+ "expectedChecksumSHA256": "1dfe085c467637ebf6a71cc6c8e8bc11fac85202056b960633607627bd647a6d",
77
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.15.1",
78
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.1"
79
79
  }
80
80
  }
81
81
  }
@@ -13,6 +13,7 @@ const hub_1 = require("./hub");
13
13
  const downloadableEmulators_1 = require("./downloadableEmulators");
14
14
  const node_fs_1 = require("node:fs");
15
15
  const track_1 = require("../track");
16
+ const api_1 = require("../api");
16
17
  class HubExport {
17
18
  constructor(projectId, options) {
18
19
  this.projectId = projectId;
@@ -234,5 +235,9 @@ function fetchToFile(options, path) {
234
235
  });
235
236
  }
236
237
  function shouldExport(e) {
238
+ if (e === types_1.Emulators.DATACONNECT && (0, api_1.dataConnectLocalConnString)()) {
239
+ logger_1.logger.info("Skipping export for Data Connect because FIREBASE_DATACONNECT_POSTGRESQL_STRING is set.");
240
+ return false;
241
+ }
237
242
  return types_1.IMPORT_EXPORT_EMULATORS.includes(e) && registry_1.EmulatorRegistry.isRunning(e);
238
243
  }
@@ -41,13 +41,6 @@ exports.ALL_EXPERIMENTS = experiments({
41
41
  "of how that image was created.",
42
42
  public: true,
43
43
  },
44
- dangerouslyAllowFunctionsConfig: {
45
- shortDescription: "Allows the use of deprecated functions.config() API",
46
- fullDescription: "The functions.config() API is deprecated and will be removed on December 31, 2025. " +
47
- "This experiment allows continued use of the API during the migration period.",
48
- default: true,
49
- public: true,
50
- },
51
44
  runfunctions: {
52
45
  shortDescription: "Functions created using the V2 API target Cloud Run Functions (not production ready)",
53
46
  public: false,
@@ -580,6 +580,21 @@ class FirestoreApi {
580
580
  }
581
581
  return database;
582
582
  }
583
+ async cloneDatabase(project, pitrSnapshot, databaseId, encryptionConfig) {
584
+ const url = `/projects/${project}/databases:clone`;
585
+ const payload = {
586
+ databaseId,
587
+ pitrSnapshot,
588
+ encryptionConfig,
589
+ };
590
+ const options = { queryParams: { databaseId: databaseId } };
591
+ const res = await this.apiClient.post(url, payload, options);
592
+ const lro = res.body;
593
+ if (!lro) {
594
+ throw new error_1.FirebaseError("Not found");
595
+ }
596
+ return lro;
597
+ }
583
598
  async listOperations(project, databaseId, limit) {
584
599
  const url = `/projects/${project}/databases/${databaseId}/operations`;
585
600
  const res = await this.apiClient.get(url, {
@@ -1,9 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.booleanXOR = exports.parseFieldName = exports.parseIndexName = void 0;
3
+ exports.getCurrentMinuteAsIsoString = exports.booleanXOR = exports.parseFieldName = exports.parseIndexName = exports.parseDatabaseName = void 0;
4
4
  const error_1 = require("../error");
5
+ const DATABASE_NAME_REGEX = /projects\/([^\/]+?)\/databases\/([^\/]+)/;
5
6
  const INDEX_NAME_REGEX = /projects\/([^\/]+?)\/databases\/([^\/]+?)\/collectionGroups\/([^\/]+?)\/indexes\/([^\/]*)/;
6
7
  const FIELD_NAME_REGEX = /projects\/([^\/]+?)\/databases\/([^\/]+?)\/collectionGroups\/([^\/]+?)\/fields\/([^\/]*)/;
8
+ function parseDatabaseName(name) {
9
+ if (!name) {
10
+ throw new error_1.FirebaseError(`Cannot parse undefined database name.`);
11
+ }
12
+ const m = name.match(DATABASE_NAME_REGEX);
13
+ if (!m || m.length < 3) {
14
+ throw new error_1.FirebaseError(`Error parsing database name: ${name}`);
15
+ }
16
+ return {
17
+ projectId: m[1],
18
+ databaseId: m[2],
19
+ };
20
+ }
21
+ exports.parseDatabaseName = parseDatabaseName;
7
22
  function parseIndexName(name) {
8
23
  if (!name) {
9
24
  throw new error_1.FirebaseError(`Cannot parse undefined index name.`);
@@ -37,3 +52,9 @@ function booleanXOR(a, b) {
37
52
  return !!(Number(a) - Number(b));
38
53
  }
39
54
  exports.booleanXOR = booleanXOR;
55
+ function getCurrentMinuteAsIsoString() {
56
+ const mostRecentTimestamp = new Date(Date.now());
57
+ mostRecentTimestamp.setSeconds(0, 0);
58
+ return mostRecentTimestamp.toISOString();
59
+ }
60
+ exports.getCurrentMinuteAsIsoString = getCurrentMinuteAsIsoString;
@@ -26,7 +26,7 @@ async function discover(dir) {
26
26
  }
27
27
  exports.discover = discover;
28
28
  function init(setup, config) {
29
- (0, child_process_1.execSync)(`npx --yes -p @angular/cli@"${exports.supportedRange}" ng new ${setup.projectId} --directory ${setup.hosting.source} --skip-git`, {
29
+ (0, child_process_1.execSync)(`npx --yes -p @angular/cli@"${exports.supportedRange}" ng new ${setup.projectId} --directory ${setup.featureInfo.hosting.source} --skip-git`, {
30
30
  stdio: "inherit",
31
31
  cwd: config.projectDir,
32
32
  });
@@ -34,7 +34,7 @@ function init(setup, config) {
34
34
  `--project-name=${projectName}`,
35
35
  "--overwrite",
36
36
  "--platforms=web",
37
- setup.hosting.source,
37
+ setup.featureInfo.hosting.source,
38
38
  ], { stdio: "inherit", cwd: config.projectDir });
39
39
  if (result.status !== 0)
40
40
  throw new error_1.FirebaseError("We were not able to create your flutter app, create the application yourself https://docs.flutter.dev/get-started/test-drive?tab=terminal before trying again.");
@@ -214,7 +214,7 @@ async function init(setup, config) {
214
214
  ],
215
215
  });
216
216
  (0, child_process_1.execSync)(`npx --yes create-next-app@"${exports.supportedRange}" -e hello-world ` +
217
- `${setup.hosting.source} --use-npm --${language}`, { stdio: "inherit", cwd: config.projectDir });
217
+ `${setup.featureInfo.hosting.source} --use-npm --${language}`, { stdio: "inherit", cwd: config.projectDir });
218
218
  }
219
219
  exports.init = init;
220
220
  async function ɵcodegenPublicDirectory(sourceDir, destDir, _, context) {
@@ -93,7 +93,7 @@ async function getConfig(cwd) {
93
93
  }
94
94
  exports.getConfig = getConfig;
95
95
  function init(setup, config) {
96
- (0, child_process_1.execSync)(`npx --yes nuxi@"${exports.supportedRange}" init ${setup.hosting.source}`, {
96
+ (0, child_process_1.execSync)(`npx --yes nuxi@"${exports.supportedRange}" init ${setup.featureInfo.hosting.source}`, {
97
97
  stdio: "inherit",
98
98
  cwd: config.projectDir,
99
99
  });
@@ -25,11 +25,14 @@ async function init(setup, config, baseTemplate = "vanilla") {
25
25
  { name: "TypeScript", value: `${baseTemplate}-ts` },
26
26
  ],
27
27
  });
28
- (0, child_process_1.execSync)(`npm create vite@"${exports.supportedRange}" ${setup.hosting.source} --yes -- --template ${template}`, {
28
+ (0, child_process_1.execSync)(`npm create vite@"${exports.supportedRange}" ${setup.featureInfo.hosting.source} --yes -- --template ${template}`, {
29
29
  stdio: "inherit",
30
30
  cwd: config.projectDir,
31
31
  });
32
- (0, child_process_1.execSync)(`npm install`, { stdio: "inherit", cwd: (0, path_1.join)(config.projectDir, setup.hosting.source) });
32
+ (0, child_process_1.execSync)(`npm install`, {
33
+ stdio: "inherit",
34
+ cwd: (0, path_1.join)(config.projectDir, setup.featureInfo.hosting.source),
35
+ });
33
36
  }
34
37
  exports.init = init;
35
38
  const viteDiscoverWithNpmDependency = (dep) => async (dir) => await discover(dir, undefined, dep);
@@ -11,7 +11,7 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  return t;
12
12
  };
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.resolveConfigDir = exports.requireLocal = exports.isRemoteConfig = exports.isLocalConfig = exports.configForCodebase = exports.normalizeAndValidate = exports.validate = exports.assertUnique = exports.validatePrefix = exports.validateCodebase = exports.normalize = exports.DEFAULT_CODEBASE = void 0;
14
+ exports.shouldUseRuntimeConfig = exports.resolveConfigDir = exports.requireLocal = exports.isRemoteConfig = exports.isLocalConfig = exports.configForCodebase = exports.normalizeAndValidate = exports.validate = exports.assertUnique = exports.validatePrefix = exports.validateCodebase = exports.normalize = exports.DEFAULT_CODEBASE = void 0;
15
15
  const error_1 = require("../error");
16
16
  exports.DEFAULT_CODEBASE = "default";
17
17
  function normalize(config) {
@@ -149,3 +149,7 @@ function resolveConfigDir(c) {
149
149
  return c.configDir || c.source;
150
150
  }
151
151
  exports.resolveConfigDir = resolveConfigDir;
152
+ function shouldUseRuntimeConfig(cfg) {
153
+ return isLocalConfig(cfg) && cfg.disallowLegacyRuntimeConfig !== true;
154
+ }
155
+ exports.shouldUseRuntimeConfig = shouldUseRuntimeConfig;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.describeSecret = exports.updateEndpointSecret = exports.pruneAndDestroySecrets = exports.pruneSecrets = exports.versionInUse = exports.inUse = exports.getSecretVersions = exports.of = exports.ensureSecret = exports.ensureValidKey = void 0;
3
+ exports.describeSecret = exports.updateEndpointSecret = exports.pruneAndDestroySecrets = exports.pruneSecrets = exports.versionInUse = exports.inUse = exports.getSecretVersions = exports.of = exports.ensureSecret = exports.validateJsonSecret = exports.ensureValidKey = void 0;
4
4
  const utils = require("../utils");
5
5
  const poller = require("../operation-poller");
6
6
  const gcfV1 = require("../gcp/cloudfunctions");
@@ -61,6 +61,19 @@ async function ensureValidKey(key, options) {
61
61
  return transformedKey;
62
62
  }
63
63
  exports.ensureValidKey = ensureValidKey;
64
+ function validateJsonSecret(secretName, secretValue) {
65
+ try {
66
+ JSON.parse(secretValue);
67
+ }
68
+ catch (e) {
69
+ throw new error_1.FirebaseError(`Provided value for ${secretName} is not valid JSON: ${e.message}\n\n` +
70
+ `For complex JSON values, use:\n` +
71
+ ` firebase functions:secrets:set ${secretName} --data-file <file.json>\n` +
72
+ `Or pipe from stdin:\n` +
73
+ ` cat <file.json> | firebase functions:secrets:set ${secretName} --format=json`);
74
+ }
75
+ }
76
+ exports.validateJsonSecret = validateJsonSecret;
64
77
  async function ensureSecret(projectId, name, options) {
65
78
  try {
66
79
  const secret = await (0, secretManager_1.getSecret)(projectId, name);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.interactiveCreateHostingSite = void 0;
3
+ exports.pickHostingSiteName = void 0;
4
4
  const error_1 = require("../error");
5
5
  const utils_1 = require("../utils");
6
6
  const projectUtils_1 = require("../projectUtils");
@@ -9,11 +9,11 @@ const prompt_1 = require("../prompt");
9
9
  const nameSuggestion = new RegExp("try something like `(.+)`");
10
10
  const prompt = "Please provide an unique, URL-friendly id for your site. Your site's URL will be <site-id>.web.app. " +
11
11
  'We recommend using letters, numbers, and hyphens (e.g. "{project-id}-{random-hash}"):';
12
- async function interactiveCreateHostingSite(siteId, appId, options) {
12
+ async function pickHostingSiteName(siteId, options) {
13
13
  const projectId = (0, projectUtils_1.needProjectId)(options);
14
14
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
15
15
  let id = siteId;
16
- let newSite;
16
+ let nameConfirmed = false;
17
17
  let suggestion;
18
18
  if (!id) {
19
19
  const attempt = await trySiteID(projectNumber, projectId);
@@ -24,7 +24,7 @@ async function interactiveCreateHostingSite(siteId, appId, options) {
24
24
  suggestion = attempt.suggestion;
25
25
  }
26
26
  }
27
- while (!newSite) {
27
+ while (!nameConfirmed) {
28
28
  if (!id || suggestion) {
29
29
  id = await (0, prompt_1.input)({
30
30
  message: prompt,
@@ -32,24 +32,16 @@ async function interactiveCreateHostingSite(siteId, appId, options) {
32
32
  default: suggestion,
33
33
  });
34
34
  }
35
- try {
36
- newSite = await (0, api_1.createSite)(projectNumber, id, appId);
37
- }
38
- catch (err) {
39
- if (!(err instanceof error_1.FirebaseError)) {
40
- throw err;
41
- }
42
- if (options.nonInteractive) {
43
- throw err;
44
- }
35
+ const attempt = await trySiteID(projectNumber, id, options.nonInteractive);
36
+ nameConfirmed = attempt.available;
37
+ suggestion = attempt.suggestion;
38
+ if (!nameConfirmed)
45
39
  id = "";
46
- suggestion = getSuggestionFromError(err);
47
- }
48
40
  }
49
- return newSite;
41
+ return id;
50
42
  }
51
- exports.interactiveCreateHostingSite = interactiveCreateHostingSite;
52
- async function trySiteID(projectNumber, id) {
43
+ exports.pickHostingSiteName = pickHostingSiteName;
44
+ async function trySiteID(projectNumber, id, nonInteractive = false) {
53
45
  try {
54
46
  await (0, api_1.createSite)(projectNumber, id, "", true);
55
47
  return { available: true };
@@ -58,6 +50,9 @@ async function trySiteID(projectNumber, id) {
58
50
  if (!(err instanceof error_1.FirebaseError)) {
59
51
  throw err;
60
52
  }
53
+ if (nonInteractive) {
54
+ throw err;
55
+ }
61
56
  const suggestion = getSuggestionFromError(err);
62
57
  return { available: false, suggestion };
63
58
  }
@@ -50,7 +50,7 @@ const templateServiceInfo = {
50
50
  };
51
51
  async function askQuestions(setup) {
52
52
  const info = {
53
- analyticsFlow: "cli",
53
+ flow: "",
54
54
  appDescription: "",
55
55
  serviceId: "",
56
56
  locationId: "",
@@ -97,7 +97,7 @@ async function askQuestions(setup) {
97
97
  }
98
98
  exports.askQuestions = askQuestions;
99
99
  async function actuate(setup, config, options) {
100
- var _a;
100
+ var _a, _b, _c;
101
101
  const dir = config.get("dataconnect.source", "dataconnect");
102
102
  const dataDir = config.get("emulators.dataconnect.dataDir", `${dir}/.dataconnect/pgliteData`);
103
103
  config.set("emulators.dataconnect.dataDir", dataDir);
@@ -109,37 +109,38 @@ async function actuate(setup, config, options) {
109
109
  info.cloudSqlInstanceId = info.cloudSqlInstanceId || `${info.serviceId.toLowerCase()}-fdc`;
110
110
  info.locationId = info.locationId || exports.FDC_DEFAULT_REGION;
111
111
  info.cloudSqlDatabase = info.cloudSqlDatabase || `fdcdb`;
112
+ const startTime = Date.now();
112
113
  try {
113
114
  await actuateWithInfo(setup, config, info, options);
114
115
  await sdk.actuate(setup, config);
115
116
  }
116
117
  finally {
117
- void (0, track_1.trackGA4)("dataconnect_init", {
118
- flow: info.analyticsFlow,
119
- project_status: setup.projectId
120
- ? setup.isBillingEnabled
118
+ const sdkInfo = (_b = setup.featureInfo) === null || _b === void 0 ? void 0 : _b.dataconnectSdk;
119
+ const source = ((_c = setup.featureInfo) === null || _c === void 0 ? void 0 : _c.dataconnectSource) || "init";
120
+ void (0, track_1.trackGA4)("dataconnect_init", Object.assign({ source, flow: info.flow.substring(1), project_status: setup.projectId
121
+ ? (await (0, cloudbilling_1.isBillingEnabled)(setup))
121
122
  ? info.shouldProvisionCSQL
122
123
  ? "blaze_provisioned_csql"
123
124
  : "blaze"
124
125
  : "spark"
125
- : "missing",
126
- });
126
+ : "missing" }, (sdkInfo ? sdk.initAppCounters(sdkInfo) : {})), Date.now() - startTime);
127
127
  }
128
128
  if (info.appDescription) {
129
129
  setup.instructions.push(`You can visualize the Data Connect Schema in Firebase Console:
130
130
 
131
131
  https://console.firebase.google.com/project/${setup.projectId}/dataconnect/locations/${info.locationId}/services/${info.serviceId}/schema`);
132
132
  }
133
- if (!setup.isBillingEnabled) {
133
+ if (!(await (0, cloudbilling_1.isBillingEnabled)(setup))) {
134
134
  setup.instructions.push((0, freeTrial_1.upgradeInstructions)(setup.projectId || "your-firebase-project"));
135
135
  }
136
136
  setup.instructions.push(`Install the Data Connect VS Code Extensions. You can explore Data Connect Query on local pgLite and Cloud SQL Postgres Instance.`);
137
137
  }
138
138
  exports.actuate = actuate;
139
139
  async function actuateWithInfo(setup, config, info, options) {
140
+ var _a;
140
141
  const projectId = setup.projectId;
141
142
  if (!projectId) {
142
- info.analyticsFlow += "_save_template";
143
+ info.flow += "_save_template";
143
144
  return await writeFiles(config, info, templateServiceInfo, options);
144
145
  }
145
146
  await (0, ensureApis_1.ensureApis)(projectId, true);
@@ -151,7 +152,7 @@ async function actuateWithInfo(setup, config, info, options) {
151
152
  instanceId: info.cloudSqlInstanceId,
152
153
  databaseId: info.cloudSqlDatabase,
153
154
  requireGoogleMlIntegration: false,
154
- source: info.analyticsFlow.startsWith("mcp") ? "mcp_init" : "init",
155
+ source: ((_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.dataconnectSource) || "init",
155
156
  });
156
157
  }
157
158
  const serviceName = `projects/${projectId}/locations/${info.locationId}/services/${info.serviceId}`;
@@ -160,10 +161,10 @@ async function actuateWithInfo(setup, config, info, options) {
160
161
  await downloadService(info, serviceName);
161
162
  }
162
163
  if (info.serviceGql) {
163
- info.analyticsFlow += "_save_downloaded";
164
+ info.flow += "_save_downloaded";
164
165
  return await writeFiles(config, info, info.serviceGql, options);
165
166
  }
166
- info.analyticsFlow += "_save_template";
167
+ info.flow += "_save_template";
167
168
  return await writeFiles(config, info, templateServiceInfo, options);
168
169
  }
169
170
  const serviceAlreadyExists = !(await (0, client_1.createService)(projectId, info.locationId, info.serviceId));
@@ -171,7 +172,7 @@ async function actuateWithInfo(setup, config, info, options) {
171
172
  const schemaFiles = [{ path: "schema.gql", content: schemaGql }];
172
173
  if (serviceAlreadyExists) {
173
174
  (0, utils_1.logLabeledError)("dataconnect", `Data Connect Service ${serviceName} already exists. Skip saving them...`);
174
- info.analyticsFlow += "_save_gemini_service_already_exists";
175
+ info.flow += "_save_gemini_service_already_exists";
175
176
  return await writeFiles(config, info, { schemaGql: schemaFiles, connectors: [] }, options);
176
177
  }
177
178
  await (0, utils_1.promiseWithSpinner)(async () => {
@@ -198,12 +199,12 @@ async function actuateWithInfo(setup, config, info, options) {
198
199
  ],
199
200
  },
200
201
  ];
201
- info.analyticsFlow += "_save_gemini";
202
+ info.flow += "_save_gemini";
202
203
  await writeFiles(config, info, { schemaGql: schemaFiles, connectors: connectors, seedDataGql: seedDataGql }, options);
203
204
  }
204
205
  catch (err) {
205
206
  (0, utils_1.logLabeledError)("dataconnect", `Operation Generation failed...`);
206
- info.analyticsFlow += "_save_gemini_operation_error";
207
+ info.flow += "_save_gemini_operation_error";
207
208
  await writeFiles(config, info, { schemaGql: schemaFiles, connectors: [] }, options);
208
209
  throw err;
209
210
  }
@@ -322,10 +323,10 @@ async function promptForExistingServices(setup, info) {
322
323
  if (!choice) {
323
324
  const existingServiceIds = existingServices.map((s) => s.name.split("/").pop());
324
325
  info.serviceId = (0, utils_1.newUniqueId)(defaultServiceId(), existingServiceIds);
325
- info.analyticsFlow += "_pick_new_service";
326
+ info.flow += "_pick_new_service";
326
327
  return;
327
328
  }
328
- info.analyticsFlow += "_pick_existing_service";
329
+ info.flow += "_pick_existing_service";
329
330
  const serviceName = (0, names_1.parseServiceName)(choice.name);
330
331
  info.serviceId = serviceName.serviceId;
331
332
  info.locationId = serviceName.location;
@@ -429,11 +430,11 @@ async function promptForCloudSQL(setup, info) {
429
430
  choices,
430
431
  });
431
432
  if (info.cloudSqlInstanceId !== "") {
432
- info.analyticsFlow += "_pick_existing_csql";
433
+ info.flow += "_pick_existing_csql";
433
434
  info.locationId = choices.find((c) => c.value === info.cloudSqlInstanceId).location;
434
435
  }
435
436
  else {
436
- info.analyticsFlow += "_pick_new_csql";
437
+ info.flow += "_pick_new_csql";
437
438
  info.cloudSqlInstanceId = await (0, prompt_1.input)({
438
439
  message: `What ID would you like to use for your new CloudSQL instance?`,
439
440
  default: (0, utils_1.newUniqueId)(`${defaultServiceId().toLowerCase()}-fdc`, instances.map((i) => i.name)),
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.addSdkGenerateToConnectorYaml = exports.actuate = exports.chooseApp = exports.askQuestions = exports.FDC_SDK_PLATFORM_ENV = exports.FDC_SDK_FRAMEWORKS_ENV = exports.FDC_APP_FOLDER = void 0;
3
+ exports.addSdkGenerateToConnectorYaml = exports.initAppCounters = exports.actuate = exports.chooseApp = exports.askQuestions = exports.FDC_SDK_PLATFORM_ENV = exports.FDC_SDK_FRAMEWORKS_ENV = exports.FDC_APP_FOLDER = void 0;
4
4
  const yaml = require("yaml");
5
5
  const clc = require("colorette");
6
6
  const path = require("path");
@@ -16,6 +16,7 @@ const auth_1 = require("../../../auth");
16
16
  const create_app_1 = require("./create_app");
17
17
  const track_1 = require("../../../track");
18
18
  const fsutils_1 = require("../../../fsutils");
19
+ const cloudbilling_1 = require("../../../gcp/cloudbilling");
19
20
  exports.FDC_APP_FOLDER = "FDC_APP_FOLDER";
20
21
  exports.FDC_SDK_FRAMEWORKS_ENV = "FDC_SDK_FRAMEWORKS";
21
22
  exports.FDC_SDK_PLATFORM_ENV = "FDC_SDK_PLATFORM";
@@ -37,7 +38,7 @@ async function askQuestions(setup) {
37
38
  { name: `React${npxMissingWarning}`, value: "react" },
38
39
  { name: `Next.JS${npxMissingWarning}`, value: "next" },
39
40
  { name: `Flutter${flutterMissingWarning}`, value: "flutter" },
40
- { name: "no", value: "no" },
41
+ { name: "skip", value: "skip" },
41
42
  ],
42
43
  });
43
44
  try {
@@ -51,7 +52,7 @@ async function askQuestions(setup) {
51
52
  case "flutter":
52
53
  await (0, create_app_1.createFlutterApp)((0, utils_1.newUniqueId)("flutter_app", (0, fsutils_1.listFiles)(cwd)));
53
54
  break;
54
- case "no":
55
+ case "skip":
55
56
  break;
56
57
  }
57
58
  }
@@ -69,7 +70,7 @@ async function chooseApp() {
69
70
  (0, utils_1.logLabeledSuccess)("dataconnect", `Detected existing apps ${apps.map((a) => (0, appUtils_1.appDescription)(a)).join(", ")}`);
70
71
  }
71
72
  else {
72
- (0, utils_1.logLabeledWarning)("dataconnect", "No app exists in the current directory.");
73
+ (0, utils_1.logLabeledWarning)("dataconnect", "Cannot detect an existing app in the current directory.");
73
74
  }
74
75
  const envAppFolder = (0, utils_1.envOverride)(exports.FDC_APP_FOLDER, "");
75
76
  const envPlatform = (0, utils_1.envOverride)(exports.FDC_SDK_PLATFORM_ENV, "");
@@ -115,33 +116,55 @@ async function chooseApp() {
115
116
  }
116
117
  exports.chooseApp = chooseApp;
117
118
  async function actuate(setup, config) {
118
- var _a, _b;
119
- const fdcInfo = (_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.dataconnect;
120
- const sdkInfo = (_b = setup.featureInfo) === null || _b === void 0 ? void 0 : _b.dataconnectSdk;
119
+ var _a, _b, _c;
120
+ const sdkInfo = (_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.dataconnectSdk;
121
121
  if (!sdkInfo) {
122
122
  throw new Error("Data Connect SDK feature RequiredInfo is not provided");
123
123
  }
124
+ const startTime = Date.now();
124
125
  try {
125
126
  await actuateWithInfo(setup, config, sdkInfo);
126
127
  }
127
128
  finally {
128
- let flow = "no_app";
129
- if (sdkInfo.apps.length) {
130
- const platforms = sdkInfo.apps.map((a) => a.platform.toLowerCase()).sort();
131
- flow = `${platforms.join("_")}_app`;
132
- }
133
- if (fdcInfo) {
134
- fdcInfo.analyticsFlow += `_${flow}`;
135
- }
136
- else {
137
- void (0, track_1.trackGA4)("dataconnect_init", {
138
- project_status: setup.projectId ? (setup.isBillingEnabled ? "blaze" : "spark") : "missing",
139
- flow: `cli_sdk_${flow}`,
140
- });
129
+ const fdcInfo = (_b = setup.featureInfo) === null || _b === void 0 ? void 0 : _b.dataconnect;
130
+ if (!fdcInfo) {
131
+ const source = ((_c = setup.featureInfo) === null || _c === void 0 ? void 0 : _c.dataconnectSource) || "init_sdk";
132
+ void (0, track_1.trackGA4)("dataconnect_init", Object.assign({ source, project_status: setup.projectId
133
+ ? (await (0, cloudbilling_1.isBillingEnabled)(setup))
134
+ ? "blaze"
135
+ : "spark"
136
+ : "missing" }, initAppCounters(sdkInfo)), Date.now() - startTime);
141
137
  }
142
138
  }
143
139
  }
144
140
  exports.actuate = actuate;
141
+ function initAppCounters(info) {
142
+ var _a;
143
+ const counts = {
144
+ num_web_apps: 0,
145
+ num_android_apps: 0,
146
+ num_ios_apps: 0,
147
+ num_flutter_apps: 0,
148
+ };
149
+ for (const app of (_a = info.apps) !== null && _a !== void 0 ? _a : []) {
150
+ switch (app.platform) {
151
+ case appUtils_1.Platform.WEB:
152
+ counts.num_web_apps++;
153
+ break;
154
+ case appUtils_1.Platform.ANDROID:
155
+ counts.num_android_apps++;
156
+ break;
157
+ case appUtils_1.Platform.IOS:
158
+ counts.num_ios_apps++;
159
+ break;
160
+ case appUtils_1.Platform.FLUTTER:
161
+ counts.num_flutter_apps++;
162
+ break;
163
+ }
164
+ }
165
+ return counts;
166
+ }
167
+ exports.initAppCounters = initAppCounters;
145
168
  async function actuateWithInfo(setup, config, info) {
146
169
  if (!info.apps.length) {
147
170
  info.apps = await (0, appUtils_1.detectApps)(cwd);
@@ -245,7 +268,7 @@ function addSdkGenerateToConnectorYaml(connectorInfo, connectorYaml, app) {
245
268
  case appUtils_1.Platform.FLUTTER: {
246
269
  const dartSdk = {
247
270
  outputDir: path.relative(connectorDir, path.join(appDir, `lib/dataconnect_generated`)),
248
- package: "dataconnect_generated",
271
+ package: "dataconnect_generated/generated.dart",
249
272
  };
250
273
  if (!(0, lodash_1.isArray)(generate === null || generate === void 0 ? void 0 : generate.dartSdk)) {
251
274
  generate.dartSdk = generate.dartSdk ? [generate.dartSdk] : [];
@@ -98,6 +98,7 @@ async function initNewCodebase(setup, config) {
98
98
  setup.config.functions.push({
99
99
  source,
100
100
  codebase,
101
+ disallowLegacyRuntimeConfig: true,
101
102
  });
102
103
  setup.functions.source = source;
103
104
  setup.functions.codebase = codebase;