firebase-tools 14.3.1 → 14.5.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 (162) hide show
  1. package/lib/api.js +10 -2
  2. package/lib/apphosting/backend.js +74 -18
  3. package/lib/apphosting/rollout.js +2 -2
  4. package/lib/apphosting/secrets/dialogs.js +4 -4
  5. package/lib/apphosting/secrets/index.js +2 -2
  6. package/lib/auth.js +22 -2
  7. package/lib/bin/cli.js +1 -33
  8. package/lib/bin/mcp.js +13 -2
  9. package/lib/checkValidTargetFilters.js +1 -0
  10. package/lib/command.js +3 -2
  11. package/lib/commands/apphosting-secrets-grantaccess.js +1 -1
  12. package/lib/commands/deploy.js +1 -0
  13. package/lib/commands/init.js +1 -4
  14. package/lib/commands/login-use.js +2 -11
  15. package/lib/commands/use.js +9 -8
  16. package/lib/config.js +43 -24
  17. package/lib/crashlytics/listTopIssues.js +44 -0
  18. package/lib/dataconnect/cloudAICompanionClient.js +72 -0
  19. package/lib/dataconnect/cloudAICompanionTypes.js +2 -0
  20. package/lib/dataconnect/fileUtils.js +11 -4
  21. package/lib/dataconnect/schemaMigration.js +6 -7
  22. package/lib/deploy/apphosting/args.js +2 -0
  23. package/lib/deploy/apphosting/deploy.js +77 -0
  24. package/lib/deploy/apphosting/index.js +9 -0
  25. package/lib/deploy/apphosting/prepare.js +147 -0
  26. package/lib/deploy/apphosting/release.js +56 -0
  27. package/lib/deploy/apphosting/util.js +65 -0
  28. package/lib/deploy/extensions/v2FunctionHelper.js +2 -1
  29. package/lib/deploy/firestore/deploy.js +47 -4
  30. package/lib/deploy/functions/checkIam.js +3 -3
  31. package/lib/deploy/functions/ensure.js +2 -1
  32. package/lib/deploy/functions/prepare.js +23 -16
  33. package/lib/deploy/functions/release/fabricator.js +4 -4
  34. package/lib/deploy/functions/release/index.js +1 -1
  35. package/lib/deploy/functions/runtimes/python/index.js +3 -0
  36. package/lib/deploy/functions/runtimes/supported/types.js +17 -11
  37. package/lib/deploy/index.js +2 -0
  38. package/lib/emulator/apphosting/index.js +1 -0
  39. package/lib/emulator/apphosting/serve.js +77 -3
  40. package/lib/emulator/auth/widget_ui.js +2 -1
  41. package/lib/emulator/controller.js +18 -4
  42. package/lib/emulator/dataconnectEmulator.js +9 -2
  43. package/lib/emulator/downloadableEmulatorInfo.json +81 -0
  44. package/lib/emulator/downloadableEmulators.js +28 -108
  45. package/lib/experiments.js +1 -1
  46. package/lib/extensions/manifest.js +2 -5
  47. package/lib/frameworks/angular/index.js +1 -1
  48. package/lib/frameworks/angular/utils.js +17 -6
  49. package/lib/fsAsync.js +9 -2
  50. package/lib/gcp/apphosting.js +13 -1
  51. package/lib/gcp/auth.js +32 -2
  52. package/lib/gcp/cloudbilling.js +12 -1
  53. package/lib/gcp/cloudfunctions.js +1 -2
  54. package/lib/gcp/cloudfunctionsv2.js +1 -2
  55. package/lib/gcp/cloudscheduler.js +2 -2
  56. package/lib/gcp/computeEngine.js +19 -2
  57. package/lib/gcp/devConnect.js +6 -1
  58. package/lib/gcp/firestore.js +24 -1
  59. package/lib/gcp/iam.js +1 -5
  60. package/lib/gcp/run.js +19 -1
  61. package/lib/gcp/storage.js +25 -1
  62. package/lib/index.js +1 -2
  63. package/lib/init/features/apphosting.js +85 -6
  64. package/lib/init/features/database.js +63 -52
  65. package/lib/init/features/dataconnect/index.js +78 -79
  66. package/lib/init/features/dataconnect/sdk.js +19 -6
  67. package/lib/init/features/emulators.js +12 -7
  68. package/lib/init/features/firestore/index.js +80 -39
  69. package/lib/init/features/firestore/indexes.js +29 -31
  70. package/lib/init/features/firestore/rules.js +35 -48
  71. package/lib/init/features/functions/index.js +2 -0
  72. package/lib/init/features/functions/javascript.js +3 -2
  73. package/lib/init/features/functions/typescript.js +3 -2
  74. package/lib/init/features/genkit/index.js +18 -10
  75. package/lib/init/features/hosting/github.js +3 -2
  76. package/lib/init/features/hosting/index.js +9 -8
  77. package/lib/init/features/index.js +10 -5
  78. package/lib/init/features/remoteconfig.js +3 -2
  79. package/lib/init/features/storage.js +31 -8
  80. package/lib/init/index.js +80 -24
  81. package/lib/logger.js +71 -7
  82. package/lib/management/projects.js +24 -1
  83. package/lib/mcp/errors.js +1 -1
  84. package/lib/mcp/index.js +142 -46
  85. package/lib/mcp/tool.js +2 -1
  86. package/lib/mcp/tools/apphosting/fetch_logs.js +69 -0
  87. package/lib/mcp/tools/apphosting/index.js +6 -0
  88. package/lib/mcp/tools/apphosting/list_backends.js +51 -0
  89. package/lib/mcp/tools/auth/get_user.js +16 -7
  90. package/lib/mcp/tools/auth/index.js +8 -1
  91. package/lib/mcp/tools/auth/list_users.js +47 -0
  92. package/lib/mcp/tools/auth/set_claims.js +20 -11
  93. package/lib/mcp/tools/auth/set_sms_region_policy.js +1 -1
  94. package/lib/mcp/tools/core/consult_assistant.js +1 -1
  95. package/lib/mcp/tools/core/create_android_sha.js +40 -0
  96. package/lib/mcp/tools/core/create_app.js +90 -0
  97. package/lib/mcp/tools/core/create_project.js +68 -0
  98. package/lib/mcp/tools/core/get_admin_sdk_config.js +26 -0
  99. package/lib/mcp/tools/core/get_environment.js +51 -0
  100. package/lib/mcp/tools/core/get_sdk_config.js +6 -3
  101. package/lib/mcp/tools/core/index.js +21 -2
  102. package/lib/mcp/tools/core/init.js +153 -0
  103. package/lib/mcp/tools/core/list_apps.js +10 -5
  104. package/lib/mcp/tools/core/list_projects.js +45 -0
  105. package/lib/mcp/tools/core/update_environment.js +55 -0
  106. package/lib/mcp/tools/crashlytics/index.js +5 -0
  107. package/lib/mcp/tools/crashlytics/list_top_issues.js +34 -0
  108. package/lib/mcp/tools/dataconnect/converter.js +13 -1
  109. package/lib/mcp/tools/dataconnect/emulator.js +32 -0
  110. package/lib/mcp/tools/dataconnect/execute_graphql.js +24 -8
  111. package/lib/mcp/tools/dataconnect/execute_graphql_read.js +24 -8
  112. package/lib/mcp/tools/dataconnect/execute_mutation.js +27 -15
  113. package/lib/mcp/tools/dataconnect/execute_query.js +26 -14
  114. package/lib/mcp/tools/dataconnect/generate_operation.js +7 -7
  115. package/lib/mcp/tools/dataconnect/generate_schema.js +1 -1
  116. package/lib/mcp/tools/dataconnect/get_connector.js +7 -7
  117. package/lib/mcp/tools/dataconnect/get_schema.js +5 -5
  118. package/lib/mcp/tools/dataconnect/index.js +1 -5
  119. package/lib/mcp/tools/dataconnect/list_services.js +1 -1
  120. package/lib/mcp/tools/firestore/converter.js +47 -1
  121. package/lib/mcp/tools/firestore/delete_document.js +37 -0
  122. package/lib/mcp/tools/firestore/index.js +12 -2
  123. package/lib/mcp/tools/firestore/list_collections.js +1 -6
  124. package/lib/mcp/tools/firestore/query_collection.js +122 -0
  125. package/lib/mcp/tools/index.js +37 -16
  126. package/lib/mcp/tools/messaging/index.js +5 -0
  127. package/lib/mcp/tools/messaging/send_message.js +42 -0
  128. package/lib/mcp/tools/remoteconfig/get_template.js +27 -0
  129. package/lib/mcp/tools/remoteconfig/index.js +7 -0
  130. package/lib/mcp/tools/remoteconfig/publish_template.js +34 -0
  131. package/lib/mcp/tools/remoteconfig/rollback_template.js +29 -0
  132. package/lib/mcp/tools/rules/get_rules.js +29 -0
  133. package/lib/mcp/tools/rules/validate_rules.js +98 -0
  134. package/lib/mcp/tools/storage/get_download_url.js +8 -5
  135. package/lib/mcp/tools/storage/index.js +7 -2
  136. package/lib/mcp/types.js +10 -1
  137. package/lib/mcp/util.js +165 -2
  138. package/lib/mcp/util.test.js +468 -0
  139. package/lib/messaging/interfaces.js +2 -0
  140. package/lib/messaging/sendMessage.js +48 -0
  141. package/lib/prompt.js +1 -1
  142. package/lib/remoteconfig/publish.js +39 -0
  143. package/lib/requireAuth.js +2 -2
  144. package/lib/track.js +1 -1
  145. package/lib/utils.js +2 -37
  146. package/package.json +2 -1
  147. package/schema/connector-yaml.json +12 -0
  148. package/schema/extension-yaml.json +17 -4
  149. package/schema/firebase-config.json +65 -10
  150. package/standalone/package.json +1 -1
  151. package/templates/dataconnect-prompts/operation-generation-cursor-windsurf-rule.txt +273 -0
  152. package/templates/dataconnect-prompts/schema-generation-cursor-windsurf-rule.txt +653 -0
  153. package/templates/genkit/firebase.1.0.0.template +5 -0
  154. package/templates/init/firestore/firestore.rules +2 -0
  155. package/templates/init/functions/javascript/package.lint.json +1 -1
  156. package/templates/init/functions/javascript/package.nolint.json +1 -1
  157. package/templates/init/functions/typescript/package.lint.json +2 -2
  158. package/templates/init/functions/typescript/package.nolint.json +3 -3
  159. package/lib/mcp/tools/directory/get_project_directory.js +0 -20
  160. package/lib/mcp/tools/directory/index.js +0 -6
  161. package/lib/mcp/tools/directory/set_project_directory.js +0 -33
  162. package/lib/mcp/tools/firestore/get_rules.js +0 -26
package/lib/config.js CHANGED
@@ -156,11 +156,20 @@ class Config {
156
156
  }
157
157
  }
158
158
  writeProjectFile(p, content) {
159
- if (typeof content !== "string") {
160
- content = JSON.stringify(content, null, 2) + "\n";
159
+ const path = this.path(p);
160
+ fs.ensureFileSync(path);
161
+ fs.writeFileSync(path, stringifyContent(content), "utf8");
162
+ switch (p) {
163
+ case "firebase.json":
164
+ utils.logSuccess("Wrote configuration info to " + clc.bold("firebase.json"));
165
+ break;
166
+ case ".firebaserc":
167
+ utils.logSuccess("Wrote project information to " + clc.bold(".firebaserc"));
168
+ break;
169
+ default:
170
+ utils.logSuccess("Wrote " + clc.bold(p));
171
+ break;
161
172
  }
162
- fs.ensureFileSync(this.path(p));
163
- fs.writeFileSync(this.path(p), content, "utf8");
164
173
  }
165
174
  projectFileExists(p) {
166
175
  return fs.existsSync(this.path(p));
@@ -168,32 +177,35 @@ class Config {
168
177
  deleteProjectFile(p) {
169
178
  fs.removeSync(this.path(p));
170
179
  }
171
- async askWriteProjectFile(path, content, force, confirmByDefault) {
180
+ async confirmWriteProjectFile(path, content, confirmByDefault) {
172
181
  const writeTo = this.path(path);
173
- let next = true;
174
- if (typeof content !== "string") {
175
- content = JSON.stringify(content, null, 2) + "\n";
176
- }
177
- let existingContent;
178
- if (fsutils.fileExistsSync(writeTo)) {
179
- existingContent = fsutils.readFile(writeTo);
180
- }
181
- if (existingContent && existingContent !== content && !force) {
182
- next = await (0, prompt_1.confirm)({
183
- message: "File " + clc.underline(path) + " already exists. Overwrite?",
184
- default: !!confirmByDefault,
185
- });
182
+ if (!fsutils.fileExistsSync(writeTo)) {
183
+ return true;
186
184
  }
187
- if (existingContent === content) {
185
+ const existingContent = fsutils.readFile(writeTo);
186
+ const newContent = stringifyContent(content);
187
+ if (existingContent === newContent) {
188
188
  utils.logBullet(clc.bold(path) + " is unchanged");
189
+ return false;
189
190
  }
190
- else if (next) {
191
- this.writeProjectFile(path, content);
192
- utils.logSuccess("Wrote " + clc.bold(path));
193
- }
194
- else {
191
+ const shouldWrite = await (0, prompt_1.confirm)({
192
+ message: "File " + clc.underline(path) + " already exists. Overwrite?",
193
+ default: !!confirmByDefault,
194
+ });
195
+ if (!shouldWrite) {
195
196
  utils.logBullet("Skipping write of " + clc.bold(path));
197
+ return false;
196
198
  }
199
+ return true;
200
+ }
201
+ async askWriteProjectFile(path, content, force, confirmByDefault) {
202
+ if (!force) {
203
+ const shouldWrite = await this.confirmWriteProjectFile(path, content, confirmByDefault);
204
+ if (!shouldWrite) {
205
+ return;
206
+ }
207
+ }
208
+ this.writeProjectFile(path, content);
197
209
  }
198
210
  static load(options, allowMissing) {
199
211
  const pd = (0, detectProjectRoot_1.detectProjectRoot)(options);
@@ -239,4 +251,11 @@ Config.MATERIALIZE_TARGETS = [
239
251
  "storage",
240
252
  "remoteconfig",
241
253
  "dataconnect",
254
+ "apphosting",
242
255
  ];
256
+ function stringifyContent(content) {
257
+ if (typeof content === "string") {
258
+ return content;
259
+ }
260
+ return JSON.stringify(content, null, 2) + "\n";
261
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listTopIssues = void 0;
4
+ const apiv2_1 = require("../apiv2");
5
+ const logger_1 = require("../logger");
6
+ const error_1 = require("../error");
7
+ const api_1 = require("../api");
8
+ const TIMEOUT = 10000;
9
+ const apiClient = new apiv2_1.Client({
10
+ urlPrefix: (0, api_1.crashlyticsApiOrigin)(),
11
+ apiVersion: "v1alpha",
12
+ });
13
+ async function listTopIssues(projectId, appId, issueCount) {
14
+ try {
15
+ const queryParams = new URLSearchParams();
16
+ queryParams.set("page_size", `${issueCount}`);
17
+ const requestProjectId = parseProjectId(appId);
18
+ if (requestProjectId === undefined) {
19
+ throw new error_1.FirebaseError("Unable to get the projectId from the AppId.");
20
+ }
21
+ const response = await apiClient.request({
22
+ method: "GET",
23
+ headers: {
24
+ "Content-Type": "application/json",
25
+ },
26
+ path: `/projects/${requestProjectId}/apps/${appId}/reports/topIssues`,
27
+ queryParams: queryParams,
28
+ timeout: TIMEOUT,
29
+ });
30
+ return response.body;
31
+ }
32
+ catch (err) {
33
+ logger_1.logger.debug(err.message);
34
+ throw new error_1.FirebaseError(`Failed to fetch the top issues for the Firebase Project ${projectId}, AppId ${appId}. Error: ${err}.`, { original: err });
35
+ }
36
+ }
37
+ exports.listTopIssues = listTopIssues;
38
+ function parseProjectId(appId) {
39
+ const appIdParts = appId.split(":");
40
+ if (appIdParts.length > 1) {
41
+ return appIdParts[1];
42
+ }
43
+ return undefined;
44
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.callCloudAICompanion = exports.cloudAICompationClient = void 0;
4
+ const apiv2_1 = require("../apiv2");
5
+ const api_1 = require("../api");
6
+ const CLOUD_AI_COMPANION_VERSION = "v1";
7
+ const CLIENT_CONTEXT_NAME_IDENTIFIER = "firebase_vscode";
8
+ const FIREBASE_CHAT_REQUEST_CONTEXT_TYPE_NAME = "type.googleapis.com/google.cloud.cloudaicompanion.v1main.FirebaseChatRequestContext";
9
+ const FDC_SCHEMA_EXPERIENCE_CONTEXT = "/appeco/firebase/fdc-schema-generator";
10
+ const FDC_OPERATION_EXPERIENCE_CONTEXT = "/appeco/firebase/fdc-query-generator";
11
+ const USER_AUTHOR = "USER";
12
+ function cloudAICompationClient() {
13
+ return new apiv2_1.Client({
14
+ urlPrefix: (0, api_1.cloudAiCompanionOrigin)(),
15
+ apiVersion: CLOUD_AI_COMPANION_VERSION,
16
+ auth: true,
17
+ });
18
+ }
19
+ exports.cloudAICompationClient = cloudAICompationClient;
20
+ async function callCloudAICompanion(client, vscodeRequest, type) {
21
+ const request = buildRequest(vscodeRequest, type);
22
+ const { projectId } = getServiceParts(vscodeRequest.servicePath);
23
+ const instance = toChatResourceName(projectId);
24
+ try {
25
+ const res = await client.post(`${instance}:completeTask`, request);
26
+ return res.body;
27
+ }
28
+ catch (error) {
29
+ return { output: { messages: [] }, error: error };
30
+ }
31
+ }
32
+ exports.callCloudAICompanion = callCloudAICompanion;
33
+ function buildRequest({ servicePath, naturalLanguageQuery, chatHistory }, type) {
34
+ const { serviceId } = getServiceParts(servicePath);
35
+ const input = {
36
+ messages: [
37
+ ...chatHistory,
38
+ {
39
+ author: USER_AUTHOR,
40
+ content: naturalLanguageQuery,
41
+ },
42
+ ],
43
+ };
44
+ const clientContext = {
45
+ name: CLIENT_CONTEXT_NAME_IDENTIFIER,
46
+ additionalContext: {
47
+ "@type": FIREBASE_CHAT_REQUEST_CONTEXT_TYPE_NAME,
48
+ fdcInfo: {
49
+ serviceId,
50
+ fdcServiceName: servicePath,
51
+ requiresQuery: true,
52
+ },
53
+ },
54
+ };
55
+ return {
56
+ input,
57
+ clientContext,
58
+ experienceContext: {
59
+ experience: type === "schema" ? FDC_SCHEMA_EXPERIENCE_CONTEXT : FDC_OPERATION_EXPERIENCE_CONTEXT,
60
+ },
61
+ };
62
+ }
63
+ function toChatResourceName(projectId) {
64
+ return `projects/${projectId}/locations/global/instances/default`;
65
+ }
66
+ function getServiceParts(name) {
67
+ const match = name.match(/projects\/([^/]*)\/locations\/([^/]*)\/services\/([^/]*)/);
68
+ if (!match) {
69
+ throw new Error(`Invalid service name: ${name}`);
70
+ }
71
+ return { projectId: match[1], locationId: match[2], serviceId: match[3] };
72
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getFrameworksFromPackageJson = exports.frameworksMap = exports.SUPPORTED_FRAMEWORKS = exports.resolvePackageJson = exports.getPlatformFromFolder = exports.pickService = exports.readGQLFiles = exports.readConnectorYaml = exports.readDataConnectYaml = exports.readFirebaseJson = void 0;
4
4
  const fs = require("fs-extra");
5
5
  const path = require("path");
6
+ const clc = require("colorette");
6
7
  const error_1 = require("../error");
7
8
  const types_1 = require("./types");
8
9
  const utils_1 = require("../utils");
@@ -77,10 +78,16 @@ async function pickService(projectId, config, serviceId) {
77
78
  const serviceCfgs = readFirebaseJson(config);
78
79
  let serviceInfo;
79
80
  if (serviceCfgs.length === 0) {
80
- throw new error_1.FirebaseError("No Data Connect services found in firebase.json.");
81
+ throw new error_1.FirebaseError("No Data Connect services found in firebase.json." +
82
+ `\nYou can run ${clc.bold("firebase init dataconnect")} to add a Data Connect service.`);
81
83
  }
82
84
  else if (serviceCfgs.length === 1) {
83
85
  serviceInfo = await (0, load_1.load)(projectId, config, serviceCfgs[0].source);
86
+ if (serviceId && serviceId !== serviceInfo.dataConnectYaml.serviceId) {
87
+ throw new error_1.FirebaseError(`No service named ${serviceId} declared in firebase.json. Found ${serviceInfo.dataConnectYaml.serviceId}.` +
88
+ `\nYou can run ${clc.bold("firebase init dataconnect")} to add this Data Connect service.`);
89
+ }
90
+ return serviceInfo;
84
91
  }
85
92
  else {
86
93
  if (!serviceId) {
@@ -89,11 +96,11 @@ async function pickService(projectId, config, serviceId) {
89
96
  const infos = await Promise.all(serviceCfgs.map((c) => (0, load_1.load)(projectId, config, c.source)));
90
97
  const maybe = infos.find((i) => i.dataConnectYaml.serviceId === serviceId);
91
98
  if (!maybe) {
92
- throw new error_1.FirebaseError(`No service named ${serviceId} declared in firebase.json.`);
99
+ throw new error_1.FirebaseError(`No service named ${serviceId} declared in firebase.json. Found ${infos.map((i) => i.dataConnectYaml.serviceId).join(", ")}.` +
100
+ `\nYou can run ${clc.bold("firebase init dataconnect")} to add this Data Connect service.`);
93
101
  }
94
- serviceInfo = maybe;
102
+ return maybe;
95
103
  }
96
- return serviceInfo;
97
104
  }
98
105
  exports.pickService = pickService;
99
106
  const WEB_INDICATORS = ["package.json", "package-lock.json", "node_modules"];
@@ -29,6 +29,7 @@ async function setupSchemaIfNecessary(instanceId, databaseId, options) {
29
29
  return schemaInfo.setupStatus;
30
30
  }
31
31
  async function diffSchema(options, schema, schemaValidation) {
32
+ (0, utils_1.logLabeledBullet)("dataconnect", `generating required schema changes...`);
32
33
  const { serviceName, instanceName, databaseId, instanceId } = getIdentifiers(schema);
33
34
  await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, false);
34
35
  let diffs = [];
@@ -36,15 +37,12 @@ async function diffSchema(options, schema, schemaValidation) {
36
37
  let validationMode = schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE";
37
38
  setSchemaValidationMode(schema, validationMode);
38
39
  try {
39
- if (!schemaValidation) {
40
- (0, utils_1.logLabeledBullet)("dataconnect", `generating required schema changes...`);
41
- }
42
40
  await (0, client_1.upsertSchema)(schema, true);
43
41
  if (validationMode === "STRICT") {
44
- (0, utils_1.logLabeledSuccess)("dataconnect", `Database schema is up to date.`);
42
+ (0, utils_1.logLabeledSuccess)("dataconnect", `database schema of ${instanceId}:${databaseId} is up to date.`);
45
43
  }
46
44
  else {
47
- (0, utils_1.logLabeledSuccess)("dataconnect", `Database schema is compatible.`);
45
+ (0, utils_1.logLabeledSuccess)("dataconnect", `database schema of ${instanceId}:${databaseId} is compatible.`);
48
46
  }
49
47
  }
50
48
  catch (err) {
@@ -97,6 +95,7 @@ async function diffSchema(options, schema, schemaValidation) {
97
95
  }
98
96
  exports.diffSchema = diffSchema;
99
97
  async function migrateSchema(args) {
98
+ (0, utils_1.logLabeledBullet)("dataconnect", `generating required schema changes...`);
100
99
  const { options, schema, validateOnly, schemaValidation } = args;
101
100
  const { serviceName, instanceId, instanceName, databaseId } = getIdentifiers(schema);
102
101
  await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, true);
@@ -107,7 +106,7 @@ async function migrateSchema(args) {
107
106
  setSchemaValidationMode(schema, validationMode);
108
107
  try {
109
108
  await (0, client_1.upsertSchema)(schema, validateOnly);
110
- logger_1.logger.debug(`Database schema was up to date for ${instanceId}:${databaseId}`);
109
+ (0, utils_1.logLabeledBullet)("dataconnect", `database schema of ${instanceId}:${databaseId} is up to date.`);
111
110
  }
112
111
  catch (err) {
113
112
  if ((err === null || err === void 0 ? void 0 : err.status) !== 400) {
@@ -355,7 +354,7 @@ async function ensureServiceIsConnectedToCloudSql(serviceName, instanceId, datab
355
354
  (0, utils_1.logLabeledWarning)("dataconnect", `Not yet linked to the Cloud SQL instance.`);
356
355
  return;
357
356
  }
358
- (0, utils_1.logLabeledBullet)("dataconnect", `Linking the Cloud SQL instance...`);
357
+ (0, utils_1.logLabeledBullet)("dataconnect", `linking the Cloud SQL instance...`);
359
358
  currentSchema = {
360
359
  name: `${serviceName}/schemas/${types_1.SCHEMA_ID}`,
361
360
  source: {
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const error_1 = require("../../error");
6
+ const gcs = require("../../gcp/storage");
7
+ const getProjectNumber_1 = require("../../getProjectNumber");
8
+ const projectUtils_1 = require("../../projectUtils");
9
+ const utils_1 = require("../../utils");
10
+ const util_1 = require("./util");
11
+ async function default_1(context, options) {
12
+ if (context.backendConfigs.size === 0) {
13
+ return;
14
+ }
15
+ const projectId = (0, projectUtils_1.needProjectId)(options);
16
+ options.projectNumber = await (0, getProjectNumber_1.getProjectNumber)(options);
17
+ if (!context.backendConfigs) {
18
+ return;
19
+ }
20
+ for (const loc of context.backendLocations.values()) {
21
+ const bucketName = `firebaseapphosting-sources-${options.projectNumber}-${loc.toLowerCase()}`;
22
+ try {
23
+ await gcs.getBucket(bucketName);
24
+ }
25
+ catch (err) {
26
+ const errStatus = (0, error_1.getErrStatus)(err.original);
27
+ if (errStatus === 403 || errStatus === 404) {
28
+ (0, utils_1.logLabeledBullet)("apphosting", `Creating Cloud Storage bucket in ${loc} to store App Hosting source code uploads at ${bucketName}...`);
29
+ try {
30
+ await gcs.createBucket(projectId, {
31
+ name: bucketName,
32
+ location: loc,
33
+ lifecycle: {
34
+ rule: [
35
+ {
36
+ action: {
37
+ type: "Delete",
38
+ },
39
+ condition: {
40
+ age: 30,
41
+ },
42
+ },
43
+ ],
44
+ },
45
+ });
46
+ }
47
+ catch (err) {
48
+ if ((0, error_1.getErrStatus)(err.original) === 403) {
49
+ (0, utils_1.logLabeledWarning)("apphosting", "Failed to create Cloud Storage bucket because user does not have sufficient permissions. " +
50
+ "See https://cloud.google.com/storage/docs/access-control/iam-roles for more details on " +
51
+ "IAM roles that are able to create a Cloud Storage bucket, and ask your project administrator " +
52
+ "to grant you one of those roles.");
53
+ throw err.original;
54
+ }
55
+ }
56
+ }
57
+ else {
58
+ throw err;
59
+ }
60
+ }
61
+ }
62
+ for (const cfg of context.backendConfigs.values()) {
63
+ const { projectSourcePath, zippedSourcePath } = await (0, util_1.createArchive)(cfg, options.projectRoot);
64
+ const backendLocation = context.backendLocations.get(cfg.backendId);
65
+ if (!backendLocation) {
66
+ throw new error_1.FirebaseError(`Failed to find location for backend ${cfg.backendId}. Please contact support with the contents of your firebase-debug.log to report your issue.`);
67
+ }
68
+ (0, utils_1.logLabeledBullet)("apphosting", `Uploading source code at ${projectSourcePath} for backend ${cfg.backendId}...`);
69
+ const { bucket, object } = await gcs.uploadObject({
70
+ file: zippedSourcePath,
71
+ stream: fs.createReadStream(zippedSourcePath),
72
+ }, `firebaseapphosting-sources-${options.projectNumber}-${backendLocation.toLowerCase()}`);
73
+ (0, utils_1.logLabeledBullet)("apphosting", `Source code uploaded at gs://${bucket}/${object}`);
74
+ context.backendStorageUris.set(cfg.backendId, `gs://firebaseapphosting-sources-${options.projectNumber}-${backendLocation.toLowerCase()}/${path.basename(zippedSourcePath)}`);
75
+ }
76
+ }
77
+ exports.default = default_1;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.release = exports.deploy = exports.prepare = void 0;
4
+ const prepare_1 = require("./prepare");
5
+ exports.prepare = prepare_1.default;
6
+ const deploy_1 = require("./deploy");
7
+ exports.deploy = deploy_1.default;
8
+ const release_1 = require("./release");
9
+ exports.release = release_1.default;
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getBackendConfigs = void 0;
4
+ const path = require("path");
5
+ const backend_1 = require("../../apphosting/backend");
6
+ const apphosting_1 = require("../../gcp/apphosting");
7
+ const devConnect_1 = require("../../gcp/devConnect");
8
+ const projectUtils_1 = require("../../projectUtils");
9
+ const prompt_1 = require("../../prompt");
10
+ const utils_1 = require("../../utils");
11
+ async function default_1(context, options) {
12
+ var _a;
13
+ const projectId = (0, projectUtils_1.needProjectId)(options);
14
+ await (0, apphosting_1.ensureApiEnabled)(options);
15
+ await (0, backend_1.ensureRequiredApisEnabled)(projectId);
16
+ try {
17
+ await (0, backend_1.ensureAppHostingComputeServiceAccount)(projectId, "", true);
18
+ }
19
+ catch (err) {
20
+ if (err.status === 400) {
21
+ (0, utils_1.logLabeledWarning)("apphosting", "Your App Hosting compute service account is still being provisioned. Please try again in a few moments.");
22
+ }
23
+ throw err;
24
+ }
25
+ context.backendConfigs = new Map();
26
+ context.backendLocations = new Map();
27
+ context.backendStorageUris = new Map();
28
+ const configs = getBackendConfigs(options);
29
+ const { backends } = await (0, apphosting_1.listBackends)(projectId, "-");
30
+ const foundBackends = [];
31
+ const notFoundBackends = [];
32
+ const ambiguousBackends = [];
33
+ const skippedBackends = [];
34
+ for (const cfg of configs) {
35
+ const filteredBackends = backends.filter((backend) => (0, apphosting_1.parseBackendName)(backend.name).id === cfg.backendId);
36
+ if (filteredBackends.length === 0) {
37
+ notFoundBackends.push(cfg);
38
+ }
39
+ else if (filteredBackends.length === 1) {
40
+ foundBackends.push(cfg);
41
+ }
42
+ else {
43
+ ambiguousBackends.push(cfg);
44
+ }
45
+ }
46
+ for (const cfg of ambiguousBackends) {
47
+ const filteredBackends = backends.filter((backend) => (0, apphosting_1.parseBackendName)(backend.name).id === cfg.backendId);
48
+ const locations = filteredBackends.map((b) => (0, apphosting_1.parseBackendName)(b.name).location);
49
+ (0, utils_1.logLabeledWarning)("apphosting", `You have multiple backends with the same ${cfg.backendId} ID in regions: ${locations.join(", ")}. This is not allowed until we can support more locations. ` +
50
+ "Please delete and recreate any backends that share an ID with another backend.");
51
+ }
52
+ if (foundBackends.length > 0) {
53
+ (0, utils_1.logLabeledBullet)("apphosting", `Found backend(s) ${foundBackends.map((cfg) => cfg.backendId).join(", ")}`);
54
+ }
55
+ for (const cfg of foundBackends) {
56
+ const filteredBackends = backends.filter((backend) => (0, apphosting_1.parseBackendName)(backend.name).id === cfg.backendId);
57
+ if (cfg.alwaysDeployFromSource === false) {
58
+ skippedBackends.push(cfg);
59
+ continue;
60
+ }
61
+ const backend = filteredBackends[0];
62
+ const { location } = (0, apphosting_1.parseBackendName)(backend.name);
63
+ if (cfg.alwaysDeployFromSource === undefined && ((_a = backend.codebase) === null || _a === void 0 ? void 0 : _a.repository)) {
64
+ const { connectionName, id } = (0, devConnect_1.parseGitRepositoryLinkName)(backend.codebase.repository);
65
+ const gitRepositoryLink = await (0, devConnect_1.getGitRepositoryLink)(projectId, location, connectionName, id);
66
+ if (!options.force) {
67
+ const confirmDeploy = await (0, prompt_1.confirm)({
68
+ default: true,
69
+ message: `${cfg.backendId} is linked to the remote repository at ${gitRepositoryLink.cloneUri}. Are you sure you want to deploy your local source?`,
70
+ });
71
+ cfg.alwaysDeployFromSource = confirmDeploy;
72
+ const configPath = path.join(options.projectRoot || "", "firebase.json");
73
+ options.config.writeProjectFile(configPath, options.config.src);
74
+ (0, utils_1.logLabeledBullet)("apphosting", `On future invocations of "firebase deploy", your local source will ${!confirmDeploy ? "not " : ""}be deployed to ${cfg.backendId}. You can edit this setting in your firebase.json at any time.`);
75
+ if (!confirmDeploy) {
76
+ skippedBackends.push(cfg);
77
+ continue;
78
+ }
79
+ }
80
+ }
81
+ context.backendConfigs.set(cfg.backendId, cfg);
82
+ context.backendLocations.set(cfg.backendId, location);
83
+ }
84
+ if (notFoundBackends.length > 0) {
85
+ if (options.force) {
86
+ (0, utils_1.logLabeledWarning)("apphosting", `Skipping deployments of backend(s) ${notFoundBackends.map((cfg) => cfg.backendId).join(", ")}; ` +
87
+ "the backend(s) do not exist yet and we cannot create them for you because you must choose primary regions for each one. " +
88
+ "Please run 'firebase deploy' without the --force flag, or 'firebase apphosting:backends:create' to create the backend, " +
89
+ "then retry deployment.");
90
+ return;
91
+ }
92
+ const confirmCreate = await (0, prompt_1.confirm)({
93
+ default: true,
94
+ message: `Did not find backend(s) ${notFoundBackends.map((cfg) => cfg.backendId).join(", ")}. Do you want to create them (you'll have the option to select which to create in the next step)?`,
95
+ });
96
+ if (confirmCreate) {
97
+ const selected = await (0, prompt_1.checkbox)({
98
+ message: "Which backends do you want to create and deploy to?",
99
+ choices: notFoundBackends.map((cfg) => cfg.backendId),
100
+ });
101
+ const selectedBackends = selected.map((id) => notFoundBackends.find((backend) => backend.backendId === id));
102
+ for (const cfg of selectedBackends) {
103
+ (0, utils_1.logLabeledBullet)("apphosting", `Creating a new backend ${cfg.backendId}...`);
104
+ const { location } = await (0, backend_1.doSetupSourceDeploy)(projectId, cfg.backendId);
105
+ context.backendConfigs.set(cfg.backendId, cfg);
106
+ context.backendLocations.set(cfg.backendId, location);
107
+ }
108
+ }
109
+ else {
110
+ skippedBackends.push(...notFoundBackends);
111
+ }
112
+ }
113
+ if (skippedBackends.length > 0) {
114
+ (0, utils_1.logLabeledWarning)("apphosting", `Skipping deployment of backend(s) ${skippedBackends.map((cfg) => cfg.backendId).join(", ")}.`);
115
+ }
116
+ return;
117
+ }
118
+ exports.default = default_1;
119
+ function getBackendConfigs(options) {
120
+ if (!options.config.src.apphosting) {
121
+ return [];
122
+ }
123
+ const backendConfigs = Array.isArray(options.config.src.apphosting)
124
+ ? options.config.src.apphosting
125
+ : [options.config.src.apphosting];
126
+ if (!options.only) {
127
+ return backendConfigs;
128
+ }
129
+ const selectors = options.only.split(",");
130
+ const backendIds = [];
131
+ for (const selector of selectors) {
132
+ if (selector === "apphosting") {
133
+ return backendConfigs;
134
+ }
135
+ if (selector.startsWith("apphosting:")) {
136
+ const backendId = selector.replace("apphosting:", "");
137
+ if (backendId.length > 0) {
138
+ backendIds.push(backendId);
139
+ }
140
+ }
141
+ }
142
+ if (backendIds.length === 0) {
143
+ return [];
144
+ }
145
+ return backendConfigs.filter((cfg) => backendIds.includes(cfg.backendId));
146
+ }
147
+ exports.getBackendConfigs = getBackendConfigs;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const ora = require("ora");
4
+ const api_1 = require("../../api");
5
+ const backend_1 = require("../../apphosting/backend");
6
+ const rollout_1 = require("../../apphosting/rollout");
7
+ const projectUtils_1 = require("../../projectUtils");
8
+ const utils_1 = require("../../utils");
9
+ async function default_1(context, options) {
10
+ if (context.backendConfigs.size === 0) {
11
+ return;
12
+ }
13
+ const projectId = (0, projectUtils_1.needProjectId)(options);
14
+ const rollouts = [];
15
+ const backendIds = [];
16
+ for (const backendId of context.backendConfigs.keys()) {
17
+ const config = context.backendConfigs.get(backendId);
18
+ const location = context.backendLocations.get(backendId);
19
+ const storageUri = context.backendStorageUris.get(backendId);
20
+ if (!config || !location || !storageUri) {
21
+ (0, utils_1.logLabeledWarning)("apphosting", `Failed to find metadata for backend ${backendId}. Please contact support with the contents of your firebase-debug.log to report your issue.`);
22
+ continue;
23
+ }
24
+ backendIds.push(backendId);
25
+ rollouts.push((0, rollout_1.orchestrateRollout)({
26
+ projectId,
27
+ location,
28
+ backendId,
29
+ buildInput: {
30
+ source: {
31
+ archive: {
32
+ userStorageUri: storageUri,
33
+ rootDirectory: config.rootDir,
34
+ },
35
+ },
36
+ },
37
+ }));
38
+ }
39
+ (0, utils_1.logLabeledBullet)("apphosting", `You may also track the rollout(s) at:\n\t${(0, api_1.consoleOrigin)()}/project/${projectId}/apphosting`);
40
+ const rolloutsSpinner = ora(`Starting rollout(s) for backend(s) ${Array.from(context.backendConfigs.keys()).join(", ")}; this may take a few minutes. It's safe to exit now.\n`).start();
41
+ const results = await Promise.allSettled(rollouts);
42
+ for (let i = 0; i < results.length; i++) {
43
+ const res = results[i];
44
+ if (res.status === "fulfilled") {
45
+ const backend = await (0, backend_1.getBackend)(projectId, backendIds[i]);
46
+ (0, utils_1.logLabeledSuccess)("apphosting", `Rollout for backend ${backendIds[i]} complete!`);
47
+ (0, utils_1.logLabeledSuccess)("apphosting", `Your backend is now deployed at:\n\thttps://${backend.uri}`);
48
+ }
49
+ else {
50
+ (0, utils_1.logLabeledWarning)("apphosting", `Rollout for backend ${backendIds[i]} failed.`);
51
+ (0, utils_1.logLabeledError)("apphosting", res.reason);
52
+ }
53
+ }
54
+ rolloutsSpinner.stop();
55
+ }
56
+ exports.default = default_1;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createArchive = void 0;
4
+ const archiver = require("archiver");
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+ const tmp = require("tmp");
8
+ const error_1 = require("../../error");
9
+ const fsAsync = require("../../fsAsync");
10
+ async function createArchive(config, projectRoot) {
11
+ const tmpFile = tmp.fileSync({ prefix: `${config.backendId}-`, postfix: ".zip" }).name;
12
+ const fileStream = fs.createWriteStream(tmpFile, {
13
+ flags: "w",
14
+ encoding: "binary",
15
+ });
16
+ const archive = archiver("zip");
17
+ if (!projectRoot) {
18
+ projectRoot = process.cwd();
19
+ }
20
+ const ignore = config.ignore || ["node_modules", ".git"];
21
+ ignore.push("firebase-debug.log", "firebase-debug.*.log");
22
+ const gitIgnorePatterns = parseGitIgnorePatterns(projectRoot);
23
+ ignore.push(...gitIgnorePatterns);
24
+ try {
25
+ const files = await fsAsync.readdirRecursive({
26
+ path: projectRoot,
27
+ ignore: ignore,
28
+ isGitIgnore: true,
29
+ });
30
+ for (const file of files) {
31
+ const name = path.relative(projectRoot, file.name);
32
+ archive.file(file.name, {
33
+ name,
34
+ mode: file.mode,
35
+ });
36
+ }
37
+ await pipeAsync(archive, fileStream);
38
+ }
39
+ catch (err) {
40
+ throw new error_1.FirebaseError("Could not read source directory. Remove links and shortcuts and try again.", { original: err, exit: 1 });
41
+ }
42
+ return { projectSourcePath: projectRoot, zippedSourcePath: tmpFile };
43
+ }
44
+ exports.createArchive = createArchive;
45
+ function parseGitIgnorePatterns(projectRoot, gitIgnorePath = ".gitignore") {
46
+ const absoluteFilePath = path.resolve(projectRoot, gitIgnorePath);
47
+ if (!fs.existsSync(absoluteFilePath)) {
48
+ return [];
49
+ }
50
+ const lines = fs
51
+ .readFileSync(absoluteFilePath)
52
+ .toString()
53
+ .split("\n")
54
+ .map((line) => line.trim())
55
+ .filter((line) => !line.startsWith("#") && !(line === ""));
56
+ return lines;
57
+ }
58
+ async function pipeAsync(from, to) {
59
+ from.pipe(to);
60
+ await from.finalize();
61
+ return new Promise((resolve, reject) => {
62
+ to.on("finish", resolve);
63
+ to.on("error", reject);
64
+ });
65
+ }