firebase-tools 12.3.0 → 12.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 (34) hide show
  1. package/lib/api.js +2 -3
  2. package/lib/appdistribution/client.js +52 -0
  3. package/lib/commands/appdistribution-group-create.js +19 -0
  4. package/lib/commands/appdistribution-group-delete.js +24 -0
  5. package/lib/commands/appdistribution-testers-add.js +6 -1
  6. package/lib/commands/appdistribution-testers-remove.js +20 -13
  7. package/lib/commands/experiments-describe.js +1 -1
  8. package/lib/commands/functions-secrets-access.js +2 -0
  9. package/lib/commands/functions-secrets-destroy.js +1 -0
  10. package/lib/commands/functions-secrets-get.js +2 -0
  11. package/lib/commands/functions-secrets-prune.js +3 -2
  12. package/lib/commands/functions-secrets-set.js +8 -0
  13. package/lib/commands/index.js +5 -0
  14. package/lib/commands/internaltesting-frameworks-compose.js +20 -0
  15. package/lib/deploy/functions/release/executor.js +19 -10
  16. package/lib/deploy/functions/release/fabricator.js +22 -8
  17. package/lib/deploy/storage/prepare.js +1 -1
  18. package/lib/frameworks/compose/discover/filesystem.js +52 -0
  19. package/lib/frameworks/compose/discover/frameworkMatcher.js +76 -0
  20. package/lib/frameworks/compose/discover/frameworkSpec.js +39 -0
  21. package/lib/frameworks/compose/discover/index.js +23 -0
  22. package/lib/frameworks/compose/discover/types.js +2 -0
  23. package/lib/frameworks/compose/driver/docker.js +177 -0
  24. package/lib/frameworks/compose/driver/hooks.js +22 -0
  25. package/lib/frameworks/compose/driver/index.js +16 -0
  26. package/lib/frameworks/compose/driver/local.js +42 -0
  27. package/lib/frameworks/compose/index.js +30 -0
  28. package/lib/frameworks/compose/interfaces.js +21 -0
  29. package/lib/functions/secrets.js +8 -1
  30. package/lib/gcp/storage.js +6 -5
  31. package/lib/monospace/index.js +82 -0
  32. package/lib/monospace/interfaces.js +2 -0
  33. package/lib/requireAuth.js +8 -0
  34. package/package.json +1 -1
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.githubClientId = 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.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 = 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"));
@@ -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;
@@ -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)}`;
@@ -5,8 +5,10 @@ const command_1 = require("../command");
5
5
  const logger_1 = require("../logger");
6
6
  const projectUtils_1 = require("../projectUtils");
7
7
  const secretManager_1 = require("../gcp/secretManager");
8
+ const secrets = require("../functions/secrets");
8
9
  exports.command = new command_1.Command("functions:secrets:access <KEY>[@version]")
9
10
  .description("Access secret value given secret and its version. Defaults to accessing the latest version.")
11
+ .before(secrets.ensureApi)
10
12
  .action(async (key, options) => {
11
13
  const projectId = (0, projectUtils_1.needProjectId)(options);
12
14
  let [name, version] = key.split("@");
@@ -11,6 +11,7 @@ const backend = require("../deploy/functions/backend");
11
11
  exports.command = new command_1.Command("functions:secrets:destroy <KEY>[@version]")
12
12
  .description("Destroy a secret. Defaults to destroying the latest version.")
13
13
  .withForce("Destroys a secret without confirmation.")
14
+ .before(secrets.ensureApi)
14
15
  .action(async (key, options) => {
15
16
  const projectId = (0, projectUtils_1.needProjectId)(options);
16
17
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
@@ -7,8 +7,10 @@ const logger_1 = require("../logger");
7
7
  const projectUtils_1 = require("../projectUtils");
8
8
  const secretManager_1 = require("../gcp/secretManager");
9
9
  const requirePermissions_1 = require("../requirePermissions");
10
+ const secrets = require("../functions/secrets");
10
11
  exports.command = new command_1.Command("functions:secrets:get <KEY>")
11
12
  .description("Get metadata for secret and its versions")
13
+ .before(secrets.ensureApi)
12
14
  .before(requirePermissions_1.requirePermissions, ["secretmanager.secrets.get"])
13
15
  .action(async (key, options) => {
14
16
  const projectId = (0, projectUtils_1.needProjectId)(options);
@@ -4,7 +4,7 @@ exports.command = void 0;
4
4
  const backend = require("../deploy/functions/backend");
5
5
  const command_1 = require("../command");
6
6
  const projectUtils_1 = require("../projectUtils");
7
- const secrets_1 = require("../functions/secrets");
7
+ const secrets = require("../functions/secrets");
8
8
  const requirePermissions_1 = require("../requirePermissions");
9
9
  const deploymentTool_1 = require("../deploymentTool");
10
10
  const utils_1 = require("../utils");
@@ -13,6 +13,7 @@ const secretManager_1 = require("../gcp/secretManager");
13
13
  exports.command = new command_1.Command("functions:secrets:prune")
14
14
  .withForce("Destroys unused secrets without prompt")
15
15
  .description("Destroys unused secrets")
16
+ .before(secrets.ensureApi)
16
17
  .before(requirePermissions_1.requirePermissions, [
17
18
  "cloudfunctions.functions.list",
18
19
  "secretmanager.secrets.list",
@@ -27,7 +28,7 @@ exports.command = new command_1.Command("functions:secrets:prune")
27
28
  const haveEndpoints = backend
28
29
  .allEndpoints(haveBackend)
29
30
  .filter((e) => (0, deploymentTool_1.isFirebaseManaged)(e.labels || []));
30
- const pruned = await (0, secrets_1.pruneSecrets)({ projectNumber, projectId }, haveEndpoints);
31
+ const pruned = await secrets.pruneSecrets({ projectNumber, projectId }, haveEndpoints);
31
32
  if (pruned.length === 0) {
32
33
  (0, utils_1.logBullet)("All secrets are in use. Nothing to prune today.");
33
34
  return;
@@ -4,6 +4,7 @@ exports.command = void 0;
4
4
  const tty = require("tty");
5
5
  const fs = require("fs");
6
6
  const clc = require("colorette");
7
+ const logger_1 = require("../logger");
7
8
  const secrets_1 = require("../functions/secrets");
8
9
  const command_1 = require("../command");
9
10
  const requirePermissions_1 = require("../requirePermissions");
@@ -13,9 +14,11 @@ const projectUtils_1 = require("../projectUtils");
13
14
  const secretManager_1 = require("../gcp/secretManager");
14
15
  const secrets = require("../functions/secrets");
15
16
  const backend = require("../deploy/functions/backend");
17
+ const ensureApiEnabled_1 = require("../ensureApiEnabled");
16
18
  exports.command = new command_1.Command("functions:secrets:set <KEY>")
17
19
  .description("Create or update a secret for use in Cloud Functions for Firebase.")
18
20
  .withForce("Automatically updates functions to use the new secret.")
21
+ .before(secrets.ensureApi)
19
22
  .before(requirePermissions_1.requirePermissions, [
20
23
  "secretmanager.secrets.create",
21
24
  "secretmanager.secrets.get",
@@ -50,6 +53,11 @@ exports.command = new command_1.Command("functions:secrets:set <KEY>")
50
53
  clc.bold("firebase deploy --only functions"));
51
54
  return;
52
55
  }
56
+ const functionsEnabled = await (0, ensureApiEnabled_1.check)(projectId, "cloudfunctions.googleapis.com", "functions", true);
57
+ if (!functionsEnabled) {
58
+ logger_1.logger.debug("Customer set secrets before enabling functions. Exiting");
59
+ return;
60
+ }
53
61
  const haveBackend = await backend.existingBackend({ projectId });
54
62
  const endpointsToUpdate = backend
55
63
  .allEndpoints(haveBackend)
@@ -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");
@@ -137,6 +140,8 @@ function load(client) {
137
140
  client.init = loadCommand("init");
138
141
  if (experiments.isEnabled("internaltesting")) {
139
142
  client.internaltesting = {};
143
+ client.internaltesting.frameworks = {};
144
+ client.internaltesting.frameworks.compose = loadCommand("internaltesting-frameworks-compose");
140
145
  client.internaltesting.functions = {};
141
146
  client.internaltesting.functions.discover = loadCommand("internaltesting-functions-discover");
142
147
  }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.command = void 0;
4
+ const command_1 = require("../command");
5
+ const logger_1 = require("../logger");
6
+ const driver_1 = require("../frameworks/compose/driver");
7
+ const compose_1 = require("../frameworks/compose");
8
+ const error_1 = require("../error");
9
+ exports.command = new command_1.Command("internaltesting:frameworks:compose")
10
+ .option("-m, --mode <mode>", "Composer mode (local or docker)", "local")
11
+ .description("compose framework in current directory")
12
+ .action((options) => {
13
+ const mode = options.mode;
14
+ if (!driver_1.SUPPORTED_MODES.includes(mode)) {
15
+ throw new error_1.FirebaseError(`Unsupported mode ${mode}. Supported modes are [${driver_1.SUPPORTED_MODES.join(", ")}]`);
16
+ }
17
+ const bundle = (0, compose_1.compose)(mode);
18
+ logger_1.logger.info(JSON.stringify(bundle, null, 2));
19
+ return {};
20
+ });
@@ -1,21 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.InlineExecutor = exports.QueueExecutor = void 0;
3
+ exports.InlineExecutor = exports.QueueExecutor = exports.DEFAULT_RETRY_CODES = void 0;
4
4
  const queue_1 = require("../../../throttler/queue");
5
- async function handler(op) {
5
+ exports.DEFAULT_RETRY_CODES = [429, 409, 503];
6
+ function parseErrorCode(err) {
6
7
  var _a, _b, _c, _d, _e, _f;
8
+ return (err.status ||
9
+ err.code ||
10
+ ((_b = (_a = err.context) === null || _a === void 0 ? void 0 : _a.response) === null || _b === void 0 ? void 0 : _b.statusCode) ||
11
+ ((_c = err.original) === null || _c === void 0 ? void 0 : _c.code) ||
12
+ ((_f = (_e = (_d = err.original) === null || _d === void 0 ? void 0 : _d.context) === null || _e === void 0 ? void 0 : _e.response) === null || _f === void 0 ? void 0 : _f.statusCode));
13
+ }
14
+ async function handler(op) {
7
15
  try {
8
16
  op.result = await op.func();
9
17
  }
10
18
  catch (err) {
11
- const code = err.status ||
12
- err.code ||
13
- ((_b = (_a = err.context) === null || _a === void 0 ? void 0 : _a.response) === null || _b === void 0 ? void 0 : _b.statusCode) ||
14
- ((_c = err.original) === null || _c === void 0 ? void 0 : _c.code) ||
15
- ((_f = (_e = (_d = err.original) === null || _d === void 0 ? void 0 : _d.context) === null || _e === void 0 ? void 0 : _e.response) === null || _f === void 0 ? void 0 : _f.statusCode);
16
- if (code === 429 || code === 409 || code === 503) {
19
+ const code = parseErrorCode(err);
20
+ if (op.retryCodes.includes(code)) {
17
21
  throw err;
18
22
  }
23
+ err.code = code;
19
24
  op.error = err;
20
25
  }
21
26
  return;
@@ -24,8 +29,12 @@ class QueueExecutor {
24
29
  constructor(options) {
25
30
  this.queue = new queue_1.Queue(Object.assign(Object.assign({}, options), { handler }));
26
31
  }
27
- async run(func) {
28
- const op = { func };
32
+ async run(func, opts) {
33
+ const retryCodes = (opts === null || opts === void 0 ? void 0 : opts.retryCodes) || exports.DEFAULT_RETRY_CODES;
34
+ const op = {
35
+ func,
36
+ retryCodes,
37
+ };
29
38
  await this.queue.run(op);
30
39
  if (op.error) {
31
40
  throw op.error;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Fabricator = void 0;
4
4
  const clc = require("colorette");
5
+ const executor_1 = require("./executor");
5
6
  const error_1 = require("../../../error");
6
7
  const sourceTokenScraper_1 = require("./sourceTokenScraper");
7
8
  const timer_1 = require("./timer");
@@ -44,6 +45,7 @@ const eventarcPollerOptions = {
44
45
  masterTimeout: 25 * 60 * 1000,
45
46
  maxBackoff: 10000,
46
47
  };
48
+ const CLOUD_RUN_RESOURCE_EXHAUSTED_CODE = 8;
47
49
  const rethrowAs = (endpoint, op) => (err) => {
48
50
  logger_1.logger.error(err.message);
49
51
  throw new reporter.DeploymentError(endpoint, op, err);
@@ -269,12 +271,24 @@ class Fabricator {
269
271
  })
270
272
  .catch(rethrowAs(endpoint, "upsert eventarc channel"));
271
273
  }
272
- const resultFunction = await this.functionExecutor
273
- .run(async () => {
274
- const op = await gcfV2.createFunction(apiFunction);
275
- return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `create-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
276
- })
277
- .catch(rethrowAs(endpoint, "create"));
274
+ let resultFunction = null;
275
+ while (!resultFunction) {
276
+ resultFunction = await this.functionExecutor
277
+ .run(async () => {
278
+ const op = await gcfV2.createFunction(apiFunction);
279
+ return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `create-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
280
+ })
281
+ .catch(async (err) => {
282
+ if (err.code === CLOUD_RUN_RESOURCE_EXHAUSTED_CODE) {
283
+ await this.deleteV2Function(endpoint);
284
+ return null;
285
+ }
286
+ else {
287
+ logger_1.logger.error(err.message);
288
+ throw new reporter.DeploymentError(endpoint, "create", err);
289
+ }
290
+ });
291
+ }
278
292
  endpoint.uri = (_d = resultFunction.serviceConfig) === null || _d === void 0 ? void 0 : _d.uri;
279
293
  const serviceName = (_e = resultFunction.serviceConfig) === null || _e === void 0 ? void 0 : _e.service;
280
294
  endpoint.runServiceId = utils.last(serviceName === null || serviceName === void 0 ? void 0 : serviceName.split("/"));
@@ -367,7 +381,7 @@ class Fabricator {
367
381
  .run(async () => {
368
382
  const op = await gcfV2.updateFunction(apiFunction);
369
383
  return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `update-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
370
- })
384
+ }, { retryCodes: [...executor_1.DEFAULT_RETRY_CODES, CLOUD_RUN_RESOURCE_EXHAUSTED_CODE] })
371
385
  .catch(rethrowAs(endpoint, "update"));
372
386
  endpoint.uri = (_c = resultFunction.serviceConfig) === null || _c === void 0 ? void 0 : _c.uri;
373
387
  const serviceName = (_d = resultFunction.serviceConfig) === null || _d === void 0 ? void 0 : _d.service;
@@ -414,7 +428,7 @@ class Fabricator {
414
428
  const op = await gcfV2.deleteFunction(fnName);
415
429
  const pollerOptions = Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `delete-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name });
416
430
  await poller.pollOperation(pollerOptions);
417
- })
431
+ }, { retryCodes: [...executor_1.DEFAULT_RETRY_CODES, CLOUD_RUN_RESOURCE_EXHAUSTED_CODE] })
418
432
  .catch(rethrowAs(endpoint, "delete"));
419
433
  }
420
434
  async setRunTraits(serviceName, endpoint) {
@@ -26,7 +26,7 @@ async function default_1(context, options) {
26
26
  }
27
27
  const rulesDeploy = new rulesDeploy_1.RulesDeploy(options, rulesDeploy_1.RulesetServiceType.FIREBASE_STORAGE);
28
28
  const rulesConfigsToDeploy = [];
29
- if (!Array.isArray(rulesConfig)) {
29
+ if (!Array.isArray(rulesConfig) && options.project) {
30
30
  const defaultBucket = await gcp.storage.getDefaultBucket(options.project);
31
31
  rulesConfig = [Object.assign(rulesConfig, { bucket: defaultBucket })];
32
32
  }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readOrNull = exports.LocalFileSystem = void 0;
4
+ const fs_extra_1 = require("fs-extra");
5
+ const path = require("path");
6
+ const error_1 = require("../../../error");
7
+ const logger_1 = require("../../../../src/logger");
8
+ class LocalFileSystem {
9
+ constructor(cwd) {
10
+ this.cwd = cwd;
11
+ this.existsCache = {};
12
+ this.contentCache = {};
13
+ }
14
+ async exists(file) {
15
+ try {
16
+ if (!(file in this.contentCache)) {
17
+ this.existsCache[file] = await (0, fs_extra_1.pathExists)(path.resolve(this.cwd, file));
18
+ }
19
+ return this.existsCache[file];
20
+ }
21
+ catch (error) {
22
+ throw new error_1.FirebaseError(`Error occured while searching for file: ${error}`);
23
+ }
24
+ }
25
+ async read(file) {
26
+ try {
27
+ if (!(file in this.contentCache)) {
28
+ const fileContents = await (0, fs_extra_1.readFile)(path.resolve(this.cwd, file), "utf-8");
29
+ this.contentCache[file] = fileContents;
30
+ }
31
+ return this.contentCache[file];
32
+ }
33
+ catch (error) {
34
+ logger_1.logger.error("Error occured while reading file contents.");
35
+ throw error;
36
+ }
37
+ }
38
+ }
39
+ exports.LocalFileSystem = LocalFileSystem;
40
+ async function readOrNull(fs, path) {
41
+ try {
42
+ return fs.read(path);
43
+ }
44
+ catch (err) {
45
+ if (err && typeof err === "object" && (err === null || err === void 0 ? void 0 : err.code) === "ENOENT") {
46
+ logger_1.logger.debug("ENOENT error occured while reading file.");
47
+ return null;
48
+ }
49
+ throw new Error(`Unknown error occured while reading file: ${err}`);
50
+ }
51
+ }
52
+ exports.readOrNull = readOrNull;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.frameworkMatcher = exports.removeEmbededFrameworks = exports.filterFrameworksWithFiles = exports.filterFrameworksWithDependencies = void 0;
4
+ const error_1 = require("../../../error");
5
+ const logger_1 = require("../../../logger");
6
+ function filterFrameworksWithDependencies(allFrameworkSpecs, dependencies) {
7
+ return allFrameworkSpecs.filter((framework) => {
8
+ return framework.requiredDependencies.every((dependency) => {
9
+ return dependency.name in dependencies;
10
+ });
11
+ });
12
+ }
13
+ exports.filterFrameworksWithDependencies = filterFrameworksWithDependencies;
14
+ async function filterFrameworksWithFiles(allFrameworkSpecs, fs) {
15
+ try {
16
+ const filteredFrameworks = [];
17
+ for (const framework of allFrameworkSpecs) {
18
+ if (!framework.requiredFiles) {
19
+ filteredFrameworks.push(framework);
20
+ continue;
21
+ }
22
+ let isRequired = true;
23
+ for (let files of framework.requiredFiles) {
24
+ files = Array.isArray(files) ? files : [files];
25
+ for (const file of files) {
26
+ isRequired = isRequired && (await fs.exists(file));
27
+ if (!isRequired) {
28
+ break;
29
+ }
30
+ }
31
+ }
32
+ if (isRequired) {
33
+ filteredFrameworks.push(framework);
34
+ }
35
+ }
36
+ return filteredFrameworks;
37
+ }
38
+ catch (error) {
39
+ logger_1.logger.error("Error: Unable to filter frameworks based on required files", error);
40
+ throw error;
41
+ }
42
+ }
43
+ exports.filterFrameworksWithFiles = filterFrameworksWithFiles;
44
+ function removeEmbededFrameworks(allFrameworkSpecs) {
45
+ const embededFrameworkSet = new Set();
46
+ for (const framework of allFrameworkSpecs) {
47
+ if (!framework.embedsFrameworks) {
48
+ continue;
49
+ }
50
+ for (const item of framework.embedsFrameworks) {
51
+ embededFrameworkSet.add(item);
52
+ }
53
+ }
54
+ return allFrameworkSpecs.filter((item) => !embededFrameworkSet.has(item.id));
55
+ }
56
+ exports.removeEmbededFrameworks = removeEmbededFrameworks;
57
+ async function frameworkMatcher(runtime, fs, frameworks, dependencies) {
58
+ try {
59
+ const filterRuntimeFramework = frameworks.filter((framework) => framework.runtime === runtime);
60
+ const frameworksWithDependencies = filterFrameworksWithDependencies(filterRuntimeFramework, dependencies);
61
+ const frameworkWithFiles = await filterFrameworksWithFiles(frameworksWithDependencies, fs);
62
+ const allMatches = removeEmbededFrameworks(frameworkWithFiles);
63
+ if (allMatches.length === 0) {
64
+ return null;
65
+ }
66
+ if (allMatches.length > 1) {
67
+ const frameworkNames = allMatches.map((framework) => framework.id);
68
+ throw new error_1.FirebaseError(`Multiple Frameworks are matched: ${frameworkNames.join(", ")} Manually set up override commands in firebase.json`);
69
+ }
70
+ return allMatches[0];
71
+ }
72
+ catch (error) {
73
+ throw new error_1.FirebaseError(`Failed to match the correct framework: ${error}`);
74
+ }
75
+ }
76
+ exports.frameworkMatcher = frameworkMatcher;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.frameworkSpecs = void 0;
4
+ exports.frameworkSpecs = [
5
+ {
6
+ id: "express",
7
+ runtime: "nodejs",
8
+ webFrameworkId: "Express.js",
9
+ requiredDependencies: [
10
+ {
11
+ name: "express",
12
+ },
13
+ ],
14
+ },
15
+ {
16
+ id: "nextjs",
17
+ runtime: "nodejs",
18
+ webFrameworkId: "Next.js",
19
+ requiredFiles: ["next.config.js", "next.config.ts"],
20
+ requiredDependencies: [
21
+ {
22
+ name: "next",
23
+ },
24
+ ],
25
+ commands: {
26
+ build: {
27
+ cmd: "next build",
28
+ },
29
+ dev: {
30
+ cmd: "next dev",
31
+ env: { NODE_ENV: "dev" },
32
+ },
33
+ run: {
34
+ cmd: "next run",
35
+ env: { NODE_ENV: "production" },
36
+ },
37
+ },
38
+ },
39
+ ];
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.discover = void 0;
4
+ function discover() {
5
+ return {
6
+ baseImage: "us-docker.pkg.dev/firestack-build/test/run:latest",
7
+ environmentVariables: {
8
+ NODE_ENV: "PRODUCTION",
9
+ },
10
+ installCommand: "npm install",
11
+ buildCommand: "npm run build",
12
+ startCommand: "npm run start",
13
+ afterInstall: (b) => {
14
+ console.log("HOOK: AFTER INSTALL");
15
+ return Object.assign(Object.assign({}, b), { version: "v1alpha", notes: "afterInstall" });
16
+ },
17
+ afterBuild(b) {
18
+ console.log("HOOK: AFTER BUILD");
19
+ return Object.assign(Object.assign({}, b), { version: "v1alpha", notes: "afterBuild" });
20
+ },
21
+ };
22
+ }
23
+ exports.discover = discover;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DockerDriver = exports.DockerfileBuilder = void 0;
4
+ const fs = require("node:fs");
5
+ const path = require("node:path");
6
+ const spawn = require("cross-spawn");
7
+ const hooks_1 = require("./hooks");
8
+ const ADAPTER_SCRIPTS_PATH = "./.firebase/adapters";
9
+ const DOCKER_STAGE_INSTALL = "installer";
10
+ const DOCKER_STAGE_BUILD = "builder";
11
+ class DockerfileBuilder {
12
+ constructor() {
13
+ this.dockerfile = "";
14
+ this.lastStage = "";
15
+ }
16
+ from(image, name) {
17
+ this.dockerfile += `FROM ${image}`;
18
+ if (name) {
19
+ this.dockerfile += ` AS ${name}`;
20
+ this.lastStage = name;
21
+ }
22
+ this.dockerfile += "\n";
23
+ return this;
24
+ }
25
+ fromLastStage(name) {
26
+ return this.from(this.lastStage, name);
27
+ }
28
+ tempFrom(image, name) {
29
+ this.dockerfile += `FROM ${image}`;
30
+ if (name) {
31
+ this.dockerfile += ` AS ${name}`;
32
+ }
33
+ this.dockerfile += "\n";
34
+ return this;
35
+ }
36
+ workdir(dir) {
37
+ this.dockerfile += `WORKDIR ${dir}\n`;
38
+ return this;
39
+ }
40
+ copyForFirebase(src, dest, from) {
41
+ if (from) {
42
+ this.dockerfile += `COPY --chown=firebase:firebase --from=${from} ${src} ${dest}\n`;
43
+ }
44
+ else {
45
+ this.dockerfile += `COPY --chown=firebase:firebase ${src} ${dest}\n`;
46
+ }
47
+ return this;
48
+ }
49
+ copyFrom(src, dest, from) {
50
+ this.dockerfile += `COPY --from=${from} ${src} ${dest}\n`;
51
+ return this;
52
+ }
53
+ run(cmd, mount) {
54
+ if (mount) {
55
+ this.dockerfile += `RUN --mount=${mount} ${cmd}\n`;
56
+ }
57
+ else {
58
+ this.dockerfile += `RUN ${cmd}\n`;
59
+ }
60
+ return this;
61
+ }
62
+ env(key, value) {
63
+ this.dockerfile += `ENV ${key}="${value}"\n`;
64
+ return this;
65
+ }
66
+ envs(envs) {
67
+ for (const [key, value] of Object.entries(envs)) {
68
+ this.env(key, value);
69
+ }
70
+ return this;
71
+ }
72
+ cmd(cmds) {
73
+ this.dockerfile += `CMD [${cmds.map((c) => `"${c}"`).join(", ")}]\n`;
74
+ return this;
75
+ }
76
+ user(user) {
77
+ this.dockerfile += `USER ${user}\n`;
78
+ return this;
79
+ }
80
+ toString() {
81
+ return this.dockerfile;
82
+ }
83
+ }
84
+ exports.DockerfileBuilder = DockerfileBuilder;
85
+ class DockerDriver {
86
+ constructor(spec) {
87
+ this.spec = spec;
88
+ this.dockerfileBuilder = new DockerfileBuilder();
89
+ this.dockerfileBuilder.from(spec.baseImage, "base").user("firebase");
90
+ }
91
+ execDockerPush(args) {
92
+ console.log(`executing docker build: ${args.join(" ")}`);
93
+ return spawn.sync("docker", ["push", ...args], {
94
+ stdio: ["pipe", "inherit", "inherit"],
95
+ });
96
+ }
97
+ execDockerBuild(args, contextDir) {
98
+ console.log(`executing docker build: ${args.join(" ")} ${contextDir}`);
99
+ console.log(this.dockerfileBuilder.toString());
100
+ return spawn.sync("docker", ["buildx", "build", ...args, "-f", "-", contextDir], {
101
+ env: Object.assign(Object.assign({}, process.env), this.spec.environmentVariables),
102
+ input: this.dockerfileBuilder.toString(),
103
+ stdio: ["pipe", "inherit", "inherit"],
104
+ });
105
+ }
106
+ buildStage(stage, contextDir, tag) {
107
+ console.log(`Building stage: ${stage}`);
108
+ const args = ["--target", stage];
109
+ if (tag) {
110
+ args.push("--tag", tag);
111
+ }
112
+ const ret = this.execDockerBuild(args, contextDir);
113
+ if (ret.error || ret.status !== 0) {
114
+ throw new Error(`Failed to execute stage ${stage}: error=${ret.error} status=${ret.status}`);
115
+ }
116
+ }
117
+ exportBundle(stage, contextDir) {
118
+ const exportStage = `${stage}-export`;
119
+ this.dockerfileBuilder
120
+ .tempFrom("scratch", exportStage)
121
+ .copyFrom(hooks_1.BUNDLE_PATH, "/bundle.json", stage);
122
+ const ret = this.execDockerBuild(["--target", exportStage, "--output", ".firebase/.output"], contextDir);
123
+ if (ret.error || ret.status !== 0) {
124
+ throw new Error(`Failed to export bundle ${stage}: error=${ret.error} status=${ret.status}`);
125
+ }
126
+ return JSON.parse(fs.readFileSync("./.firebase/.output/bundle.json", "utf8"));
127
+ }
128
+ install() {
129
+ this.dockerfileBuilder
130
+ .fromLastStage(DOCKER_STAGE_INSTALL)
131
+ .workdir("/home/firebase/app")
132
+ .envs(this.spec.environmentVariables || {})
133
+ .copyForFirebase("package.json", ".")
134
+ .run(this.spec.installCommand);
135
+ this.buildStage(DOCKER_STAGE_INSTALL, ".");
136
+ }
137
+ build() {
138
+ this.dockerfileBuilder
139
+ .fromLastStage(DOCKER_STAGE_BUILD)
140
+ .copyForFirebase(".", ".")
141
+ .run(this.spec.buildCommand);
142
+ this.buildStage(DOCKER_STAGE_BUILD, ".");
143
+ }
144
+ export(bundle) {
145
+ var _a;
146
+ const startCmd = (_a = bundle.server) === null || _a === void 0 ? void 0 : _a.start.cmd;
147
+ if (startCmd) {
148
+ const exportStage = "exporter";
149
+ this.dockerfileBuilder
150
+ .from(this.spec.baseImage, exportStage)
151
+ .workdir("/home/firebase/app")
152
+ .copyForFirebase("/home/firebase/app", ".", DOCKER_STAGE_BUILD)
153
+ .cmd(startCmd);
154
+ const imageName = `us-docker.pkg.dev/${process.env.PROJECT_ID}/test/demo-nodappe`;
155
+ this.buildStage(exportStage, ".", imageName);
156
+ const ret = this.execDockerPush([imageName]);
157
+ if (ret.error || ret.status !== 0) {
158
+ throw new Error(`Failed to push image ${imageName}: error=${ret.error} status=${ret.status}`);
159
+ }
160
+ }
161
+ }
162
+ execHook(bundle, hook) {
163
+ const hookScript = `hook-${Date.now()}.js`;
164
+ const hookScriptSrc = (0, hooks_1.genHookScript)(bundle, hook);
165
+ if (!fs.existsSync(ADAPTER_SCRIPTS_PATH)) {
166
+ fs.mkdirSync(ADAPTER_SCRIPTS_PATH, { recursive: true });
167
+ }
168
+ fs.writeFileSync(path.join(ADAPTER_SCRIPTS_PATH, hookScript), hookScriptSrc);
169
+ const hookStage = path.basename(hookScript, ".js");
170
+ this.dockerfileBuilder
171
+ .fromLastStage(hookStage)
172
+ .run(`NODE_PATH=./node_modules node /framework/adapters/${hookScript}`, `source=${ADAPTER_SCRIPTS_PATH},target=/framework/adapters`);
173
+ this.buildStage(hookStage, ".");
174
+ return this.exportBundle(hookStage, ".");
175
+ }
176
+ }
177
+ exports.DockerDriver = DockerDriver;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.genHookScript = exports.BUNDLE_PATH = void 0;
4
+ exports.BUNDLE_PATH = "/home/firebase/app/.firebase/.output/bundle.json";
5
+ function genHookScript(bundle, hook) {
6
+ let hookSrc = hook.toString().trimLeft();
7
+ if (!hookSrc.startsWith("(") && !hookSrc.startsWith("function ")) {
8
+ hookSrc = `function ${hookSrc}`;
9
+ }
10
+ return `
11
+ const fs = require("node:fs");
12
+ const path = require("node:path");
13
+
14
+ const bundleDir = path.dirname("${exports.BUNDLE_PATH}");
15
+ if (!fs.existsSync(bundleDir)) {
16
+ fs.mkdirSync(bundleDir, { recursive: true });
17
+ }
18
+ const bundle = (${hookSrc})(${JSON.stringify(bundle)});
19
+ fs.writeFileSync("${exports.BUNDLE_PATH}", JSON.stringify(bundle));
20
+ `;
21
+ }
22
+ exports.genHookScript = genHookScript;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getDriver = exports.SUPPORTED_MODES = void 0;
4
+ const local_1 = require("./local");
5
+ const docker_1 = require("./docker");
6
+ exports.SUPPORTED_MODES = ["local", "docker"];
7
+ function getDriver(mode, app) {
8
+ if (mode === "local") {
9
+ return new local_1.LocalDriver(app);
10
+ }
11
+ else if (mode === "docker") {
12
+ return new docker_1.DockerDriver(app);
13
+ }
14
+ throw new Error(`Unsupported mode ${mode}`);
15
+ }
16
+ exports.getDriver = getDriver;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalDriver = void 0;
4
+ const fs = require("node:fs");
5
+ const spawn = require("cross-spawn");
6
+ const hooks_1 = require("./hooks");
7
+ class LocalDriver {
8
+ constructor(spec) {
9
+ this.spec = spec;
10
+ }
11
+ execCmd(cmd, args) {
12
+ const ret = spawn.sync(cmd, args, {
13
+ env: Object.assign(Object.assign({}, process.env), this.spec.environmentVariables),
14
+ stdio: ["pipe", "inherit", "inherit"],
15
+ });
16
+ if (ret.error) {
17
+ throw ret.error;
18
+ }
19
+ }
20
+ install() {
21
+ const [cmd, ...args] = this.spec.installCommand.split(" ");
22
+ this.execCmd(cmd, args);
23
+ }
24
+ build() {
25
+ const [cmd, ...args] = this.spec.buildCommand.split(" ");
26
+ this.execCmd(cmd, args);
27
+ }
28
+ export(bundle) {
29
+ }
30
+ execHook(bundle, hook) {
31
+ const script = (0, hooks_1.genHookScript)(bundle, hook);
32
+ this.execCmd("node", ["-e", script]);
33
+ if (!fs.existsSync(hooks_1.BUNDLE_PATH)) {
34
+ console.warn(`Expected hook to generate app bundle at ${hooks_1.BUNDLE_PATH} but got nothing.`);
35
+ console.warn("Returning original bundle.");
36
+ return bundle;
37
+ }
38
+ const newBundle = JSON.parse(fs.readFileSync(hooks_1.BUNDLE_PATH, "utf8"));
39
+ return newBundle;
40
+ }
41
+ }
42
+ exports.LocalDriver = LocalDriver;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compose = void 0;
4
+ const driver_1 = require("./driver");
5
+ const discover_1 = require("./discover");
6
+ function compose(mode) {
7
+ let bundle = { version: "v1alpha" };
8
+ const spec = (0, discover_1.discover)();
9
+ const driver = (0, driver_1.getDriver)(mode, spec);
10
+ if (spec.startCommand) {
11
+ bundle.server = {
12
+ start: {
13
+ cmd: spec.startCommand.split(" "),
14
+ },
15
+ };
16
+ }
17
+ driver.install();
18
+ if (spec.afterInstall) {
19
+ bundle = driver.execHook(bundle, spec.afterInstall);
20
+ }
21
+ driver.build();
22
+ if (spec.afterBuild) {
23
+ bundle = driver.execHook(bundle, spec.afterBuild);
24
+ }
25
+ if (bundle.server) {
26
+ driver.export(bundle);
27
+ }
28
+ return bundle;
29
+ }
30
+ exports.compose = compose;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Driver = void 0;
4
+ class Driver {
5
+ constructor(spec) {
6
+ this.spec = spec;
7
+ }
8
+ install() {
9
+ throw new Error("install() not implemented");
10
+ }
11
+ build() {
12
+ throw new Error("build() not implemented");
13
+ }
14
+ export(bundle) {
15
+ throw new Error("export() not implemented");
16
+ }
17
+ execHook(bundle, hook) {
18
+ throw new Error("execHook() not implemented");
19
+ }
20
+ }
21
+ exports.Driver = Driver;
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.updateEndpointSecret = exports.pruneAndDestroySecrets = exports.pruneSecrets = exports.inUse = exports.getSecretVersions = exports.of = exports.ensureSecret = exports.ensureValidKey = exports.labels = exports.isFirebaseManaged = void 0;
3
+ exports.updateEndpointSecret = exports.pruneAndDestroySecrets = exports.pruneSecrets = exports.inUse = exports.getSecretVersions = exports.of = exports.ensureSecret = exports.ensureValidKey = exports.ensureApi = exports.labels = exports.isFirebaseManaged = void 0;
4
4
  const utils = require("../utils");
5
5
  const poller = require("../operation-poller");
6
6
  const gcf = require("../gcp/cloudfunctions");
7
+ const ensureApiEnabled = require("../ensureApiEnabled");
7
8
  const secretManager_1 = require("../gcp/secretManager");
8
9
  const error_1 = require("../error");
9
10
  const utils_1 = require("../utils");
@@ -12,6 +13,7 @@ const env_1 = require("./env");
12
13
  const logger_1 = require("../logger");
13
14
  const api_1 = require("../api");
14
15
  const functional_1 = require("../functional");
16
+ const projectUtils_1 = require("../projectUtils");
15
17
  const FIREBASE_MANAGED = "firebase-managed";
16
18
  function isFirebaseManaged(secret) {
17
19
  return Object.keys(secret.labels || []).includes(FIREBASE_MANAGED);
@@ -27,6 +29,11 @@ function toUpperSnakeCase(key) {
27
29
  .replace(/([a-z])([A-Z])/g, "$1_$2")
28
30
  .toUpperCase();
29
31
  }
32
+ function ensureApi(options) {
33
+ const projectId = (0, projectUtils_1.needProjectId)(options);
34
+ return ensureApiEnabled.ensure(projectId, "secretmanager.googleapis.com", "runtimeconfig", true);
35
+ }
36
+ exports.ensureApi = ensureApi;
30
37
  async function ensureValidKey(key, options) {
31
38
  const transformedKey = toUpperSnakeCase(key);
32
39
  if (transformedKey !== key) {
@@ -4,17 +4,18 @@ exports.getServiceAccount = exports.listBuckets = exports.getBucket = exports.de
4
4
  const path = require("path");
5
5
  const api_1 = require("../api");
6
6
  const apiv2_1 = require("../apiv2");
7
- const logger_1 = require("../logger");
8
7
  const error_1 = require("../error");
8
+ const logger_1 = require("../logger");
9
+ const projects_1 = require("../management/projects");
9
10
  async function getDefaultBucket(projectId) {
11
+ var _a;
10
12
  try {
11
- const appengineClient = new apiv2_1.Client({ urlPrefix: api_1.appengineOrigin, apiVersion: "v1" });
12
- const resp = await appengineClient.get(`/apps/${projectId}`);
13
- if (resp.body.defaultBucket === "undefined") {
13
+ const metadata = await (0, projects_1.getFirebaseProject)(projectId);
14
+ if (!((_a = metadata.resources) === null || _a === void 0 ? void 0 : _a.storageBucket)) {
14
15
  logger_1.logger.debug("Default storage bucket is undefined.");
15
16
  throw new error_1.FirebaseError("Your project is being set up. Please wait a minute before deploying again.");
16
17
  }
17
- return resp.body.defaultBucket;
18
+ return metadata.resources.storageBucket;
18
19
  }
19
20
  catch (err) {
20
21
  logger_1.logger.info("\n\nThere was an issue deploying your functions. Verify that your project has a Google App Engine instance setup at https://console.cloud.google.com/appengine and try again. If this issue persists, please contact support.");
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isMonospaceEnv = exports.selectProjectInMonospace = void 0;
4
+ const node_fetch_1 = require("node-fetch");
5
+ const error_1 = require("../error");
6
+ const logger_1 = require("../logger");
7
+ const rc_1 = require("../rc");
8
+ const POLL_USER_RESPONSE_MILLIS = 5000;
9
+ async function selectProjectInMonospace({ projectRoot, project, isVSCE, }) {
10
+ const initFirebaseResponse = await initFirebase(project);
11
+ if (initFirebaseResponse.success === false) {
12
+ throw new Error(String(initFirebaseResponse.error));
13
+ }
14
+ const { rid } = initFirebaseResponse;
15
+ const authorizedProject = await pollAuthorizedProject(rid);
16
+ if (!authorizedProject)
17
+ return null;
18
+ if (isVSCE)
19
+ return authorizedProject;
20
+ if (projectRoot)
21
+ createFirebaseRc(projectRoot, authorizedProject);
22
+ }
23
+ exports.selectProjectInMonospace = selectProjectInMonospace;
24
+ async function pollAuthorizedProject(rid) {
25
+ const getInitFirebaseRes = await getInitFirebaseResponse(rid);
26
+ if ("userResponse" in getInitFirebaseRes) {
27
+ if (getInitFirebaseRes.userResponse.success) {
28
+ return getInitFirebaseRes.userResponse.projectId;
29
+ }
30
+ else {
31
+ return null;
32
+ }
33
+ }
34
+ const { error } = getInitFirebaseRes;
35
+ if (error === "WAITING_FOR_RESPONSE") {
36
+ await new Promise((res) => setTimeout(res, POLL_USER_RESPONSE_MILLIS));
37
+ return pollAuthorizedProject(rid);
38
+ }
39
+ if (error === "USER_CANCELED") {
40
+ throw new error_1.FirebaseError("User canceled without authorizing any project");
41
+ }
42
+ throw new error_1.FirebaseError(`Unhandled /get-init-firebase-response error: ${error}`);
43
+ }
44
+ async function initFirebase(project) {
45
+ const port = getMonospaceDaemonPort();
46
+ if (!port)
47
+ throw new error_1.FirebaseError("Undefined MONOSPACE_DAEMON_PORT");
48
+ const initFirebaseURL = new URL(`http://localhost:${port}/init-firebase`);
49
+ if (project) {
50
+ initFirebaseURL.searchParams.set("known_project", project);
51
+ }
52
+ const initFirebaseRes = await (0, node_fetch_1.default)(initFirebaseURL.toString(), {
53
+ method: "POST",
54
+ headers: {
55
+ "Content-Type": "application/json",
56
+ },
57
+ });
58
+ const initFirebaseResponse = (await initFirebaseRes.json());
59
+ return initFirebaseResponse;
60
+ }
61
+ async function getInitFirebaseResponse(rid) {
62
+ const port = getMonospaceDaemonPort();
63
+ if (!port)
64
+ throw new error_1.FirebaseError("Undefined MONOSPACE_DAEMON_PORT");
65
+ const getInitFirebaseRes = await (0, node_fetch_1.default)(`http://localhost:${port}/get-init-firebase-response?rid=${rid}`);
66
+ const getInitFirebaseJson = (await getInitFirebaseRes.json());
67
+ logger_1.logger.debug(`/get-init-firebase-response?rid=${rid} response:`);
68
+ logger_1.logger.debug(getInitFirebaseJson);
69
+ return getInitFirebaseJson;
70
+ }
71
+ function createFirebaseRc(projectDir, authorizedProject) {
72
+ const firebaseRc = (0, rc_1.loadRC)({ cwd: projectDir });
73
+ firebaseRc.addProjectAlias("default", authorizedProject);
74
+ return firebaseRc.save();
75
+ }
76
+ async function isMonospaceEnv() {
77
+ return Promise.resolve(Boolean(getMonospaceDaemonPort()));
78
+ }
79
+ exports.isMonospaceEnv = isMonospaceEnv;
80
+ function getMonospaceDaemonPort() {
81
+ return process.env.MONOSPACE_DAEMON_PORT;
82
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -10,6 +10,7 @@ const logger_1 = require("./logger");
10
10
  const utils = require("./utils");
11
11
  const scopes = require("./scopes");
12
12
  const auth_1 = require("./auth");
13
+ const monospace_1 = require("./monospace");
13
14
  const AUTH_ERROR_MESSAGE = `Command requires authentication, please run ${clc.bold("firebase login")}`;
14
15
  let authClient;
15
16
  function getAuthClient(config) {
@@ -23,6 +24,13 @@ async function autoAuth(options, authScopes) {
23
24
  const client = getAuthClient({ scopes: authScopes, projectId: options.project });
24
25
  const token = await client.getAccessToken();
25
26
  token !== null ? apiv2.setAccessToken(token) : false;
27
+ if (!options.isVSCE && (await (0, monospace_1.isMonospaceEnv)())) {
28
+ await (0, monospace_1.selectProjectInMonospace)({
29
+ projectRoot: options.config.projectDir,
30
+ project: options.project,
31
+ isVSCE: options.isVSCE,
32
+ });
33
+ }
26
34
  }
27
35
  async function requireAuth(options) {
28
36
  api.setScopes([scopes.CLOUD_PLATFORM, scopes.FIREBASE_PLATFORM]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "12.3.0",
3
+ "version": "12.4.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {