firebase-tools 12.3.1 → 12.4.1

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 (62) hide show
  1. package/lib/api/frameworks.js +21 -0
  2. package/lib/api.js +4 -3
  3. package/lib/appdistribution/client.js +52 -0
  4. package/lib/auth.js +3 -3
  5. package/lib/command.js +15 -4
  6. package/lib/commands/appdistribution-group-create.js +19 -0
  7. package/lib/commands/appdistribution-group-delete.js +24 -0
  8. package/lib/commands/appdistribution-testers-add.js +6 -1
  9. package/lib/commands/appdistribution-testers-remove.js +20 -13
  10. package/lib/commands/experiments-describe.js +1 -1
  11. package/lib/commands/ext-install.js +10 -4
  12. package/lib/commands/index.js +5 -0
  13. package/lib/commands/init.js +8 -0
  14. package/lib/commands/internaltesting-frameworks-init.js +14 -0
  15. package/lib/config.js +1 -0
  16. package/lib/deploy/extensions/prepare.js +1 -0
  17. package/lib/deploy/extensions/release.js +11 -1
  18. package/lib/deploy/functions/checkIam.js +4 -1
  19. package/lib/deploy/functions/prepare.js +2 -1
  20. package/lib/deploy/functions/runtimes/discovery/index.js +6 -0
  21. package/lib/deploy/functions/runtimes/node/index.js +12 -4
  22. package/lib/deploy/functions/runtimes/python/index.js +19 -25
  23. package/lib/deploy/hosting/deploy.js +0 -6
  24. package/lib/deploy/hosting/prepare.js +7 -1
  25. package/lib/deploy/index.js +11 -4
  26. package/lib/deploy/lifecycleHooks.js +3 -0
  27. package/lib/deploy/storage/prepare.js +1 -1
  28. package/lib/detectProjectRoot.js +4 -1
  29. package/lib/dynamicImport.js +11 -1
  30. package/lib/emulator/commandUtils.js +4 -4
  31. package/lib/emulator/controller.js +9 -7
  32. package/lib/emulator/downloadableEmulators.js +3 -3
  33. package/lib/emulator/functionsEmulator.js +1 -2
  34. package/lib/emulator/storage/index.js +6 -0
  35. package/lib/emulator/storage/rules/manager.js +0 -4
  36. package/lib/emulator/storage/server.js +52 -0
  37. package/lib/ensureApiEnabled.js +3 -1
  38. package/lib/experiments.js +5 -0
  39. package/lib/extensions/paramHelper.js +0 -5
  40. package/lib/frameworks/compose/discover/filesystem.js +52 -0
  41. package/lib/frameworks/compose/discover/frameworkMatcher.js +76 -0
  42. package/lib/frameworks/compose/discover/frameworkSpec.js +39 -0
  43. package/lib/frameworks/compose/discover/types.js +2 -0
  44. package/lib/frameworks/constants.js +2 -15
  45. package/lib/frameworks/index.js +13 -8
  46. package/lib/frameworks/utils.js +50 -20
  47. package/lib/functionsConfig.js +2 -2
  48. package/lib/gcp/cloudbuild.js +50 -0
  49. package/lib/gcp/storage.js +6 -5
  50. package/lib/init/features/composer/repo.js +121 -0
  51. package/lib/init/features/frameworks/constants.js +7 -0
  52. package/lib/init/features/frameworks/index.js +36 -0
  53. package/lib/init/features/index.js +3 -1
  54. package/lib/init/index.js +4 -0
  55. package/lib/management/projects.js +5 -1
  56. package/lib/monospace/index.js +82 -0
  57. package/lib/monospace/interfaces.js +2 -0
  58. package/lib/requireAuth.js +8 -0
  59. package/lib/track.js +91 -52
  60. package/lib/utils.js +6 -1
  61. package/package.json +1 -1
  62. package/schema/extension-yaml.json +432 -0
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createBuild = exports.createStack = exports.API_VERSION = void 0;
4
+ const apiv2_1 = require("../apiv2");
5
+ const api_1 = require("../api");
6
+ exports.API_VERSION = "v1";
7
+ const client = new apiv2_1.Client({
8
+ urlPrefix: api_1.frameworksOrigin,
9
+ auth: true,
10
+ apiVersion: exports.API_VERSION,
11
+ });
12
+ async function createStack(projectId, location, stackId, stack) {
13
+ const res = await client.post(`projects/${projectId}/locations/${location}/stacks`, stack, { queryParams: { stackId } });
14
+ return res.body;
15
+ }
16
+ exports.createStack = createStack;
17
+ async function createBuild(projectId, location, stackId, buildId, build) {
18
+ const res = await client.post(`projects/${projectId}/locations/${location}/stacks/${stackId}/builds`, build, { queryParams: { buildId } });
19
+ return res.body;
20
+ }
21
+ exports.createBuild = createBuild;
package/lib/api.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.secretManagerOrigin = exports.githubApiOrigin = exports.githubOrigin = exports.serviceUsageOrigin = exports.cloudRunApiOrigin = exports.hostingApiOrigin = exports.firebaseStorageOrigin = exports.storageOrigin = exports.runtimeconfigOrigin = exports.rulesOrigin = exports.resourceManagerOrigin = exports.remoteConfigApiOrigin = exports.rtdbMetadataOrigin = exports.rtdbManagementOrigin = exports.realtimeOrigin = exports.extensionsTOSOrigin = exports.extensionsPublisherOrigin = exports.extensionsOrigin = exports.iamOrigin = exports.identityOrigin = exports.hostingOrigin = exports.googleOrigin = exports.pubsubOrigin = exports.cloudTasksOrigin = exports.cloudschedulerOrigin = exports.functionsDefaultRegion = exports.runOrigin = exports.functionsV2Origin = exports.functionsOrigin = exports.firestoreOrigin = exports.firestoreOriginOrEmulator = exports.firedataOrigin = exports.firebaseExtensionsRegistryOrigin = exports.firebaseApiOrigin = exports.eventarcOrigin = exports.dynamicLinksKey = exports.dynamicLinksOrigin = exports.deployOrigin = exports.consoleOrigin = exports.authOrigin = exports.appengineOrigin = exports.appDistributionOrigin = exports.artifactRegistryDomain = exports.containerRegistryDomain = exports.cloudMonitoringOrigin = exports.cloudloggingOrigin = exports.cloudbillingOrigin = exports.clientSecret = exports.clientId = exports.authProxyOrigin = void 0;
4
- exports.setScopes = exports.getScopes = exports.githubClientSecret = exports.githubClientId = void 0;
3
+ exports.githubApiOrigin = exports.githubOrigin = exports.frameworksOrigin = exports.serviceUsageOrigin = exports.cloudRunApiOrigin = exports.hostingApiOrigin = exports.firebaseStorageOrigin = exports.storageOrigin = exports.runtimeconfigOrigin = exports.rulesOrigin = exports.resourceManagerOrigin = exports.remoteConfigApiOrigin = exports.rtdbMetadataOrigin = exports.rtdbManagementOrigin = exports.realtimeOrigin = exports.extensionsTOSOrigin = exports.extensionsPublisherOrigin = exports.extensionsOrigin = exports.iamOrigin = exports.identityOrigin = exports.hostingOrigin = exports.googleOrigin = exports.pubsubOrigin = exports.cloudTasksOrigin = exports.cloudschedulerOrigin = exports.cloudbuildOrigin = exports.functionsDefaultRegion = exports.runOrigin = exports.functionsV2Origin = exports.functionsOrigin = exports.firestoreOrigin = exports.firestoreOriginOrEmulator = exports.firedataOrigin = exports.firebaseExtensionsRegistryOrigin = exports.firebaseApiOrigin = exports.eventarcOrigin = exports.dynamicLinksKey = exports.dynamicLinksOrigin = exports.deployOrigin = exports.consoleOrigin = exports.authOrigin = exports.appDistributionOrigin = exports.artifactRegistryDomain = exports.containerRegistryDomain = exports.cloudMonitoringOrigin = exports.cloudloggingOrigin = exports.cloudbillingOrigin = exports.clientSecret = exports.clientId = exports.authProxyOrigin = void 0;
4
+ exports.setScopes = exports.getScopes = exports.githubClientSecret = exports.githubClientId = exports.secretManagerOrigin = void 0;
5
5
  const constants_1 = require("./emulator/constants");
6
6
  const logger_1 = require("./logger");
7
7
  const scopes = require("./scopes");
@@ -16,7 +16,6 @@ exports.cloudMonitoringOrigin = utils.envOverride("CLOUD_MONITORING_URL", "https
16
16
  exports.containerRegistryDomain = utils.envOverride("CONTAINER_REGISTRY_DOMAIN", "gcr.io");
17
17
  exports.artifactRegistryDomain = utils.envOverride("ARTIFACT_REGISTRY_DOMAIN", "https://artifactregistry.googleapis.com");
18
18
  exports.appDistributionOrigin = utils.envOverride("FIREBASE_APP_DISTRIBUTION_URL", "https://firebaseappdistribution.googleapis.com");
19
- exports.appengineOrigin = utils.envOverride("FIREBASE_APPENGINE_URL", "https://appengine.googleapis.com");
20
19
  exports.authOrigin = utils.envOverride("FIREBASE_AUTH_URL", "https://accounts.google.com");
21
20
  exports.consoleOrigin = utils.envOverride("FIREBASE_CONSOLE_URL", "https://console.firebase.google.com");
22
21
  exports.deployOrigin = utils.envOverride("FIREBASE_DEPLOY_URL", utils.envOverride("FIREBASE_UPLOAD_URL", "https://deploy.firebase.com"));
@@ -37,6 +36,7 @@ exports.functionsOrigin = utils.envOverride("FIREBASE_FUNCTIONS_URL", "https://c
37
36
  exports.functionsV2Origin = utils.envOverride("FIREBASE_FUNCTIONS_V2_URL", "https://cloudfunctions.googleapis.com");
38
37
  exports.runOrigin = utils.envOverride("CLOUD_RUN_URL", "https://run.googleapis.com");
39
38
  exports.functionsDefaultRegion = utils.envOverride("FIREBASE_FUNCTIONS_DEFAULT_REGION", "us-central1");
39
+ exports.cloudbuildOrigin = utils.envOverride("FIREBASE_CLOUDBUILD_URL", "https://cloudbuild.googleapis.com");
40
40
  exports.cloudschedulerOrigin = utils.envOverride("FIREBASE_CLOUDSCHEDULER_URL", "https://cloudscheduler.googleapis.com");
41
41
  exports.cloudTasksOrigin = utils.envOverride("FIREBASE_CLOUD_TAKS_URL", "https://cloudtasks.googleapis.com");
42
42
  exports.pubsubOrigin = utils.envOverride("FIREBASE_PUBSUB_URL", "https://pubsub.googleapis.com");
@@ -59,6 +59,7 @@ exports.firebaseStorageOrigin = utils.envOverride("FIREBASE_FIREBASESTORAGE_URL"
59
59
  exports.hostingApiOrigin = utils.envOverride("FIREBASE_HOSTING_API_URL", "https://firebasehosting.googleapis.com");
60
60
  exports.cloudRunApiOrigin = utils.envOverride("CLOUD_RUN_API_URL", "https://run.googleapis.com");
61
61
  exports.serviceUsageOrigin = utils.envOverride("FIREBASE_SERVICE_USAGE_URL", "https://serviceusage.googleapis.com");
62
+ exports.frameworksOrigin = utils.envOverride("FRAMEWORKS_URL", "https://placeholder.googleapis.com");
62
63
  exports.githubOrigin = utils.envOverride("GITHUB_URL", "https://github.com");
63
64
  exports.githubApiOrigin = utils.envOverride("GITHUB_API_URL", "https://api.github.com");
64
65
  exports.secretManagerOrigin = utils.envOverride("CLOUD_SECRET_MANAGER_URL", "https://secretmanager.googleapis.com");
@@ -137,5 +137,57 @@ class AppDistributionClient {
137
137
  }
138
138
  return apiResponse.body;
139
139
  }
140
+ async createGroup(projectName, displayName, alias) {
141
+ let apiResponse;
142
+ try {
143
+ apiResponse = await this.appDistroV2Client.request({
144
+ method: "POST",
145
+ path: alias === undefined ? `${projectName}/groups` : `${projectName}/groups?groupId=${alias}`,
146
+ body: { displayName: displayName },
147
+ });
148
+ }
149
+ catch (err) {
150
+ throw new error_1.FirebaseError(`Failed to create group ${err}`);
151
+ }
152
+ return apiResponse.body;
153
+ }
154
+ async deleteGroup(groupName) {
155
+ try {
156
+ await this.appDistroV2Client.request({
157
+ method: "DELETE",
158
+ path: groupName,
159
+ });
160
+ }
161
+ catch (err) {
162
+ throw new error_1.FirebaseError(`Failed to delete group ${err}`);
163
+ }
164
+ utils.logSuccess(`Group deleted successfully`);
165
+ }
166
+ async addTestersToGroup(groupName, emails) {
167
+ try {
168
+ await this.appDistroV2Client.request({
169
+ method: "POST",
170
+ path: `${groupName}:batchJoin`,
171
+ body: { emails: emails },
172
+ });
173
+ }
174
+ catch (err) {
175
+ throw new error_1.FirebaseError(`Failed to add testers to group ${err}`);
176
+ }
177
+ utils.logSuccess(`Testers added to group successfully`);
178
+ }
179
+ async removeTestersFromGroup(groupName, emails) {
180
+ try {
181
+ await this.appDistroV2Client.request({
182
+ method: "POST",
183
+ path: `${groupName}:batchLeave`,
184
+ body: { emails: emails },
185
+ });
186
+ }
187
+ catch (err) {
188
+ throw new error_1.FirebaseError(`Failed to remove testers from group ${err}`);
189
+ }
190
+ utils.logSuccess(`Testers removed from group successfully`);
191
+ }
140
192
  }
141
193
  exports.AppDistributionClient = AppDistributionClient;
package/lib/auth.js CHANGED
@@ -307,7 +307,7 @@ async function loginRemotely() {
307
307
  });
308
308
  try {
309
309
  const tokens = await getTokensFromAuthorizationCode(code, `${api_1.authProxyOrigin}/complete`, codeVerifier);
310
- void (0, track_1.track)("login", "google_remote");
310
+ void (0, track_1.trackGA4)("login", { method: "google_remote" });
311
311
  return {
312
312
  user: jwt.decode(tokens.id_token),
313
313
  tokens: tokens,
@@ -323,7 +323,7 @@ async function loginWithLocalhostGoogle(port, userHint) {
323
323
  const authUrl = getLoginUrl(callbackUrl, userHint);
324
324
  const successTemplate = "../templates/loginSuccess.html";
325
325
  const tokens = await loginWithLocalhost(port, callbackUrl, authUrl, successTemplate, getTokensFromAuthorizationCode);
326
- void (0, track_1.track)("login", "google_localhost");
326
+ void (0, track_1.trackGA4)("login", { method: "google_localhost" });
327
327
  return {
328
328
  user: jwt.decode(tokens.id_token),
329
329
  tokens: tokens,
@@ -335,7 +335,7 @@ async function loginWithLocalhostGitHub(port) {
335
335
  const authUrl = getGithubLoginUrl(callbackUrl);
336
336
  const successTemplate = "../templates/loginSuccessGithub.html";
337
337
  const tokens = await loginWithLocalhost(port, callbackUrl, authUrl, successTemplate, getGithubTokensFromAuthorizationCode);
338
- void (0, track_1.track)("login", "google_localhost");
338
+ void (0, track_1.trackGA4)("login", { method: "github_localhost" });
339
339
  return tokens;
340
340
  }
341
341
  async function loginWithLocalhost(port, callbackUrl, authUrl, successTemplate, getTokens) {
package/lib/command.js CHANGED
@@ -89,7 +89,12 @@ class Command {
89
89
  }, null, 2));
90
90
  }
91
91
  const duration = Math.floor((process.uptime() - start) * 1000);
92
- const trackSuccess = (0, track_1.track)(this.name, "success", duration);
92
+ const trackSuccess = (0, track_1.trackGA4)("command_execution", {
93
+ command_name: this.name,
94
+ result: "success",
95
+ duration,
96
+ interactive: (0, utils_1.getInheritedOption)(options, "nonInteractive") ? "false" : "true",
97
+ });
93
98
  if (!isEmulator) {
94
99
  await (0, utils_1.withTimeout)(5000, trackSuccess);
95
100
  }
@@ -113,8 +118,11 @@ class Command {
113
118
  }
114
119
  const duration = Math.floor((process.uptime() - start) * 1000);
115
120
  await (0, utils_1.withTimeout)(5000, Promise.all([
116
- (0, track_1.track)(this.name, "error", duration),
117
- (0, track_1.track)(err.exit === 1 ? "Error (User)" : "Error (Unexpected)", "", duration),
121
+ (0, track_1.trackGA4)("command_execution", {
122
+ command_name: this.name,
123
+ result: "error",
124
+ interactive: (0, utils_1.getInheritedOption)(options, "nonInteractive") ? "false" : "true",
125
+ }, duration),
118
126
  isEmulator
119
127
  ? (0, track_1.trackEmulator)("command_error", {
120
128
  command_name: this.name,
@@ -223,7 +231,10 @@ function validateProjectId(project) {
223
231
  if (PROJECT_ID_REGEX.test(project)) {
224
232
  return;
225
233
  }
226
- (0, track_1.track)("Project ID Check", "invalid");
234
+ (0, track_1.trackGA4)("error", {
235
+ error_type: "Error (User)",
236
+ details: "Invalid project ID",
237
+ });
227
238
  const invalidMessage = "Invalid project id: " + clc.bold(project) + ".";
228
239
  if (project.toLowerCase() !== project) {
229
240
  throw new error_1.FirebaseError(invalidMessage + "\nNote: Project id must be all lowercase.");
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.command = void 0;
4
+ const command_1 = require("../command");
5
+ const utils = require("../utils");
6
+ const requireAuth_1 = require("../requireAuth");
7
+ const client_1 = require("../appdistribution/client");
8
+ const options_parser_util_1 = require("../appdistribution/options-parser-util");
9
+ exports.command = new command_1.Command("appdistribution:group:create <displayName> [alias]")
10
+ .description("create group in project")
11
+ .before(requireAuth_1.requireAuth)
12
+ .action(async (displayName, alias, options) => {
13
+ const projectName = await (0, options_parser_util_1.getProjectName)(options);
14
+ const appDistroClient = new client_1.AppDistributionClient();
15
+ utils.logBullet(`Creating group in project`);
16
+ const group = await appDistroClient.createGroup(projectName, displayName, alias);
17
+ alias = group.name.split("/").pop();
18
+ utils.logSuccess(`Group '${group.displayName}' (alias: ${alias}) created successfully`);
19
+ });
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.command = void 0;
4
+ const command_1 = require("../command");
5
+ const utils = require("../utils");
6
+ const requireAuth_1 = require("../requireAuth");
7
+ const error_1 = require("../error");
8
+ const client_1 = require("../appdistribution/client");
9
+ const options_parser_util_1 = require("../appdistribution/options-parser-util");
10
+ exports.command = new command_1.Command("appdistribution:group:delete <alias>")
11
+ .description("delete group from a project")
12
+ .before(requireAuth_1.requireAuth)
13
+ .action(async (alias, options) => {
14
+ const projectName = await (0, options_parser_util_1.getProjectName)(options);
15
+ const appDistroClient = new client_1.AppDistributionClient();
16
+ try {
17
+ utils.logBullet(`Deleting group from project`);
18
+ await appDistroClient.deleteGroup(`${projectName}/groups/${alias}`);
19
+ }
20
+ catch (err) {
21
+ throw new error_1.FirebaseError(`Failed to delete group ${err}`);
22
+ }
23
+ utils.logSuccess(`Group ${alias} has successfully been deleted`);
24
+ });
@@ -7,8 +7,9 @@ const requireAuth_1 = require("../requireAuth");
7
7
  const client_1 = require("../appdistribution/client");
8
8
  const options_parser_util_1 = require("../appdistribution/options-parser-util");
9
9
  exports.command = new command_1.Command("appdistribution:testers:add [emails...]")
10
- .description("add testers to project")
10
+ .description("add testers to project (and possibly group)")
11
11
  .option("--file <file>", "a path to a file containing a list of tester emails to be added")
12
+ .option("--group-alias <group-alias>", "if specified, the testers are also added to the group identified by this alias")
12
13
  .before(requireAuth_1.requireAuth)
13
14
  .action(async (emails, options) => {
14
15
  const projectName = await (0, options_parser_util_1.getProjectName)(options);
@@ -16,4 +17,8 @@ exports.command = new command_1.Command("appdistribution:testers:add [emails...]
16
17
  const emailsToAdd = (0, options_parser_util_1.getEmails)(emails, options.file);
17
18
  utils.logBullet(`Adding ${emailsToAdd.length} testers to project`);
18
19
  await appDistroClient.addTesters(projectName, emailsToAdd);
20
+ if (options.groupAlias) {
21
+ utils.logBullet(`Adding ${emailsToAdd.length} testers to group`);
22
+ await appDistroClient.addTestersToGroup(`${projectName}/groups/${options.groupAlias}`, emailsToAdd);
23
+ }
19
24
  });
@@ -9,25 +9,32 @@ const client_1 = require("../appdistribution/client");
9
9
  const options_parser_util_1 = require("../appdistribution/options-parser-util");
10
10
  const logger_1 = require("../logger");
11
11
  exports.command = new command_1.Command("appdistribution:testers:remove [emails...]")
12
- .description("remove testers from a project")
12
+ .description("remove testers from a project (or group)")
13
13
  .option("--file <file>", "a path to a file containing a list of tester emails to be removed")
14
+ .option("--group-alias <group-alias>", "if specified, the testers are only removed from the group identified by this alias, but not the project")
14
15
  .before(requireAuth_1.requireAuth)
15
16
  .action(async (emails, options) => {
16
17
  const projectName = await (0, options_parser_util_1.getProjectName)(options);
17
18
  const appDistroClient = new client_1.AppDistributionClient();
18
19
  const emailsArr = (0, options_parser_util_1.getEmails)(emails, options.file);
19
- let deleteResponse;
20
- try {
21
- utils.logBullet(`Deleting ${emailsArr.length} testers from project`);
22
- deleteResponse = await appDistroClient.removeTesters(projectName, emailsArr);
20
+ if (options.groupAlias) {
21
+ utils.logBullet(`Removing ${emailsArr.length} testers from group`);
22
+ await appDistroClient.removeTestersFromGroup(`${projectName}/groups/${options.groupAlias}`, emailsArr);
23
23
  }
24
- catch (err) {
25
- throw new error_1.FirebaseError(`Failed to remove testers ${err}`);
24
+ else {
25
+ let deleteResponse;
26
+ try {
27
+ utils.logBullet(`Deleting ${emailsArr.length} testers from project`);
28
+ deleteResponse = await appDistroClient.removeTesters(projectName, emailsArr);
29
+ }
30
+ catch (err) {
31
+ throw new error_1.FirebaseError(`Failed to remove testers ${err}`);
32
+ }
33
+ if (!deleteResponse.emails) {
34
+ utils.logSuccess(`Testers did not exist`);
35
+ return;
36
+ }
37
+ logger_1.logger.debug(`Testers: ${deleteResponse.emails}, have been successfully deleted`);
38
+ utils.logSuccess(`${deleteResponse.emails.length} testers have successfully been deleted`);
26
39
  }
27
- if (!deleteResponse.emails) {
28
- utils.logSuccess(`Testers did not exist`);
29
- return;
30
- }
31
- logger_1.logger.debug(`Testers: ${deleteResponse.emails}, have been successfully deleted`);
32
- utils.logSuccess(`${deleteResponse.emails.length} testers have successfully been deleted`);
33
40
  });
@@ -8,7 +8,7 @@ const experiments = require("../experiments");
8
8
  const logger_1 = require("../logger");
9
9
  const utils_1 = require("../utils");
10
10
  exports.command = new command_1.Command("experiments:describe <experiment>")
11
- .description("enable an experiment on this machine")
11
+ .description("describe what an experiment does when enabled")
12
12
  .action((experiment) => {
13
13
  if (!experiments.isValidExperiment(experiment)) {
14
14
  let message = `Cannot find experiment ${(0, colorette_1.bold)(experiment)}`;
@@ -35,7 +35,7 @@ exports.command = new command_1.Command("ext:install [extensionName]")
35
35
  .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
36
36
  .before(extensionsHelper_1.diagnoseAndFixProject)
37
37
  .action(async (extensionName, options) => {
38
- var _a;
38
+ var _a, _b;
39
39
  const projectId = (0, projectUtils_1.getProjectId)(options);
40
40
  const paramsEnvPath = "";
41
41
  let learnMore = false;
@@ -61,12 +61,18 @@ exports.command = new command_1.Command("ext:install [extensionName]")
61
61
  if ((0, extensionsHelper_1.isLocalPath)(extensionName)) {
62
62
  source = await (0, extensionsHelper_1.createSourceFromLocation)((0, projectUtils_1.needProjectId)({ projectId }), extensionName);
63
63
  await (0, displayExtensionInfo_1.displayExtInfo)(extensionName, "", source.spec);
64
- void (0, track_1.track)("Extension Install", "Install by Source", options.interactive ? 1 : 0);
64
+ void (0, track_1.trackGA4)("extension_added_to_manifest", {
65
+ published: "local",
66
+ interactive: options.nonInteractive ? "false" : "true",
67
+ });
65
68
  }
66
69
  else {
67
- void (0, track_1.track)("Extension Install", "Install by Extension Ref", options.interactive ? 1 : 0);
68
70
  extensionName = await (0, extensionsHelper_1.canonicalizeRefInput)(extensionName);
69
71
  extensionVersion = await extensionsApi.getExtensionVersion(extensionName);
72
+ void (0, track_1.trackGA4)("extension_added_to_manifest", {
73
+ published: ((_a = extensionVersion.listing) === null || _a === void 0 ? void 0 : _a.state) === "APPROVED" ? "published" : "uploaded",
74
+ interactive: options.nonInteractive ? "false" : "true",
75
+ });
70
76
  await infoExtensionVersion({
71
77
  extensionName,
72
78
  extensionVersion,
@@ -82,7 +88,7 @@ exports.command = new command_1.Command("ext:install [extensionName]")
82
88
  if (!source && !extensionVersion) {
83
89
  throw new error_1.FirebaseError("Could not find a source. Please specify a valid source to continue.");
84
90
  }
85
- const spec = (_a = source === null || source === void 0 ? void 0 : source.spec) !== null && _a !== void 0 ? _a : extensionVersion === null || extensionVersion === void 0 ? void 0 : extensionVersion.spec;
91
+ const spec = (_b = source === null || source === void 0 ? void 0 : source.spec) !== null && _b !== void 0 ? _b : extensionVersion === null || extensionVersion === void 0 ? void 0 : extensionVersion.spec;
86
92
  if (!spec) {
87
93
  throw new error_1.FirebaseError(`Could not find the extension.yaml for extension '${clc.bold(extensionName)}'. Please make sure this is a valid extension and try again.`);
88
94
  }
@@ -19,6 +19,9 @@ function load(client) {
19
19
  client.appdistribution.testers = {};
20
20
  client.appdistribution.testers.add = loadCommand("appdistribution-testers-add");
21
21
  client.appdistribution.testers.delete = loadCommand("appdistribution-testers-remove");
22
+ client.appdistribution.group = {};
23
+ client.appdistribution.group.create = loadCommand("appdistribution-group-create");
24
+ client.appdistribution.group.delete = loadCommand("appdistribution-group-delete");
22
25
  client.apps = {};
23
26
  client.apps.create = loadCommand("apps-create");
24
27
  client.apps.list = loadCommand("apps-list");
@@ -141,6 +144,8 @@ function load(client) {
141
144
  client.internaltesting.frameworks.compose = loadCommand("internaltesting-frameworks-compose");
142
145
  client.internaltesting.functions = {};
143
146
  client.internaltesting.functions.discover = loadCommand("internaltesting-functions-discover");
147
+ client.internaltesting.frameworks = {};
148
+ client.internaltesting.frameworks.init = loadCommand("internaltesting-frameworks-init");
144
149
  }
145
150
  client.login = loadCommand("login");
146
151
  client.login.add = loadCommand("login-add");
@@ -14,6 +14,7 @@ const prompt_1 = require("../prompt");
14
14
  const requireAuth_1 = require("../requireAuth");
15
15
  const fsutils = require("../fsutils");
16
16
  const utils = require("../utils");
17
+ const experiments_1 = require("../experiments");
17
18
  const homeDir = os.homedir();
18
19
  const TEMPLATE_ROOT = path.resolve(__dirname, "../../templates/");
19
20
  const BANNER_TEXT = fs.readFileSync(path.join(TEMPLATE_ROOT, "banner.txt"), "utf8");
@@ -68,6 +69,13 @@ const choices = [
68
69
  checked: false,
69
70
  },
70
71
  ];
72
+ if ((0, experiments_1.isEnabled)("frameworks")) {
73
+ choices.push({
74
+ value: "frameworks",
75
+ name: "Frameworks: Get started with Frameworks projects.",
76
+ checked: false,
77
+ });
78
+ }
71
79
  const featureNames = choices.map((choice) => choice.value);
72
80
  const DESCRIPTION = `Interactively configure the current directory as a Firebase project or initialize new features in an already configured Firebase project directory.
73
81
 
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.command = void 0;
4
+ const command_1 = require("../command");
5
+ const repo_1 = require("../init/features/composer/repo");
6
+ const projectUtils_1 = require("../projectUtils");
7
+ const requireInteractive_1 = require("../requireInteractive");
8
+ exports.command = new command_1.Command("internaltesting:frameworks:init")
9
+ .description("connect github repo to cloud build")
10
+ .before(requireInteractive_1.default)
11
+ .action(async (options) => {
12
+ const projectId = (0, projectUtils_1.needProjectId)(options);
13
+ await (0, repo_1.linkGitHubRepository)(projectId, "us-central2", "stack0");
14
+ });
package/lib/config.js CHANGED
@@ -208,6 +208,7 @@ class Config {
208
208
  }
209
209
  throw new error_1.FirebaseError("Not in a Firebase app directory (could not locate firebase.json)", {
210
210
  exit: 1,
211
+ status: 404,
211
212
  });
212
213
  }
213
214
  }
@@ -18,6 +18,7 @@ const v2FunctionHelper_1 = require("./v2FunctionHelper");
18
18
  const tos_1 = require("../../extensions/tos");
19
19
  async function prepare(context, options, payload) {
20
20
  var _a, _b;
21
+ context.extensionsStartTime = Date.now();
21
22
  const projectId = (0, projectUtils_1.needProjectId)(options);
22
23
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
23
24
  const aliases = (0, projectUtils_1.getAliases)(options, projectId);
@@ -8,8 +8,9 @@ const error_1 = require("../../error");
8
8
  const errors_1 = require("./errors");
9
9
  const projectUtils_1 = require("../../projectUtils");
10
10
  const etags_1 = require("../../extensions/etags");
11
+ const track_1 = require("../../track");
11
12
  async function release(context, options, payload) {
12
- var _a, _b, _c, _d;
13
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
13
14
  const projectId = (0, projectUtils_1.needProjectId)(options);
14
15
  const errorHandler = new errors_1.ErrorHandler();
15
16
  const deploymentQueue = new queue_1.default({
@@ -37,6 +38,15 @@ async function release(context, options, payload) {
37
38
  deploymentQueue.process();
38
39
  deploymentQueue.close();
39
40
  await deploymentPromise;
41
+ const duration = context.extensionsStartTime ? Date.now() - context.extensionsStartTime : 1;
42
+ await (0, track_1.trackGA4)("extensions_deploy", {
43
+ extension_instance_created: (_f = (_e = payload.instancesToCreate) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0,
44
+ extension_instance_updated: (_h = (_g = payload.instancesToUpdate) === null || _g === void 0 ? void 0 : _g.length) !== null && _h !== void 0 ? _h : 0,
45
+ extension_instance_configured: (_k = (_j = payload.instancesToConfigure) === null || _j === void 0 ? void 0 : _j.length) !== null && _k !== void 0 ? _k : 0,
46
+ extension_instance_deleted: (_m = (_l = payload.instancesToDelete) === null || _l === void 0 ? void 0 : _l.length) !== null && _m !== void 0 ? _m : 0,
47
+ errors: (_o = errorHandler.errors.length) !== null && _o !== void 0 ? _o : 0,
48
+ interactive: options.nonInteractive ? "false" : "true",
49
+ }, duration);
40
50
  const newHave = await planner.have(projectId);
41
51
  (0, etags_1.saveEtags)(options.rc, projectId, newHave);
42
52
  if (errorHandler.hasErrors()) {
@@ -59,7 +59,10 @@ async function checkHttpIam(context, options, payload) {
59
59
  return;
60
60
  }
61
61
  if (!passed) {
62
- void (0, track_1.track)("Error (User)", "deploy:functions:http_create_missing_iam");
62
+ void (0, track_1.trackGA4)("error", {
63
+ error_type: "Error (User)",
64
+ details: "deploy:functions:http_create_missing_iam",
65
+ });
63
66
  throw new error_1.FirebaseError(`Missing required permission on project ${(0, colorette_1.bold)(context.projectId)} to deploy new HTTPS functions. The permission ${(0, colorette_1.bold)(PERMISSION)} is required to deploy the following functions:\n\n- ` +
64
67
  newHttpsEndpoints.map((func) => func.id).join("\n- ") +
65
68
  `\n\nTo address this error, please ask a project Owner to assign your account the "Cloud Functions Admin" role at the following URL:\n\nhttps://console.cloud.google.com/iam-admin/iam?project=${context.projectId}`);
@@ -301,7 +301,8 @@ async function loadCodebases(config, options, firebaseConfig, runtimeConfig, fil
301
301
  logger_1.logger.debug(`Building ${runtimeDelegate.name} source`);
302
302
  await runtimeDelegate.build();
303
303
  const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId);
304
- wantBuilds[codebase] = await runtimeDelegate.discoverBuild(runtimeConfig, firebaseEnvs);
304
+ (0, utils_1.logLabeledBullet)("functions", `Loading and anaylzing source code for codebase ${codebase} to determine what to deploy`);
305
+ wantBuilds[codebase] = await runtimeDelegate.discoverBuild(runtimeConfig, Object.assign(Object.assign({}, firebaseEnvs), { GOOGLE_CLOUD_QUOTA_PROJECT: projectId }));
305
306
  }
306
307
  return wantBuilds;
307
308
  }
@@ -64,6 +64,12 @@ async function detectFromPort(port, project, runtime, timeout = 10000) {
64
64
  throw err;
65
65
  }
66
66
  }
67
+ if (res.status !== 200) {
68
+ const text = await res.text();
69
+ logger_1.logger.debug(`Got response code ${res.status}; body ${text}`);
70
+ throw new error_1.FirebaseError("Functions codebase could not be analyzed successfully. " +
71
+ "It may have a syntax or runtime error");
72
+ }
67
73
  const text = await res.text();
68
74
  logger_1.logger.debug("Got response from /__/functions.yaml", text);
69
75
  let parsed;
@@ -92,7 +92,7 @@ class Delegate {
92
92
  return Promise.resolve(() => Promise.resolve());
93
93
  }
94
94
  serveAdmin(port, config, envs) {
95
- var _a;
95
+ var _a, _b;
96
96
  const env = Object.assign(Object.assign({}, envs), { PORT: port, FUNCTIONS_CONTROL_API: "true", HOME: process.env.HOME, PATH: process.env.PATH, NODE_ENV: process.env.NODE_ENV, __FIREBASE_FRAMEWORKS_ENTRY__: process.env.__FIREBASE_FRAMEWORKS_ENTRY__ });
97
97
  if (Object.keys(config || {}).length) {
98
98
  env.CLOUD_RUNTIME_CONFIG = JSON.stringify(config);
@@ -114,17 +114,25 @@ class Delegate {
114
114
  const childProcess = spawn(binPath, [this.sourceDir], {
115
115
  env,
116
116
  cwd: this.sourceDir,
117
- stdio: ["ignore", "pipe", "inherit"],
117
+ stdio: ["ignore", "pipe", "pipe"],
118
118
  });
119
119
  (_a = childProcess.stdout) === null || _a === void 0 ? void 0 : _a.on("data", (chunk) => {
120
- logger_1.logger.debug(chunk.toString());
120
+ logger_1.logger.info(chunk.toString("utf8"));
121
+ });
122
+ (_b = childProcess.stderr) === null || _b === void 0 ? void 0 : _b.on("data", (chunk) => {
123
+ logger_1.logger.error(chunk.toString("utf8"));
121
124
  });
122
125
  return Promise.resolve(async () => {
123
126
  const p = new Promise((resolve, reject) => {
124
127
  childProcess.once("exit", resolve);
125
128
  childProcess.once("error", reject);
126
129
  });
127
- await (0, node_fetch_1.default)(`http://localhost:${port}/__/quitquitquit`);
130
+ try {
131
+ await (0, node_fetch_1.default)(`http://localhost:${port}/__/quitquitquit`);
132
+ }
133
+ catch (e) {
134
+ logger_1.logger.debug("Failed to call quitquitquit. This often means the server failed to start", e);
135
+ }
128
136
  setTimeout(() => {
129
137
  if (!childProcess.killed) {
130
138
  childProcess.kill("SIGKILL");
@@ -11,7 +11,6 @@ const discovery = require("../discovery");
11
11
  const logger_1 = require("../../../../logger");
12
12
  const python_1 = require("../../../../functions/python");
13
13
  const error_1 = require("../../../../error");
14
- const utils_1 = require("../../../../utils");
15
14
  exports.LATEST_VERSION = "python311";
16
15
  async function tryCreateDelegate(context) {
17
16
  const requirementsTextPath = path.join(context.sourceDir, "requirements.txt");
@@ -108,32 +107,31 @@ class Delegate {
108
107
  const modulesDir = await this.modulesDir();
109
108
  const envWithAdminPort = Object.assign(Object.assign({}, envs), { ADMIN_PORT: port.toString() });
110
109
  const args = [this.bin, `"${path.join(modulesDir, "private", "serving.py")}"`];
111
- const stdout = [];
112
- const stderr = [];
113
110
  logger_1.logger.debug(`Running admin server with args: ${JSON.stringify(args)} and env: ${JSON.stringify(envWithAdminPort)} in ${this.sourceDir}`);
114
111
  const childProcess = (0, python_1.runWithVirtualEnv)(args, this.sourceDir, envWithAdminPort);
115
112
  (_a = childProcess.stdout) === null || _a === void 0 ? void 0 : _a.on("data", (chunk) => {
116
- const chunkString = chunk.toString();
117
- stdout.push(chunkString);
118
- logger_1.logger.debug(`stdout: ${chunkString}`);
113
+ logger_1.logger.info(chunk.toString("utf8"));
119
114
  });
120
115
  (_b = childProcess.stderr) === null || _b === void 0 ? void 0 : _b.on("data", (chunk) => {
121
- const chunkString = chunk.toString();
122
- stderr.push(chunkString);
123
- logger_1.logger.debug(`stderr: ${chunkString}`);
116
+ logger_1.logger.error(chunk.toString("utf8"));
124
117
  });
125
- return Promise.resolve({
126
- stderr,
127
- stdout,
128
- killProcess: async () => {
118
+ return Promise.resolve(async () => {
119
+ try {
129
120
  await (0, node_fetch_1.default)(`http://127.0.0.1:${port}/__/quitquitquit`);
130
- const quitTimeout = setTimeout(() => {
131
- if (!childProcess.killed) {
132
- childProcess.kill("SIGKILL");
133
- }
134
- }, 10000);
135
- clearTimeout(quitTimeout);
136
- },
121
+ }
122
+ catch (e) {
123
+ logger_1.logger.debug("Failed to call quitquitquit. This often means the server failed to start", e);
124
+ }
125
+ const quitTimeout = setTimeout(() => {
126
+ if (!childProcess.killed) {
127
+ childProcess.kill("SIGKILL");
128
+ }
129
+ }, 10000);
130
+ clearTimeout(quitTimeout);
131
+ return new Promise((resolve, reject) => {
132
+ childProcess.once("exit", resolve);
133
+ childProcess.once("error", reject);
134
+ });
137
135
  });
138
136
  }
139
137
  async discoverBuild(_configValues, envs) {
@@ -142,14 +140,10 @@ class Delegate {
142
140
  const adminPort = await portfinder.getPortPromise({
143
141
  port: 8081,
144
142
  });
145
- const { killProcess, stderr } = await this.serveAdmin(adminPort, envs);
143
+ const killProcess = await this.serveAdmin(adminPort, envs);
146
144
  try {
147
145
  discovered = await discovery.detectFromPort(adminPort, this.projectId, this.runtime);
148
146
  }
149
- catch (e) {
150
- (0, utils_1.logLabeledWarning)("functions", `Failed to detect functions from source ${e}.\nstderr:${stderr.join("\n")}`);
151
- throw e;
152
- }
153
147
  finally {
154
148
  await killProcess();
155
149
  }
@@ -5,7 +5,6 @@ const uploader_1 = require("./uploader");
5
5
  const detectProjectRoot_1 = require("../../detectProjectRoot");
6
6
  const listFiles_1 = require("../../listFiles");
7
7
  const logger_1 = require("../../logger");
8
- const track_1 = require("../../track");
9
8
  const utils_1 = require("../../utils");
10
9
  const colorette_1 = require("colorette");
11
10
  const ora = require("ora");
@@ -67,10 +66,6 @@ async function deploy(context, options) {
67
66
  try {
68
67
  await uploader.start();
69
68
  }
70
- catch (err) {
71
- void (0, track_1.track)("Hosting Deploy", "failure");
72
- throw err;
73
- }
74
69
  finally {
75
70
  clearInterval(progressInterval);
76
71
  updateSpinner(uploader.statusMessage(), debugging);
@@ -81,7 +76,6 @@ async function deploy(context, options) {
81
76
  (0, utils_1.logLabeledSuccess)(`hosting[${deploy.config.site}]`, "file upload complete");
82
77
  const dt = Date.now() - t0;
83
78
  logger_1.logger.debug(`[hosting] deploy completed after ${dt}ms`);
84
- void (0, track_1.track)("Hosting Deploy", "success", dt);
85
79
  return runDeploys(deploys, debugging);
86
80
  }
87
81
  const debugging = !!(options.debug || options.nonInteractive);