firebase-tools 14.3.0 → 14.4.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 (146) hide show
  1. package/lib/api.js +10 -2
  2. package/lib/apphosting/backend.js +72 -18
  3. package/lib/apphosting/githubConnections.js +13 -19
  4. package/lib/apphosting/rollout.js +2 -2
  5. package/lib/apphosting/secrets/dialogs.js +4 -4
  6. package/lib/apphosting/secrets/index.js +2 -2
  7. package/lib/auth.js +22 -2
  8. package/lib/bin/cli.js +1 -33
  9. package/lib/bin/mcp.js +12 -2
  10. package/lib/checkValidTargetFilters.js +1 -0
  11. package/lib/command.js +3 -2
  12. package/lib/commands/apphosting-secrets-grantaccess.js +1 -1
  13. package/lib/commands/deploy.js +1 -0
  14. package/lib/commands/init.js +1 -1
  15. package/lib/commands/login-use.js +2 -11
  16. package/lib/commands/use.js +9 -8
  17. package/lib/config.js +1 -0
  18. package/lib/crashlytics/listTopIssues.js +44 -0
  19. package/lib/dataconnect/cloudAICompanionClient.js +67 -0
  20. package/lib/dataconnect/dataplaneClient.js +16 -1
  21. package/lib/dataconnect/types.js +5 -1
  22. package/lib/deploy/apphosting/args.js +2 -0
  23. package/lib/deploy/apphosting/deploy.js +74 -0
  24. package/lib/deploy/apphosting/index.js +9 -0
  25. package/lib/deploy/apphosting/prepare.js +141 -0
  26. package/lib/deploy/apphosting/release.js +53 -0
  27. package/lib/deploy/apphosting/util.js +65 -0
  28. package/lib/deploy/extensions/v2FunctionHelper.js +2 -1
  29. package/lib/deploy/functions/checkIam.js +3 -3
  30. package/lib/deploy/functions/ensure.js +2 -1
  31. package/lib/deploy/functions/prepare.js +23 -16
  32. package/lib/deploy/functions/release/fabricator.js +4 -4
  33. package/lib/deploy/functions/release/index.js +1 -1
  34. package/lib/deploy/functions/runtimes/python/index.js +3 -0
  35. package/lib/deploy/functions/runtimes/supported/types.js +17 -11
  36. package/lib/deploy/index.js +2 -0
  37. package/lib/emulator/apphosting/index.js +1 -0
  38. package/lib/emulator/apphosting/serve.js +77 -3
  39. package/lib/emulator/auth/widget_ui.js +2 -1
  40. package/lib/emulator/controller.js +18 -4
  41. package/lib/emulator/dataconnectEmulator.js +9 -2
  42. package/lib/emulator/downloadableEmulatorInfo.json +60 -0
  43. package/lib/emulator/downloadableEmulators.js +25 -61
  44. package/lib/ensureApiEnabled.js +11 -1
  45. package/lib/experiments.js +1 -1
  46. package/lib/extensions/manifest.js +2 -2
  47. package/lib/fsAsync.js +9 -2
  48. package/lib/gcp/auth.js +32 -2
  49. package/lib/gcp/cloudbilling.js +12 -1
  50. package/lib/gcp/cloudfunctions.js +1 -2
  51. package/lib/gcp/cloudfunctionsv2.js +1 -2
  52. package/lib/gcp/cloudscheduler.js +2 -2
  53. package/lib/gcp/computeEngine.js +19 -2
  54. package/lib/gcp/devConnect.js +6 -1
  55. package/lib/gcp/firestore.js +24 -1
  56. package/lib/gcp/iam.js +1 -5
  57. package/lib/gcp/storage.js +43 -1
  58. package/lib/index.js +1 -2
  59. package/lib/init/features/apphosting.js +84 -6
  60. package/lib/init/features/database.js +64 -45
  61. package/lib/init/features/dataconnect/index.js +51 -53
  62. package/lib/init/features/emulators.js +9 -5
  63. package/lib/init/features/firestore/index.js +54 -23
  64. package/lib/init/features/firestore/indexes.js +23 -23
  65. package/lib/init/features/firestore/rules.js +35 -40
  66. package/lib/init/features/functions/index.js +2 -0
  67. package/lib/init/features/functions/javascript.js +3 -2
  68. package/lib/init/features/functions/typescript.js +3 -2
  69. package/lib/init/features/genkit/index.js +2 -1
  70. package/lib/init/features/hosting/github.js +3 -2
  71. package/lib/init/features/index.js +8 -4
  72. package/lib/init/features/remoteconfig.js +3 -2
  73. package/lib/init/index.js +76 -24
  74. package/lib/logger.js +71 -7
  75. package/lib/management/projects.js +25 -2
  76. package/lib/mcp/errors.js +1 -1
  77. package/lib/mcp/index.js +134 -51
  78. package/lib/mcp/tools/auth/{disable_auth_user.js → disable_user.js} +3 -3
  79. package/lib/mcp/tools/auth/get_user.js +38 -0
  80. package/lib/mcp/tools/auth/index.js +8 -6
  81. package/lib/mcp/tools/auth/list_users.js +47 -0
  82. package/lib/mcp/tools/auth/set_claims.js +43 -0
  83. package/lib/mcp/tools/auth/set_sms_region_policy.js +1 -1
  84. package/lib/mcp/tools/core/{consult_firebase_assistant.js → consult_assistant.js} +4 -4
  85. package/lib/mcp/tools/core/create_android_sha.js +40 -0
  86. package/lib/mcp/tools/core/create_app.js +90 -0
  87. package/lib/mcp/tools/core/create_project.js +68 -0
  88. package/lib/mcp/tools/core/get_admin_sdk_config.js +26 -0
  89. package/lib/mcp/tools/core/get_environment.js +51 -0
  90. package/lib/mcp/tools/{project → core}/get_sdk_config.js +6 -3
  91. package/lib/mcp/tools/core/index.js +20 -6
  92. package/lib/mcp/tools/core/init.js +129 -0
  93. package/lib/mcp/tools/core/update_environment.js +55 -0
  94. package/lib/mcp/tools/crashlytics/index.js +5 -0
  95. package/lib/mcp/tools/crashlytics/list_top_issues.js +34 -0
  96. package/lib/mcp/tools/dataconnect/converter.js +30 -1
  97. package/lib/mcp/tools/dataconnect/emulator.js +32 -0
  98. package/lib/mcp/tools/dataconnect/execute_graphql.js +48 -0
  99. package/lib/mcp/tools/dataconnect/execute_graphql_read.js +48 -0
  100. package/lib/mcp/tools/dataconnect/execute_mutation.js +62 -0
  101. package/lib/mcp/tools/dataconnect/execute_query.js +62 -0
  102. package/lib/mcp/tools/dataconnect/{generate_dataconnect_operation.js → generate_operation.js} +9 -9
  103. package/lib/mcp/tools/dataconnect/{generate_dataconnect_schema.js → generate_schema.js} +4 -4
  104. package/lib/mcp/tools/dataconnect/{get_dataconnect_connector.js → get_connector.js} +9 -9
  105. package/lib/mcp/tools/dataconnect/{get_dataconnect_schema.js → get_schema.js} +10 -10
  106. package/lib/mcp/tools/dataconnect/index.js +14 -10
  107. package/lib/mcp/tools/dataconnect/{list_dataconnect_services.js → list_services.js} +5 -5
  108. package/lib/mcp/tools/firestore/converter.js +47 -1
  109. package/lib/mcp/tools/firestore/delete_document.js +37 -0
  110. package/lib/mcp/tools/firestore/{get_firestore_documents.js → get_documents.js} +3 -3
  111. package/lib/mcp/tools/firestore/index.js +12 -6
  112. package/lib/mcp/tools/firestore/{list_firestore_collections.js → list_collections.js} +4 -9
  113. package/lib/mcp/tools/firestore/query_collection.js +116 -0
  114. package/lib/mcp/tools/index.js +44 -8
  115. package/lib/mcp/tools/messaging/index.js +5 -0
  116. package/lib/mcp/tools/messaging/send_message.js +42 -0
  117. package/lib/mcp/tools/remoteconfig/get_template.js +27 -0
  118. package/lib/mcp/tools/remoteconfig/index.js +7 -0
  119. package/lib/mcp/tools/remoteconfig/publish_template.js +34 -0
  120. package/lib/mcp/tools/remoteconfig/rollback_template.js +29 -0
  121. package/lib/mcp/tools/rules/get_rules.js +29 -0
  122. package/lib/mcp/tools/rules/validate_rules.js +98 -0
  123. package/lib/mcp/tools/storage/get_download_url.js +34 -0
  124. package/lib/mcp/tools/storage/{get_storage_rules.js → get_rules.js} +6 -6
  125. package/lib/mcp/tools/storage/index.js +8 -2
  126. package/lib/mcp/types.js +9 -1
  127. package/lib/mcp/util.js +29 -2
  128. package/lib/messaging/interfaces.js +2 -0
  129. package/lib/messaging/sendMessage.js +48 -0
  130. package/lib/remoteconfig/publish.js +39 -0
  131. package/lib/requireAuth.js +2 -2
  132. package/lib/utils.js +2 -37
  133. package/package.json +2 -1
  134. package/schema/firebase-config.json +62 -10
  135. package/templates/init/functions/javascript/package.lint.json +1 -1
  136. package/templates/init/functions/javascript/package.nolint.json +1 -1
  137. package/templates/init/functions/typescript/package.lint.json +1 -1
  138. package/templates/init/functions/typescript/package.nolint.json +2 -2
  139. package/lib/mcp/tools/auth/get_auth_user.js +0 -29
  140. package/lib/mcp/tools/auth/set_auth_claims.js +0 -34
  141. package/lib/mcp/tools/core/get_firebase_directory.js +0 -20
  142. package/lib/mcp/tools/core/set_firebase_directory.js +0 -33
  143. package/lib/mcp/tools/firestore/get_firestore_rules.js +0 -26
  144. package/lib/mcp/tools/project/index.js +0 -7
  145. /package/lib/mcp/tools/{project → core}/get_project.js +0 -0
  146. /package/lib/mcp/tools/{project → core}/list_apps.js +0 -0
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.command = void 0;
3
+ exports.command = exports.setNewActive = void 0;
4
4
  const clc = require("colorette");
5
5
  const command_1 = require("../command");
6
6
  const projects_1 = require("../management/projects");
@@ -30,10 +30,10 @@ function listAliases(options) {
30
30
  function verifyMessage(name) {
31
31
  return "please verify project " + clc.bold(name) + " exists and you have access.";
32
32
  }
33
- async function setNewActive(projectOrAlias, aliasOpt, options) {
33
+ async function setNewActive(projectOrAlias, aliasOpt, rc, projectRoot) {
34
34
  let project;
35
- const hasAlias = options.rc.hasProjectAlias(projectOrAlias);
36
- const resolvedProject = options.rc.resolveAlias(projectOrAlias);
35
+ const hasAlias = rc.hasProjectAlias(projectOrAlias);
36
+ const resolvedProject = rc.resolveAlias(projectOrAlias);
37
37
  (0, command_2.validateProjectId)(resolvedProject);
38
38
  try {
39
39
  project = await (0, projects_1.getProject)(resolvedProject);
@@ -45,24 +45,25 @@ async function setNewActive(projectOrAlias, aliasOpt, options) {
45
45
  if (!project) {
46
46
  throw new error_1.FirebaseError(`Cannot create alias ${clc.bold(aliasOpt)}, ${verifyMessage(projectOrAlias)}`);
47
47
  }
48
- options.rc.addProjectAlias(aliasOpt, projectOrAlias);
48
+ rc.addProjectAlias(aliasOpt, projectOrAlias);
49
49
  logger_1.logger.info("Created alias", clc.bold(aliasOpt), "for", resolvedProject + ".");
50
50
  }
51
51
  if (hasAlias) {
52
52
  if (!project) {
53
53
  throw new error_1.FirebaseError(`Unable to use alias ${clc.bold(projectOrAlias)}, ${verifyMessage(resolvedProject)}`);
54
54
  }
55
- utils.makeActiveProject(options.projectRoot, projectOrAlias);
55
+ utils.makeActiveProject(projectRoot, projectOrAlias);
56
56
  logger_1.logger.info("Now using alias", clc.bold(projectOrAlias), "(" + resolvedProject + ")");
57
57
  }
58
58
  else if (project) {
59
- utils.makeActiveProject(options.projectRoot, projectOrAlias);
59
+ utils.makeActiveProject(projectRoot, projectOrAlias);
60
60
  logger_1.logger.info("Now using project", clc.bold(projectOrAlias));
61
61
  }
62
62
  else {
63
63
  throw new error_1.FirebaseError(`Invalid project selection, ${verifyMessage(projectOrAlias)}`);
64
64
  }
65
65
  }
66
+ exports.setNewActive = setNewActive;
66
67
  function unalias(alias, options) {
67
68
  if (options.rc.hasProjectAlias(alias)) {
68
69
  options.rc.removeProjectAlias(alias);
@@ -150,7 +151,7 @@ exports.command = new command_1.Command("use [alias_or_project_id]")
150
151
  " to start a project directory in the current folder.");
151
152
  }
152
153
  if (newActive) {
153
- return setNewActive(newActive, aliasOpt, options);
154
+ return setNewActive(newActive, aliasOpt, options.rc, options.projectRoot);
154
155
  }
155
156
  if (options.unalias) {
156
157
  return unalias(options.unalias, options);
package/lib/config.js CHANGED
@@ -239,4 +239,5 @@ Config.MATERIALIZE_TARGETS = [
239
239
  "storage",
240
240
  "remoteconfig",
241
241
  "dataconnect",
242
+ "apphosting",
242
243
  ];
@@ -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,67 @@
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
+ const res = await client.post(`${instance}:completeTask`, request);
25
+ return res;
26
+ }
27
+ exports.callCloudAICompanion = callCloudAICompanion;
28
+ function buildRequest({ servicePath, naturalLanguageQuery, chatHistory }, type) {
29
+ const { serviceId } = getServiceParts(servicePath);
30
+ const input = {
31
+ messages: [
32
+ ...chatHistory,
33
+ {
34
+ author: USER_AUTHOR,
35
+ content: naturalLanguageQuery,
36
+ },
37
+ ],
38
+ };
39
+ const clientContext = {
40
+ name: CLIENT_CONTEXT_NAME_IDENTIFIER,
41
+ additionalContext: {
42
+ "@type": FIREBASE_CHAT_REQUEST_CONTEXT_TYPE_NAME,
43
+ fdcInfo: {
44
+ serviceId,
45
+ fdcServiceName: servicePath,
46
+ requiresQuery: true,
47
+ },
48
+ },
49
+ };
50
+ return {
51
+ input,
52
+ clientContext,
53
+ experienceContext: {
54
+ experience: type === "schema" ? FDC_SCHEMA_EXPERIENCE_CONTEXT : FDC_OPERATION_EXPERIENCE_CONTEXT,
55
+ },
56
+ };
57
+ }
58
+ function toChatResourceName(projectId) {
59
+ return `projects/${projectId}/locations/global/instances/default`;
60
+ }
61
+ function getServiceParts(name) {
62
+ const match = name.match(/projects\/([^/]*)\/locations\/([^/]*)\/services\/([^/]*)/);
63
+ if (!match) {
64
+ throw new Error(`Invalid service name: ${name}`);
65
+ }
66
+ return { projectId: match[1], locationId: match[2], serviceId: match[3] };
67
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.executeGraphQL = exports.dataconnectDataplaneClient = exports.DATACONNECT_API_VERSION = exports.dataconnectOrigin = void 0;
3
+ exports.executeGraphQLMutation = exports.executeGraphQLQuery = exports.executeGraphQLRead = exports.executeGraphQL = exports.dataconnectDataplaneClient = exports.DATACONNECT_API_VERSION = exports.dataconnectOrigin = void 0;
4
4
  const api_1 = require("../api");
5
5
  var api_2 = require("../api");
6
6
  Object.defineProperty(exports, "dataconnectOrigin", { enumerable: true, get: function () { return api_2.dataconnectOrigin; } });
@@ -19,3 +19,18 @@ async function executeGraphQL(client, servicePath, body) {
19
19
  return res;
20
20
  }
21
21
  exports.executeGraphQL = executeGraphQL;
22
+ async function executeGraphQLRead(client, servicePath, body) {
23
+ const res = await client.post(`${servicePath}:executeGraphqlRead`, body, { resolveOnHTTPError: true });
24
+ return res;
25
+ }
26
+ exports.executeGraphQLRead = executeGraphQLRead;
27
+ async function executeGraphQLQuery(client, connectorPath, body) {
28
+ const res = await client.post(`${connectorPath}:executeQuery`, body, { resolveOnHTTPError: true });
29
+ return res;
30
+ }
31
+ exports.executeGraphQLQuery = executeGraphQLQuery;
32
+ async function executeGraphQLMutation(client, connectorPath, body) {
33
+ const res = await client.post(`${connectorPath}:executeMutation`, body, { resolveOnHTTPError: true });
34
+ return res;
35
+ }
36
+ exports.executeGraphQLMutation = executeGraphQLMutation;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toDatasource = exports.Platform = exports.requiresVector = exports.SCHEMA_ID = void 0;
3
+ exports.isGraphQLResponseError = exports.isGraphQLResponse = exports.toDatasource = exports.Platform = exports.requiresVector = exports.SCHEMA_ID = void 0;
4
4
  exports.SCHEMA_ID = "main";
5
5
  function requiresVector(dm) {
6
6
  var _a, _b, _c, _d;
@@ -31,3 +31,7 @@ function toDatasource(projectId, locationId, ds) {
31
31
  return {};
32
32
  }
33
33
  exports.toDatasource = toDatasource;
34
+ const isGraphQLResponse = (g) => !!g.data || !!g.errors;
35
+ exports.isGraphQLResponse = isGraphQLResponse;
36
+ const isGraphQLResponseError = (g) => !!g.error;
37
+ exports.isGraphQLResponseError = isGraphQLResponseError;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,74 @@
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
+ const projectId = (0, projectUtils_1.needProjectId)(options);
13
+ options.projectNumber = await (0, getProjectNumber_1.getProjectNumber)(options);
14
+ if (!context.backendConfigs) {
15
+ return;
16
+ }
17
+ for (const loc of context.backendLocations.values()) {
18
+ const bucketName = `firebaseapphosting-sources-${options.projectNumber}-${loc.toLowerCase()}`;
19
+ try {
20
+ await gcs.getBucket(bucketName);
21
+ }
22
+ catch (err) {
23
+ const errStatus = (0, error_1.getErrStatus)(err.original);
24
+ if (errStatus === 403 || errStatus === 404) {
25
+ (0, utils_1.logLabeledBullet)("apphosting", `Creating Cloud Storage bucket in ${loc} to store App Hosting source code uploads at ${bucketName}...`);
26
+ try {
27
+ await gcs.createBucket(projectId, {
28
+ name: bucketName,
29
+ location: loc,
30
+ lifecycle: {
31
+ rule: [
32
+ {
33
+ action: {
34
+ type: "Delete",
35
+ },
36
+ condition: {
37
+ age: 30,
38
+ },
39
+ },
40
+ ],
41
+ },
42
+ });
43
+ }
44
+ catch (err) {
45
+ if ((0, error_1.getErrStatus)(err.original) === 403) {
46
+ (0, utils_1.logLabeledWarning)("apphosting", "Failed to create Cloud Storage bucket because user does not have sufficient permissions. " +
47
+ "See https://cloud.google.com/storage/docs/access-control/iam-roles for more details on " +
48
+ "IAM roles that are able to create a Cloud Storage bucket, and ask your project administrator " +
49
+ "to grant you one of those roles.");
50
+ throw err.original;
51
+ }
52
+ }
53
+ }
54
+ else {
55
+ throw err;
56
+ }
57
+ }
58
+ }
59
+ for (const cfg of context.backendConfigs.values()) {
60
+ const { projectSourcePath, zippedSourcePath } = await (0, util_1.createArchive)(cfg, options.projectRoot);
61
+ const backendLocation = context.backendLocations.get(cfg.backendId);
62
+ if (!backendLocation) {
63
+ 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.`);
64
+ }
65
+ (0, utils_1.logLabeledBullet)("apphosting", `Uploading source code at ${projectSourcePath} for backend ${cfg.backendId}...`);
66
+ const { bucket, object } = await gcs.uploadObject({
67
+ file: zippedSourcePath,
68
+ stream: fs.createReadStream(zippedSourcePath),
69
+ }, `firebaseapphosting-sources-${options.projectNumber}-${backendLocation.toLowerCase()}`);
70
+ (0, utils_1.logLabeledBullet)("apphosting", `Source code uploaded at gs://${bucket}/${object}`);
71
+ context.backendStorageUris.set(cfg.backendId, `gs://firebaseapphosting-sources-${options.projectNumber}-${backendLocation.toLowerCase()}/${path.basename(zippedSourcePath)}`);
72
+ }
73
+ }
74
+ 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,141 @@
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
+ for (const cfg of configs) {
34
+ const filteredBackends = backends.filter((backend) => (0, apphosting_1.parseBackendName)(backend.name).id === cfg.backendId);
35
+ if (filteredBackends.length === 0) {
36
+ notFoundBackends.push(cfg);
37
+ }
38
+ else if (filteredBackends.length === 1) {
39
+ foundBackends.push(cfg);
40
+ }
41
+ else {
42
+ ambiguousBackends.push(cfg);
43
+ }
44
+ }
45
+ for (const cfg of ambiguousBackends) {
46
+ const filteredBackends = backends.filter((backend) => (0, apphosting_1.parseBackendName)(backend.name).id === cfg.backendId);
47
+ const locations = filteredBackends.map((b) => (0, apphosting_1.parseBackendName)(b.name).location);
48
+ (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. ` +
49
+ "Please delete and recreate any backends that share an ID with another backend.");
50
+ }
51
+ if (foundBackends.length > 0) {
52
+ (0, utils_1.logLabeledBullet)("apphosting", `Found backend(s) ${foundBackends.map((cfg) => cfg.backendId).join(", ")}`);
53
+ }
54
+ for (const cfg of foundBackends) {
55
+ const filteredBackends = backends.filter((backend) => (0, apphosting_1.parseBackendName)(backend.name).id === cfg.backendId);
56
+ if (cfg.alwaysDeployFromSource === false) {
57
+ continue;
58
+ }
59
+ const backend = filteredBackends[0];
60
+ const { location } = (0, apphosting_1.parseBackendName)(backend.name);
61
+ if (cfg.alwaysDeployFromSource === undefined && ((_a = backend.codebase) === null || _a === void 0 ? void 0 : _a.repository)) {
62
+ const { connectionName, id } = (0, devConnect_1.parseGitRepositoryLinkName)(backend.codebase.repository);
63
+ const gitRepositoryLink = await (0, devConnect_1.getGitRepositoryLink)(projectId, location, connectionName, id);
64
+ if (!options.force) {
65
+ const confirmDeploy = await (0, prompt_1.confirm)({
66
+ default: true,
67
+ message: `${cfg.backendId} is linked to the remote repository at ${gitRepositoryLink.cloneUri}. Are you sure you want to deploy your local source?`,
68
+ });
69
+ cfg.alwaysDeployFromSource = confirmDeploy;
70
+ const configPath = path.join(options.projectRoot || "", "firebase.json");
71
+ options.config.writeProjectFile(configPath, options.config.src);
72
+ (0, utils_1.logLabeledBullet)("apphosting", `Your deployment preferences have been saved to firebase.json. On future invocations of "firebase deploy", your local source will be deployed to ${cfg.backendId}. You can edit this setting in your firebase.json at any time.`);
73
+ if (!confirmDeploy) {
74
+ (0, utils_1.logLabeledWarning)("apphosting", `Skipping deployment of backend ${cfg.backendId}`);
75
+ continue;
76
+ }
77
+ }
78
+ }
79
+ context.backendConfigs.set(cfg.backendId, cfg);
80
+ context.backendLocations.set(cfg.backendId, location);
81
+ }
82
+ if (notFoundBackends.length === 0) {
83
+ return;
84
+ }
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
+ return;
98
+ }
99
+ const selected = await (0, prompt_1.checkbox)({
100
+ message: "Which backends do you want to create and deploy to?",
101
+ choices: notFoundBackends.map((cfg) => cfg.backendId),
102
+ });
103
+ const selectedBackends = selected.map((id) => notFoundBackends.find((backend) => backend.backendId === id));
104
+ for (const cfg of selectedBackends) {
105
+ (0, utils_1.logLabeledBullet)("apphosting", `Creating a new backend ${cfg.backendId}...`);
106
+ const { location } = await (0, backend_1.doSetupSourceDeploy)(projectId, cfg.backendId);
107
+ context.backendConfigs.set(cfg.backendId, cfg);
108
+ context.backendLocations.set(cfg.backendId, location);
109
+ }
110
+ return;
111
+ }
112
+ exports.default = default_1;
113
+ function getBackendConfigs(options) {
114
+ if (!options.config.src.apphosting) {
115
+ return [];
116
+ }
117
+ const backendConfigs = Array.isArray(options.config.src.apphosting)
118
+ ? options.config.src.apphosting
119
+ : [options.config.src.apphosting];
120
+ if (!options.only) {
121
+ return backendConfigs;
122
+ }
123
+ const selectors = options.only.split(",");
124
+ const backendIds = [];
125
+ for (const selector of selectors) {
126
+ if (selector === "apphosting") {
127
+ return backendConfigs;
128
+ }
129
+ if (selector.startsWith("apphosting:")) {
130
+ const backendId = selector.replace("apphosting:", "");
131
+ if (backendId.length > 0) {
132
+ backendIds.push(backendId);
133
+ }
134
+ }
135
+ }
136
+ if (backendIds.length === 0) {
137
+ return [];
138
+ }
139
+ return backendConfigs.filter((cfg) => backendIds.includes(cfg.backendId));
140
+ }
141
+ exports.getBackendConfigs = getBackendConfigs;
@@ -0,0 +1,53 @@
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
+ const projectId = (0, projectUtils_1.needProjectId)(options);
11
+ const rollouts = [];
12
+ const backendIds = [];
13
+ for (const backendId of context.backendConfigs.keys()) {
14
+ const config = context.backendConfigs.get(backendId);
15
+ const location = context.backendLocations.get(backendId);
16
+ const storageUri = context.backendStorageUris.get(backendId);
17
+ if (!config || !location || !storageUri) {
18
+ (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.`);
19
+ continue;
20
+ }
21
+ backendIds.push(backendId);
22
+ rollouts.push((0, rollout_1.orchestrateRollout)({
23
+ projectId,
24
+ location,
25
+ backendId,
26
+ buildInput: {
27
+ source: {
28
+ archive: {
29
+ userStorageUri: storageUri,
30
+ rootDirectory: config.rootDir,
31
+ },
32
+ },
33
+ },
34
+ }));
35
+ }
36
+ (0, utils_1.logLabeledBullet)("apphosting", `You may also track the rollout(s) at:\n\t${(0, api_1.consoleOrigin)()}/project/${projectId}/apphosting`);
37
+ 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();
38
+ const results = await Promise.allSettled(rollouts);
39
+ for (let i = 0; i < results.length; i++) {
40
+ const res = results[i];
41
+ if (res.status === "fulfilled") {
42
+ const backend = await (0, backend_1.getBackend)(projectId, backendIds[i]);
43
+ (0, utils_1.logLabeledSuccess)("apphosting", `Rollout for backend ${backendIds[i]} complete!`);
44
+ (0, utils_1.logLabeledSuccess)("apphosting", `Your backend is now deployed at:\n\thttps://${backend.uri}`);
45
+ }
46
+ else {
47
+ (0, utils_1.logLabeledWarning)("apphosting", `Rollout for backend ${backendIds[i]} failed.`);
48
+ (0, utils_1.logLabeledError)("apphosting", res.reason);
49
+ }
50
+ }
51
+ rolloutsSpinner.stop();
52
+ }
53
+ 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
+ }
@@ -9,6 +9,7 @@ const ensureApiEnabled_1 = require("../../ensureApiEnabled");
9
9
  const planner = require("./planner");
10
10
  const projectUtils_1 = require("../../projectUtils");
11
11
  const api_1 = require("../../api");
12
+ const computeEngine_1 = require("../../gcp/computeEngine");
12
13
  const SERVICE_AGENT_ROLE = "roles/eventarc.eventReceiver";
13
14
  async function checkSpecForV2Functions(i) {
14
15
  const extensionSpec = await planner.getExtensionSpec(i);
@@ -23,7 +24,7 @@ async function ensureNecessaryV2ApisAndRoles(options) {
23
24
  exports.ensureNecessaryV2ApisAndRoles = ensureNecessaryV2ApisAndRoles;
24
25
  async function ensureComputeP4SARole(projectId) {
25
26
  const projectNumber = await (0, getProjectNumber_1.getProjectNumber)({ projectId });
26
- const saEmail = `${projectNumber}-compute@developer.gserviceaccount.com`;
27
+ const saEmail = await (0, computeEngine_1.getDefaultServiceAccount)(projectNumber);
27
28
  let policy;
28
29
  try {
29
30
  policy = await resourceManager.getIamPolicy(projectId);
@@ -89,8 +89,8 @@ function obtainPubSubServiceAgentBindings(projectNumber) {
89
89
  return [serviceAccountTokenCreatorBinding];
90
90
  }
91
91
  exports.obtainPubSubServiceAgentBindings = obtainPubSubServiceAgentBindings;
92
- function obtainDefaultComputeServiceAgentBindings(projectNumber) {
93
- const defaultComputeServiceAgent = `serviceAccount:${gce.getDefaultServiceAccount(projectNumber)}`;
92
+ async function obtainDefaultComputeServiceAgentBindings(projectNumber) {
93
+ const defaultComputeServiceAgent = `serviceAccount:${await gce.getDefaultServiceAccount(projectNumber)}`;
94
94
  const runInvokerBinding = {
95
95
  role: exports.RUN_INVOKER_ROLE,
96
96
  members: [defaultComputeServiceAgent],
@@ -117,7 +117,7 @@ async function ensureServiceAgentRoles(projectId, projectNumber, want, have, dry
117
117
  const requiredBindings = [...(0, functional_1.flattenArray)(nestedRequiredBindings)];
118
118
  if (haveServices.length === 0) {
119
119
  requiredBindings.push(...obtainPubSubServiceAgentBindings(projectNumber));
120
- requiredBindings.push(...obtainDefaultComputeServiceAgentBindings(projectNumber));
120
+ requiredBindings.push(...(await obtainDefaultComputeServiceAgentBindings(projectNumber)));
121
121
  }
122
122
  if (requiredBindings.length === 0) {
123
123
  return;
@@ -10,6 +10,7 @@ const projects_1 = require("../../management/projects");
10
10
  const functional_1 = require("../../functional");
11
11
  const api_1 = require("../../api");
12
12
  const backend = require("./backend");
13
+ const computeEngine_1 = require("../../gcp/computeEngine");
13
14
  const FAQ_URL = "https://firebase.google.com/support/faq#functions-runtime";
14
15
  const metadataCallCache = new Map();
15
16
  async function defaultServiceAccount(e) {
@@ -23,7 +24,7 @@ async function defaultServiceAccount(e) {
23
24
  return `${metadata.projectId}@appspot.gserviceaccount.com`;
24
25
  }
25
26
  else if (e.platform === "gcfv2") {
26
- return `${metadata.projectNumber}-compute@developer.gserviceaccount.com`;
27
+ return await (0, computeEngine_1.getDefaultServiceAccount)(metadata.projectNumber);
27
28
  }
28
29
  (0, functional_1.assertExhaustive)(e.platform);
29
30
  }