firebase-tools 14.18.0 → 14.19.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 (46) hide show
  1. package/README.md +11 -5
  2. package/lib/appUtils.js +230 -0
  3. package/lib/bin/mcp.js +16 -1
  4. package/lib/commands/index.js +8 -0
  5. package/lib/commands/init.js +0 -2
  6. package/lib/commands/remoteconfig-experiments-delete.js +32 -0
  7. package/lib/commands/remoteconfig-experiments-get.js +20 -0
  8. package/lib/commands/remoteconfig-experiments-list.js +38 -0
  9. package/lib/commands/remoteconfig-rollouts-delete.js +32 -0
  10. package/lib/commands/remoteconfig-rollouts-get.js +20 -0
  11. package/lib/commands/remoteconfig-rollouts-list.js +38 -0
  12. package/lib/config.js +4 -2
  13. package/lib/deploy/apphosting/deploy.js +8 -5
  14. package/lib/deploy/functions/deploy.js +5 -4
  15. package/lib/deploy/functions/params.js +2 -2
  16. package/lib/emulator/commandUtils.js +3 -3
  17. package/lib/emulator/controller.js +3 -2
  18. package/lib/gcp/storage.js +73 -20
  19. package/lib/init/features/dataconnect/index.js +18 -9
  20. package/lib/init/features/project.js +66 -75
  21. package/lib/management/projects.js +16 -4
  22. package/lib/mcp/index.js +9 -0
  23. package/lib/mcp/prompts/core/deploy.js +8 -8
  24. package/lib/mcp/prompts/core/init.js +15 -18
  25. package/lib/mcp/prompts/crashlytics/connect.js +2 -2
  26. package/lib/mcp/prompts/index.js +30 -9
  27. package/lib/mcp/resources/guides/init_ai.js +8 -0
  28. package/lib/mcp/resources/guides/init_auth.js +4 -0
  29. package/lib/mcp/resources/guides/init_firestore_rules.js +2 -0
  30. package/lib/mcp/resources/index.js +16 -1
  31. package/lib/mcp/tools/apphosting/list_backends.js +1 -1
  32. package/lib/mcp/tools/core/get_environment.js +34 -17
  33. package/lib/mcp/tools/core/init.js +2 -1
  34. package/lib/mcp/tools/core/logout.js +1 -2
  35. package/lib/mcp/tools/functions/get_logs.js +9 -7
  36. package/lib/mcp/tools/index.js +3 -2
  37. package/lib/remoteconfig/deleteExperiment.js +32 -0
  38. package/lib/remoteconfig/deleteRollout.js +33 -0
  39. package/lib/remoteconfig/getExperiment.js +52 -0
  40. package/lib/remoteconfig/getRollout.js +43 -0
  41. package/lib/remoteconfig/interfaces.js +3 -1
  42. package/lib/remoteconfig/listExperiments.js +72 -0
  43. package/lib/remoteconfig/listRollouts.js +72 -0
  44. package/lib/remoteconfig/options.js +4 -0
  45. package/lib/track.js +6 -4
  46. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.availablePrompts = void 0;
3
+ exports.markdownDocsOfPrompts = exports.availablePrompts = void 0;
4
4
  const core_1 = require("./core");
5
5
  const dataconnect_1 = require("./dataconnect");
6
6
  const crashlytics_1 = require("./crashlytics");
@@ -33,16 +33,37 @@ function namespacePrompts(promptsToNamespace, feature) {
33
33
  return newPrompt;
34
34
  });
35
35
  }
36
- function availablePrompts(features) {
37
- const allPrompts = namespacePrompts(prompts["core"], "core");
38
- if (!features) {
39
- features = Object.keys(prompts).filter((f) => f !== "core");
36
+ function availablePrompts(activeFeatures) {
37
+ const allPrompts = [];
38
+ if (!(activeFeatures === null || activeFeatures === void 0 ? void 0 : activeFeatures.length)) {
39
+ activeFeatures = Object.keys(prompts);
40
40
  }
41
- for (const feature of features) {
42
- if (prompts[feature] && feature !== "core") {
43
- allPrompts.push(...namespacePrompts(prompts[feature], feature));
44
- }
41
+ if (!activeFeatures.includes("core")) {
42
+ activeFeatures = ["core", ...activeFeatures];
43
+ }
44
+ for (const feature of activeFeatures) {
45
+ allPrompts.push(...namespacePrompts(prompts[feature], feature));
45
46
  }
46
47
  return allPrompts;
47
48
  }
48
49
  exports.availablePrompts = availablePrompts;
50
+ function markdownDocsOfPrompts() {
51
+ var _a, _b;
52
+ const allPrompts = availablePrompts();
53
+ let doc = `
54
+ | Prompt Name | Feature Group | Description |
55
+ | ----------- | ------------- | ----------- |`;
56
+ for (const prompt of allPrompts) {
57
+ const feature = ((_a = prompt.mcp._meta) === null || _a === void 0 ? void 0 : _a.feature) || "";
58
+ let description = prompt.mcp.description || "";
59
+ if ((_b = prompt.mcp.arguments) === null || _b === void 0 ? void 0 : _b.length) {
60
+ const argsList = prompt.mcp.arguments.map((arg) => ` <br>&lt;${arg.name}&gt;${arg.required ? "" : " (optional)"}: ${arg.description || ""}`);
61
+ description += ` <br><br>Arguments:${argsList.join("")}`;
62
+ }
63
+ description = description.replaceAll("\n", "<br>");
64
+ doc += `
65
+ | ${prompt.mcp.name} | ${feature} | ${description} |`;
66
+ }
67
+ return doc;
68
+ }
69
+ exports.markdownDocsOfPrompts = markdownDocsOfPrompts;
@@ -60,6 +60,10 @@ Take the following actions depending on the language and platform or framework t
60
60
 
61
61
  - Use the firebase_init tool to set up ailogic
62
62
 
63
+ - For Android, the Google Services Gradle plugin is required to prevent the app from crashing. You must add it in two files:
64
+ - 1. In your project-level \`/build.gradle.kts\` file, add the plugin to the plugins block: id("com.google.gms.google-services") version "4.4.2" apply false
65
+ - 2. In your **app-level** \`/app/build.gradle.kts\` file, apply the plugin: id("com.google.gms.google-services")
66
+
63
67
  ### 3\. Implement AI Features
64
68
 
65
69
  #### Gather Building Blocks for Code Generation
@@ -77,6 +81,10 @@ Take the following actions depending on the language and platform or framework t
77
81
  - Kotlin Only
78
82
  - implementation(platform("com.google.firebase:firebase-bom:34.3.0")) or a higher bom version if it is available
79
83
  - implementation("com.google.firebase:firebase-ai")
84
+ - CRITICAL: When initializing the Firebase AI model in Kotlin, you must explicitly specify the Google AI backend by calling the googleAI() function. The correct syntax is GenerativeBackend.googleAI().
85
+ - Correct Example: val model = Firebase.ai(backend = GenerativeBackend.googleAI()).generativeModel(...)
86
+ - Incorrect: Do not use the invalid constant GenerativeBackend.GOOGLE_AI.
87
+ - The Kotlin SDK public API makes extensive use of suspend functions and coroutines. Make sure the code you generate is based on that paradigm and avoid using callbacks unless absolutely necessary in Kotlin
80
88
  - For Flutter apps, always include the following imports. do not forget or modify them
81
89
  - import 'package:firebase_core/firebase_core.dart';
82
90
  - import 'package:firebase_ai/firebase_ai.dart';
@@ -27,6 +27,10 @@ exports.init_auth = (0, resource_1.resource)({
27
27
  **Testing & Deployment:**
28
28
  - Test the complete sign-up and sign-in flow to verify authentication functionality
29
29
  - Deploy the application to production once authentication is verified and working properly
30
+
31
+ **Next Steps:**
32
+ - **Security Rules**: If an app uses *Cloud Firestore database*, *Cloud Storage for Firebase*, or *Firebase Realtime Database*, then please update user-based Security Rules that are structured according to the app's specific requirements.
33
+ - **App Deployment**: Deploy the app to production after Security Rules are verified to be working properly.
30
34
  `.trim(),
31
35
  },
32
36
  ],
@@ -30,6 +30,8 @@ ${config.readProjectFile("firestore.rules", { fallback: "<FILE DOES NOT EXIST>"
30
30
 
31
31
  For database entities that neatly fall into the "personal" and "public categories, you can use the personalData and publicData rules. Use the following firestore.rules file, and add a comment above 'personalData' and 'publicData' to note what entities apply to each rule.
32
32
 
33
+ **Next Steps:**
34
+ - **App Deployment**: Deploy the app to production after Security Rules are verified to be working properly.
33
35
  \`\`\`
34
36
  rules_version = '2';
35
37
 
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveResource = exports.resourceTemplates = exports.resources = void 0;
3
+ exports.markdownDocsOfResources = exports.resolveResource = exports.resourceTemplates = exports.resources = void 0;
4
4
  const docs_1 = require("./docs");
5
5
  const init_ai_1 = require("./guides/init_ai");
6
6
  const init_auth_1 = require("./guides/init_auth");
@@ -42,3 +42,18 @@ async function resolveResource(uri, ctx, track = true) {
42
42
  return null;
43
43
  }
44
44
  exports.resolveResource = resolveResource;
45
+ function markdownDocsOfResources() {
46
+ const allResources = [...exports.resources, ...exports.resourceTemplates];
47
+ const headings = `
48
+ | Resource Name | Description |
49
+ | ------------- | ----------- |`;
50
+ const resourceRows = allResources.map((res) => {
51
+ let desc = res.mcp.title ? `${res.mcp.title}: ` : "";
52
+ desc += res.mcp.description || "";
53
+ desc = desc.replaceAll("\n", "<br>");
54
+ return `
55
+ | ${res.mcp.name} | ${desc} |`;
56
+ });
57
+ return headings + resourceRows.join("");
58
+ }
59
+ exports.markdownDocsOfResources = markdownDocsOfResources;
@@ -13,7 +13,7 @@ exports.list_backends = (0, tool_1.tool)({
13
13
  "is the resource name of the Cloud Run service serving the App Hosting backend. The last segment of that name is the service ID. " +
14
14
  "`domains` is the list of domains that are associated with the backend. They either have type `CUSTOM` or `DEFAULT`. " +
15
15
  " Every backend should have a `DEFAULT` domain. " +
16
- " The actual domain that a user would use to conenct to the backend is the last parameter of the domain resource name. " +
16
+ " The actual domain that a user would use to connect to the backend is the last parameter of the domain resource name. " +
17
17
  " If a custom domain is correctly set up, it will have statuses ending in `ACTIVE`.",
18
18
  inputSchema: zod_1.z.object({
19
19
  location: zod_1.z
@@ -8,6 +8,7 @@ const projectUtils_1 = require("../../../projectUtils");
8
8
  const js_yaml_1 = require("js-yaml");
9
9
  const auth_1 = require("../../../auth");
10
10
  const configstore_1 = require("../../../configstore");
11
+ const appUtils_1 = require("../../../appUtils");
11
12
  exports.get_environment = (0, tool_1.tool)({
12
13
  name: "get_environment",
13
14
  description: "Use this to retrieve the current Firebase **environment** configuration for the Firebase CLI and Firebase MCP server, including current authenticated user, project directory, active Firebase Project, and more.",
@@ -23,32 +24,48 @@ exports.get_environment = (0, tool_1.tool)({
23
24
  }, async (_, { projectId, host, accountEmail, rc, config }) => {
24
25
  const aliases = projectId ? (0, projectUtils_1.getAliases)({ rc }, projectId) : [];
25
26
  const geminiTosAccepted = !!configstore_1.configstore.get("gemini");
27
+ const projectFileExists = config.projectFileExists("firebase.json");
28
+ const detectedApps = await (0, appUtils_1.detectApps)(process.cwd());
29
+ const allAccounts = (0, auth_1.getAllAccounts)().map((account) => account.user.email);
30
+ const hasOtherAccounts = allAccounts.filter((email) => email !== accountEmail).length > 0;
31
+ const projectConfigPathString = projectFileExists
32
+ ? config.path("firebase.json")
33
+ : "<NO CONFIG PRESENT>";
34
+ const detectedAppsMap = detectedApps
35
+ .filter((app) => !!app.appId)
36
+ .reduce((map, app) => {
37
+ if (app.appId) {
38
+ map.set(app.appId, app.bundleId ? app.bundleId : "<UNKNOWN BUNDLE ID>");
39
+ }
40
+ return map;
41
+ }, new Map());
42
+ const activeProjectString = projectId
43
+ ? `${projectId}${aliases.length ? ` (alias: ${aliases.join(",")})` : ""}`
44
+ : "<NONE>";
45
+ const acceptedGeminiTosString = geminiTosAccepted ? "Accepted" : "<NOT ACCEPTED>";
26
46
  return (0, util_1.toContent)(`# Environment Information
27
47
 
28
48
  Project Directory: ${host.cachedProjectDir}
29
- Project Config Path: ${config.projectFileExists("firebase.json") ? config.path("firebase.json") : "<NO CONFIG PRESENT>"}
30
- Active Project ID: ${projectId ? `${projectId}${aliases.length ? ` (alias: ${aliases.join(",")})` : ""}` : "<NONE>"}
49
+ Project Config Path: ${projectConfigPathString}
50
+ Active Project ID: ${activeProjectString}
51
+ Gemini in Firebase Terms of Service: ${acceptedGeminiTosString}
31
52
  Authenticated User: ${accountEmail || "<NONE>"}
32
- Gemini in Firebase Terms of Service: ${geminiTosAccepted ? "Accepted" : "Not Accepted"}
33
-
34
- # Available Project Aliases (format: '[alias]: [projectId]')
35
-
36
- ${(0, js_yaml_1.dump)(rc.projects).trim()}
37
-
38
- # Available Accounts:
39
-
40
- ${(0, js_yaml_1.dump)((0, auth_1.getAllAccounts)().map((account) => account.user.email)).trim()}
41
- ${config.projectFileExists("firebase.json")
42
- ? `
43
- # firebase.json contents:
53
+ Detected App IDs: ${detectedAppsMap.size > 0 ? `\n\n${(0, js_yaml_1.dump)(Object.fromEntries(detectedAppsMap)).trim()}\n` : "<NONE>"}
54
+ Available Project Aliases (format: '[alias]: [projectId]'): ${Object.entries(rc.projects).length > 0 ? `\n\n${(0, js_yaml_1.dump)(rc.projects).trim()}\n` : "<NONE>"}${hasOtherAccounts ? `\nAvailable Accounts: \n\n${(0, js_yaml_1.dump)(allAccounts).trim()}` : ""}
55
+ ${projectFileExists
56
+ ? `\nfirebase.json contents:
44
57
 
45
58
  \`\`\`json
46
59
  ${config.readProjectFile("firebase.json")}
47
60
  \`\`\``
48
- : `\n# Empty Environment
61
+ : `\nNo firebase.json file was found.
62
+
63
+ If this project does not use Firebase services that require a firebase.json file, no action is necessary.
49
64
 
50
- It looks like the current directory is not initialized as a Firebase project. The user will most likely want to:
65
+ If this project uses Firebase services that require a firebase.json file, the user will most likely want to:
51
66
 
52
67
  a) Change the project directory using the 'firebase_update_environment' tool to select a directory with a 'firebase.json' file in it, or
53
- b) Initialize a new Firebase project directory using the 'firebase_init' tool.`}`);
68
+ b) Initialize a new Firebase project directory using the 'firebase_init' tool.
69
+
70
+ Confirm with the user before taking action.`}`);
54
71
  });
@@ -11,6 +11,7 @@ const errors_1 = require("../../errors");
11
11
  const error_1 = require("../../../error");
12
12
  const utils_1 = require("../../../init/features/ailogic/utils");
13
13
  const projects_1 = require("../../../management/projects");
14
+ const dataconnect_1 = require("../../../init/features/dataconnect");
14
15
  exports.init = (0, tool_1.tool)({
15
16
  name: "init",
16
17
  description: "Use this to initialize selected Firebase services in the workspace (Cloud Firestore database, Firebase Data Connect, Firebase Realtime Database, Firebase AI Logic). All services are optional; specify only the products you want to set up. " +
@@ -70,7 +71,7 @@ exports.init = (0, tool_1.tool)({
70
71
  location_id: zod_1.z
71
72
  .string()
72
73
  .optional()
73
- .default("us-central1")
74
+ .default(dataconnect_1.FDC_DEFAULT_REGION)
74
75
  .describe("The GCP region ID to set up the Firebase Data Connect service."),
75
76
  cloudsql_instance_id: zod_1.z
76
77
  .string()
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.logout = void 0;
4
4
  const zod_1 = require("zod");
5
- const clc = require("colorette");
6
5
  const tool_1 = require("../../tool");
7
6
  const util_1 = require("../../util");
8
7
  const auth_1 = require("../../../auth");
@@ -44,7 +43,7 @@ exports.logout = (0, tool_1.tool)({
44
43
  if (token) {
45
44
  try {
46
45
  await (0, auth_1.logout)(token);
47
- logoutMessages.push(`Logged out from ${clc.bold(account.user.email)}`);
46
+ logoutMessages.push(`Logged out from ${account.user.email}`);
48
47
  }
49
48
  catch (e) {
50
49
  if (e instanceof Error) {
@@ -6,6 +6,7 @@ const tool_1 = require("../../tool");
6
6
  const util_1 = require("../../util");
7
7
  const functionslog_1 = require("../../../functions/functionslog");
8
8
  const cloudlogging_1 = require("../../../gcp/cloudlogging");
9
+ const error_1 = require("../../../error");
9
10
  const SEVERITY_LEVELS = [
10
11
  "DEFAULT",
11
12
  "DEBUG",
@@ -39,12 +40,13 @@ function validateTimestamp(label, value) {
39
40
  }
40
41
  exports.get_logs = (0, tool_1.tool)({
41
42
  name: "get_logs",
42
- description: "Retrieves a page of Cloud Functions log entries using Google Cloud Logging advanced filters.",
43
+ description: "Use this to retrieve a page of Cloud Functions log entries using Google Cloud Logging advanced filters.",
43
44
  inputSchema: zod_1.z.object({
44
45
  function_names: zod_1.z
45
- .union([zod_1.z.string(), zod_1.z.array(zod_1.z.string()).min(1)])
46
+ .array(zod_1.z.string())
47
+ .min(1)
46
48
  .optional()
47
- .describe("Optional list of deployed Cloud Function names to filter logs (string or array)."),
49
+ .describe("Optional list of deployed Cloud Function IDs to filter logs (e.g. ['fnA','fnB'])."),
48
50
  page_size: zod_1.z
49
51
  .number()
50
52
  .int()
@@ -84,8 +86,8 @@ exports.get_logs = (0, tool_1.tool)({
84
86
  requiresProject: true,
85
87
  },
86
88
  }, async ({ function_names, page_size, order, page_token, min_severity, start_time, end_time, filter }, { projectId }) => {
87
- const resolvedOrder = order;
88
- const resolvedPageSize = page_size;
89
+ const resolvedOrder = (order === null || order === void 0 ? void 0 : order.toLowerCase()) === "asc" ? "asc" : "desc";
90
+ const resolvedPageSize = page_size !== null && page_size !== void 0 ? page_size : 50;
89
91
  const normalizedSelectors = normalizeFunctionSelectors(function_names);
90
92
  const filterParts = [(0, functionslog_1.getApiFilter)(normalizedSelectors)];
91
93
  if (min_severity) {
@@ -143,7 +145,7 @@ exports.get_logs = (0, tool_1.tool)({
143
145
  return (0, util_1.toContent)(response);
144
146
  }
145
147
  catch (err) {
146
- const message = err instanceof Error ? err.message : "Failed to retrieve Cloud Logging entries.";
147
- return (0, util_1.mcpError)(message);
148
+ const errMsg = (0, error_1.getErrMsg)((err === null || err === void 0 ? void 0 : err.original) || err, "Failed to retrieve Cloud Logging entries.");
149
+ return (0, util_1.mcpError)(errMsg);
148
150
  }
149
151
  });
@@ -18,7 +18,7 @@ function availableTools(activeFeatures) {
18
18
  activeFeatures = Object.keys(tools);
19
19
  }
20
20
  if (!activeFeatures.includes("core")) {
21
- activeFeatures.unshift("core");
21
+ activeFeatures = ["core", ...activeFeatures];
22
22
  }
23
23
  for (const key of activeFeatures) {
24
24
  toolDefs.push(...tools[key]);
@@ -53,8 +53,9 @@ function markdownDocsOfTools() {
53
53
  if (feature === "firebase") {
54
54
  feature = "core";
55
55
  }
56
+ const description = (((_c = tool.mcp) === null || _c === void 0 ? void 0 : _c.description) || "").replaceAll("\n", "<br>");
56
57
  doc += `
57
- | ${tool.mcp.name} | ${feature} | ${((_c = tool.mcp) === null || _c === void 0 ? void 0 : _c.description) || ""} |`;
58
+ | ${tool.mcp.name} | ${feature} | ${description} |`;
58
59
  }
59
60
  return doc;
60
61
  }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deleteExperiment = void 0;
4
+ const clc = require("colorette");
5
+ const api_1 = require("../api");
6
+ const apiv2_1 = require("../apiv2");
7
+ const error_1 = require("../error");
8
+ const utils_1 = require("../utils");
9
+ const TIMEOUT = 30000;
10
+ const apiClient = new apiv2_1.Client({
11
+ urlPrefix: (0, api_1.remoteConfigApiOrigin)(),
12
+ apiVersion: "v1",
13
+ });
14
+ async function deleteExperiment(projectId, namespace, experimentId) {
15
+ try {
16
+ await apiClient.request({
17
+ method: "DELETE",
18
+ path: `projects/${projectId}/namespaces/${namespace}/experiments/${experimentId}`,
19
+ timeout: TIMEOUT,
20
+ });
21
+ return clc.bold(`Successfully deleted experiment ${clc.yellow(experimentId)}`);
22
+ }
23
+ catch (err) {
24
+ const error = (0, error_1.getError)(err);
25
+ if (error.message.includes("is running and cannot be deleted")) {
26
+ const rcConsoleUrl = (0, utils_1.consoleUrl)(projectId, `/config/experiment/results/${experimentId}`);
27
+ throw new error_1.FirebaseError(`Experiment ${experimentId} is currently running and cannot be deleted. If you want to delete this experiment, stop it at ${rcConsoleUrl}`, { original: error });
28
+ }
29
+ throw new error_1.FirebaseError(`Failed to delete Remote Config experiment with ID ${experimentId} for project ${projectId}. Error: ${(0, error_1.getErrMsg)(err)}`, { original: error });
30
+ }
31
+ }
32
+ exports.deleteExperiment = deleteExperiment;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deleteRollout = void 0;
4
+ const api_1 = require("../api");
5
+ const apiv2_1 = require("../apiv2");
6
+ const error_1 = require("../error");
7
+ const utils_1 = require("../utils");
8
+ const clc = require("colorette");
9
+ const TIMEOUT = 30000;
10
+ const apiClient = new apiv2_1.Client({
11
+ urlPrefix: (0, api_1.remoteConfigApiOrigin)(),
12
+ apiVersion: "v1",
13
+ });
14
+ async function deleteRollout(projectId, namespace, rolloutId) {
15
+ try {
16
+ await apiClient.request({
17
+ method: "DELETE",
18
+ path: `/projects/${projectId}/namespaces/${namespace}/rollouts/${rolloutId}`,
19
+ timeout: TIMEOUT,
20
+ });
21
+ return clc.bold(`Successfully deleted rollout ${clc.yellow(rolloutId)}`);
22
+ }
23
+ catch (err) {
24
+ const originalError = (0, error_1.getError)(err);
25
+ const errorMessage = (0, error_1.getErrMsg)(err);
26
+ if (errorMessage.includes("is running and cannot be deleted")) {
27
+ const rcConsoleUrl = (0, utils_1.consoleUrl)(projectId, `/config/env/firebase/rollout/${rolloutId}`);
28
+ throw new error_1.FirebaseError(`Rollout '${rolloutId}' is currently running and cannot be deleted. If you want to delete this rollout, stop it at ${rcConsoleUrl}`, { original: originalError });
29
+ }
30
+ throw new error_1.FirebaseError(`Failed to delete Remote Config rollout with ID ${rolloutId} for project ${projectId}. Error: ${errorMessage}`, { original: originalError });
31
+ }
32
+ }
33
+ exports.deleteRollout = deleteRollout;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getExperiment = exports.parseExperiment = void 0;
4
+ const Table = require("cli-table3");
5
+ const util = require("util");
6
+ const api_1 = require("../api");
7
+ const apiv2_1 = require("../apiv2");
8
+ const logger_1 = require("../logger");
9
+ const error_1 = require("../error");
10
+ const TIMEOUT = 30000;
11
+ const TABLE_HEAD = ["Entry Name", "Value"];
12
+ const apiClient = new apiv2_1.Client({
13
+ urlPrefix: (0, api_1.remoteConfigApiOrigin)(),
14
+ apiVersion: "v1",
15
+ });
16
+ const parseExperiment = (experiment) => {
17
+ const table = new Table({ head: TABLE_HEAD, style: { head: ["green"] } });
18
+ table.push(["Name", experiment.name]);
19
+ table.push(["Display Name", experiment.definition.displayName]);
20
+ table.push(["Service", experiment.definition.service]);
21
+ table.push([
22
+ "Objectives",
23
+ util.inspect(experiment.definition.objectives, { showHidden: false, depth: null }),
24
+ ]);
25
+ table.push([
26
+ "Variants",
27
+ util.inspect(experiment.definition.variants, { showHidden: false, depth: null }),
28
+ ]);
29
+ table.push(["State", experiment.state]);
30
+ table.push(["Start Time", experiment.startTime]);
31
+ table.push(["End Time", experiment.endTime]);
32
+ table.push(["Last Update Time", experiment.lastUpdateTime]);
33
+ table.push(["etag", experiment.etag]);
34
+ return table.toString();
35
+ };
36
+ exports.parseExperiment = parseExperiment;
37
+ async function getExperiment(projectId, namespace, experimentId) {
38
+ try {
39
+ const res = await apiClient.request({
40
+ method: "GET",
41
+ path: `projects/${projectId}/namespaces/${namespace}/experiments/${experimentId}`,
42
+ timeout: TIMEOUT,
43
+ });
44
+ return res.body;
45
+ }
46
+ catch (err) {
47
+ const error = (0, error_1.getError)(err);
48
+ logger_1.logger.debug(error.message);
49
+ throw new error_1.FirebaseError(`Failed to get Remote Config experiment with ID ${experimentId} for project ${projectId}. Error: ${error.message}`, { original: error });
50
+ }
51
+ }
52
+ exports.getExperiment = getExperiment;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getRollout = exports.parseRolloutIntoTable = void 0;
4
+ const api_1 = require("../api");
5
+ const apiv2_1 = require("../apiv2");
6
+ const logger_1 = require("../logger");
7
+ const error_1 = require("../error");
8
+ const Table = require("cli-table3");
9
+ const util = require("util");
10
+ const TIMEOUT = 30000;
11
+ const TABLE_HEAD = ["Entry Name", "Value"];
12
+ const apiClient = new apiv2_1.Client({
13
+ urlPrefix: (0, api_1.remoteConfigApiOrigin)(),
14
+ apiVersion: "v1",
15
+ });
16
+ const parseRolloutIntoTable = (rollout) => {
17
+ const table = new Table({ head: TABLE_HEAD, style: { head: ["green"] } });
18
+ table.push(["Name", rollout.name], ["Display Name", rollout.definition.displayName], ["Description", rollout.definition.description], ["State", rollout.state], ["Create Time", rollout.createTime], ["Start Time", rollout.startTime], ["End Time", rollout.endTime], ["Last Update Time", rollout.lastUpdateTime], [
19
+ "Control Variant",
20
+ util.inspect(rollout.definition.controlVariant, { showHidden: false, depth: null }),
21
+ ], [
22
+ "Enabled Variant",
23
+ util.inspect(rollout.definition.enabledVariant, { showHidden: false, depth: null }),
24
+ ], ["ETag", rollout.etag]);
25
+ return table.toString();
26
+ };
27
+ exports.parseRolloutIntoTable = parseRolloutIntoTable;
28
+ async function getRollout(projectId, namespace, rolloutId) {
29
+ try {
30
+ const res = await apiClient.request({
31
+ method: "GET",
32
+ path: `/projects/${projectId}/namespaces/${namespace}/rollouts/${rolloutId}`,
33
+ timeout: TIMEOUT,
34
+ });
35
+ return res.body;
36
+ }
37
+ catch (err) {
38
+ const error = (0, error_1.getError)(err);
39
+ logger_1.logger.debug(error.message);
40
+ throw new error_1.FirebaseError(`Failed to get Remote Config Rollout with ID ${rolloutId} for project ${projectId}. Error: ${error.message}`, { original: error });
41
+ }
42
+ }
43
+ exports.getRollout = getRollout;
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TagColor = void 0;
3
+ exports.TagColor = exports.DEFAULT_PAGE_SIZE = exports.NAMESPACE_FIREBASE = void 0;
4
+ exports.NAMESPACE_FIREBASE = "firebase";
5
+ exports.DEFAULT_PAGE_SIZE = "10";
4
6
  var TagColor;
5
7
  (function (TagColor) {
6
8
  TagColor["BLUE"] = "Blue";
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listExperiments = exports.parseExperimentList = void 0;
4
+ const Table = require("cli-table3");
5
+ const api_1 = require("../api");
6
+ const apiv2_1 = require("../apiv2");
7
+ const logger_1 = require("../logger");
8
+ const error_1 = require("../error");
9
+ const TIMEOUT = 30000;
10
+ const TABLE_HEAD = [
11
+ "Experiment ID",
12
+ "Display Name",
13
+ "Service",
14
+ "Description",
15
+ "State",
16
+ "Start Time",
17
+ "End Time",
18
+ "Last Update Time",
19
+ "etag",
20
+ ];
21
+ const apiClient = new apiv2_1.Client({
22
+ urlPrefix: (0, api_1.remoteConfigApiOrigin)(),
23
+ apiVersion: "v1",
24
+ });
25
+ const parseExperimentList = (experiments) => {
26
+ if (experiments.length === 0)
27
+ return "\x1b[33mNo experiments found\x1b[0m";
28
+ const table = new Table({ head: TABLE_HEAD, style: { head: ["green"] } });
29
+ for (const experiment of experiments) {
30
+ table.push([
31
+ experiment.name.split("/").pop(),
32
+ experiment.definition.displayName,
33
+ experiment.definition.service,
34
+ experiment.definition.description,
35
+ experiment.state,
36
+ experiment.startTime,
37
+ experiment.endTime,
38
+ experiment.lastUpdateTime,
39
+ experiment.etag,
40
+ ]);
41
+ }
42
+ return table.toString();
43
+ };
44
+ exports.parseExperimentList = parseExperimentList;
45
+ async function listExperiments(projectId, namespace, listExperimentOptions) {
46
+ try {
47
+ const params = new URLSearchParams();
48
+ if (listExperimentOptions.pageSize) {
49
+ params.set("page_size", listExperimentOptions.pageSize);
50
+ }
51
+ if (listExperimentOptions.filter) {
52
+ params.set("filter", listExperimentOptions.filter);
53
+ }
54
+ if (listExperimentOptions.pageToken) {
55
+ params.set("page_token", listExperimentOptions.pageToken);
56
+ }
57
+ logger_1.logger.debug(`Query parameters for listExperiments: ${params.toString()}`);
58
+ const res = await apiClient.request({
59
+ method: "GET",
60
+ path: `projects/${projectId}/namespaces/${namespace}/experiments`,
61
+ queryParams: params,
62
+ timeout: TIMEOUT,
63
+ });
64
+ return res.body;
65
+ }
66
+ catch (err) {
67
+ const error = (0, error_1.getError)(err);
68
+ logger_1.logger.debug(error.message);
69
+ throw new error_1.FirebaseError(`Failed to get Remote Config experiments for project ${projectId}. Error: ${error.message}`, { original: error });
70
+ }
71
+ }
72
+ exports.listExperiments = listExperiments;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listRollouts = exports.parseRolloutList = void 0;
4
+ const api_1 = require("../api");
5
+ const apiv2_1 = require("../apiv2");
6
+ const logger_1 = require("../logger");
7
+ const error_1 = require("../error");
8
+ const Table = require("cli-table3");
9
+ const TIMEOUT = 30000;
10
+ const apiClient = new apiv2_1.Client({
11
+ urlPrefix: (0, api_1.remoteConfigApiOrigin)(),
12
+ apiVersion: "v1",
13
+ });
14
+ const TABLE_HEAD = [
15
+ "Rollout ID",
16
+ "Display Name",
17
+ "Service",
18
+ "Description",
19
+ "State",
20
+ "Start Time",
21
+ "End Time",
22
+ "Last Update Time",
23
+ "ETag",
24
+ ];
25
+ const parseRolloutList = (rollouts) => {
26
+ if (rollouts.length === 0) {
27
+ return "\x1b[33mNo rollouts found.\x1b[0m";
28
+ }
29
+ const table = new Table({ head: TABLE_HEAD, style: { head: ["green"] } });
30
+ for (const rollout of rollouts) {
31
+ table.push([
32
+ rollout.name.split("/").pop() || rollout.name,
33
+ rollout.definition.displayName,
34
+ rollout.definition.service,
35
+ rollout.definition.description,
36
+ rollout.state,
37
+ rollout.startTime,
38
+ rollout.endTime,
39
+ rollout.lastUpdateTime,
40
+ rollout.etag,
41
+ ]);
42
+ }
43
+ return table.toString();
44
+ };
45
+ exports.parseRolloutList = parseRolloutList;
46
+ async function listRollouts(projectId, namespace, listRolloutOptions) {
47
+ try {
48
+ const params = new URLSearchParams();
49
+ if (listRolloutOptions.pageSize) {
50
+ params.set("page_size", listRolloutOptions.pageSize);
51
+ }
52
+ if (listRolloutOptions.filter) {
53
+ params.set("filter", listRolloutOptions.filter);
54
+ }
55
+ if (listRolloutOptions.pageToken) {
56
+ params.set("page_token", listRolloutOptions.pageToken);
57
+ }
58
+ const res = await apiClient.request({
59
+ method: "GET",
60
+ path: `/projects/${projectId}/namespaces/${namespace}/rollouts`,
61
+ queryParams: params,
62
+ timeout: TIMEOUT,
63
+ });
64
+ return res.body;
65
+ }
66
+ catch (err) {
67
+ const error = (0, error_1.getError)(err);
68
+ logger_1.logger.debug(error.message);
69
+ throw new error_1.FirebaseError(`Failed to get Remote Config rollouts for project ${projectId}. Error: ${error.message}`, { original: error });
70
+ }
71
+ }
72
+ exports.listRollouts = listRollouts;
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const metaprogramming_1 = require("../metaprogramming");
4
+ (0, metaprogramming_1.assertImplements)();