firebase-tools 14.15.1 → 14.16.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 (63) hide show
  1. package/lib/commands/dataconnect-sdk-generate.js +28 -24
  2. package/lib/commands/firestore-bulkdelete.js +73 -0
  3. package/lib/commands/firestore-operations-cancel.js +44 -0
  4. package/lib/commands/firestore-operations-describe.js +29 -0
  5. package/lib/commands/firestore-operations-list.js +29 -0
  6. package/lib/commands/firestore-utils.js +15 -0
  7. package/lib/commands/functions-config-export.js +5 -2
  8. package/lib/commands/index.js +5 -0
  9. package/lib/config.js +16 -4
  10. package/lib/dataconnect/ensureApis.js +3 -3
  11. package/lib/deploy/functions/build.js +2 -13
  12. package/lib/deploy/functions/deploy.js +4 -3
  13. package/lib/deploy/functions/prepare.js +10 -7
  14. package/lib/deploy/functions/runtimes/discovery/index.js +1 -1
  15. package/lib/emulator/auth/operations.js +10 -1
  16. package/lib/emulator/commandUtils.js +7 -1
  17. package/lib/emulator/controller.js +15 -31
  18. package/lib/emulator/dataconnectEmulator.js +27 -24
  19. package/lib/emulator/downloadableEmulatorInfo.json +18 -18
  20. package/lib/emulator/functionsEmulator.js +1 -1
  21. package/lib/emulator/hub.js +9 -5
  22. package/lib/extensions/runtimes/common.js +3 -2
  23. package/lib/firestore/api.js +45 -0
  24. package/lib/firestore/pretty-print.js +23 -0
  25. package/lib/functions/env.js +12 -1
  26. package/lib/functions/projectConfig.js +69 -9
  27. package/lib/gcp/cloudfunctions.js +1 -6
  28. package/lib/gcp/cloudfunctionsv2.js +1 -9
  29. package/lib/gcp/cloudsql/cloudsqladmin.js +2 -2
  30. package/lib/init/features/dataconnect/create_app.js +7 -2
  31. package/lib/init/features/dataconnect/index.js +72 -56
  32. package/lib/init/features/dataconnect/sdk.js +23 -11
  33. package/lib/mcp/errors.js +2 -10
  34. package/lib/mcp/index.js +1 -4
  35. package/lib/mcp/prompts/core/deploy.js +1 -1
  36. package/lib/mcp/prompts/crashlytics/connect.js +114 -0
  37. package/lib/mcp/prompts/crashlytics/index.js +2 -3
  38. package/lib/mcp/tools/auth/disable_user.js +1 -1
  39. package/lib/mcp/tools/auth/get_user.js +9 -2
  40. package/lib/mcp/tools/core/index.js +4 -0
  41. package/lib/mcp/tools/core/init.js +11 -2
  42. package/lib/mcp/tools/core/login.js +46 -0
  43. package/lib/mcp/tools/core/logout.js +62 -0
  44. package/lib/mcp/tools/dataconnect/execute.js +71 -0
  45. package/lib/mcp/tools/dataconnect/index.js +3 -13
  46. package/lib/mcp/tools/dataconnect/list_services.js +104 -7
  47. package/lib/mcp/util.js +1 -17
  48. package/lib/serve/functions.js +4 -3
  49. package/lib/track.js +16 -0
  50. package/lib/unzip.js +13 -0
  51. package/lib/utils.js +17 -1
  52. package/package.json +1 -1
  53. package/schema/firebase-config.json +160 -59
  54. package/lib/mcp/prompts/crashlytics/common.js +0 -10
  55. package/lib/mcp/prompts/crashlytics/fix_issue.js +0 -89
  56. package/lib/mcp/prompts/crashlytics/prioritize_issues.js +0 -79
  57. package/lib/mcp/tools/database/set_rules.js +0 -41
  58. package/lib/mcp/tools/dataconnect/execute_graphql.js +0 -48
  59. package/lib/mcp/tools/dataconnect/execute_graphql_read.js +0 -48
  60. package/lib/mcp/tools/dataconnect/execute_mutation.js +0 -62
  61. package/lib/mcp/tools/dataconnect/execute_query.js +0 -62
  62. package/lib/mcp/tools/dataconnect/get_connector.js +0 -31
  63. package/lib/mcp/tools/dataconnect/get_schema.js +0 -31
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logout = void 0;
4
+ const zod_1 = require("zod");
5
+ const clc = require("colorette");
6
+ const tool_1 = require("../../tool");
7
+ const util_1 = require("../../util");
8
+ const auth_1 = require("../../../auth");
9
+ const logger_1 = require("../../../logger");
10
+ exports.logout = (0, tool_1.tool)({
11
+ name: "logout",
12
+ description: "Log the CLI out of Firebase",
13
+ inputSchema: zod_1.z.object({
14
+ email: zod_1.z
15
+ .string()
16
+ .optional()
17
+ .describe("The email of the account to log out. If not provided, all accounts will be logged out."),
18
+ }),
19
+ _meta: {
20
+ requiresAuth: false,
21
+ requiresProject: false,
22
+ },
23
+ }, async ({ email }) => {
24
+ const allAccounts = (0, auth_1.getAllAccounts)();
25
+ if (allAccounts.length === 0) {
26
+ return (0, util_1.toContent)("No need to log out, not logged in");
27
+ }
28
+ const defaultAccount = (0, auth_1.getGlobalDefaultAccount)();
29
+ const additionalAccounts = (0, auth_1.getAdditionalAccounts)();
30
+ const accountsToLogOut = email
31
+ ? allAccounts.filter((a) => a.user.email === email)
32
+ : allAccounts;
33
+ if (email && accountsToLogOut.length === 0) {
34
+ return (0, util_1.toContent)(`No account matches ${email}, can't log out.`);
35
+ }
36
+ const logoutDefault = email === (defaultAccount === null || defaultAccount === void 0 ? void 0 : defaultAccount.user.email);
37
+ let newDefaultAccount = undefined;
38
+ if (logoutDefault && additionalAccounts.length > 0) {
39
+ newDefaultAccount = additionalAccounts[0];
40
+ }
41
+ const logoutMessages = [];
42
+ for (const account of accountsToLogOut) {
43
+ const token = account.tokens.refresh_token;
44
+ if (token) {
45
+ try {
46
+ await (0, auth_1.logout)(token);
47
+ logoutMessages.push(`Logged out from ${clc.bold(account.user.email)}`);
48
+ }
49
+ catch (e) {
50
+ if (e instanceof Error) {
51
+ logger_1.logger.debug(e.message);
52
+ }
53
+ logoutMessages.push(`Could not deauthorize ${account.user.email}, assuming already deauthorized.`);
54
+ }
55
+ }
56
+ }
57
+ if (newDefaultAccount) {
58
+ (0, auth_1.setGlobalDefaultAccount)(newDefaultAccount);
59
+ logoutMessages.push(`Setting default account to "${newDefaultAccount.user.email}"`);
60
+ }
61
+ return (0, util_1.toContent)(logoutMessages.join("\n"));
62
+ });
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.execute = void 0;
4
+ const zod_1 = require("zod");
5
+ const tool_1 = require("../../tool");
6
+ const dataplane = require("../../../dataconnect/dataplaneClient");
7
+ const load_1 = require("../../../dataconnect/load");
8
+ const converter_1 = require("../../util/dataconnect/converter");
9
+ const emulator_1 = require("../../util/dataconnect/emulator");
10
+ exports.execute = (0, tool_1.tool)({
11
+ name: "execute",
12
+ description: "Executes a GraphQL operation against a Data Connect service or its emulator.",
13
+ inputSchema: zod_1.z.object({
14
+ query: zod_1.z.string().describe(`A Firebase Data Connect GraphQL query or mutation to execute.
15
+ You can use the \`dataconnect_generate_operation\` tool to generate a query.
16
+ Example Data Connect schema and example queries can be found in files ending in \`.graphql\` or \`.gql\`.
17
+ `),
18
+ service_id: zod_1.z.string().optional()
19
+ .describe(`Data Connect Service ID to dis-ambulate if there are multiple.
20
+ It's only necessary if there are multiple dataconnect sources in \`firebase.json\`.
21
+ You can find candidate service_id in \`dataconnect.yaml\`
22
+ `),
23
+ variables_json: zod_1.z
24
+ .string()
25
+ .optional()
26
+ .describe("GraphQL variables to pass into the query. MUST be a valid stringified JSON object."),
27
+ auth_token_json: zod_1.z
28
+ .string()
29
+ .optional()
30
+ .describe("Firebase Auth Token JWT to use in this query. MUST be a valid stringified JSON object." +
31
+ 'Importantly, when executing queries with `@auth(level: USER)` or `auth.uid`, a valid Firebase Auth Token JWT with "sub" field is required. ' +
32
+ '"auth.uid" expression in the query evaluates to the value of "sub" field in Firebase Auth token.'),
33
+ use_emulator: zod_1.z
34
+ .boolean()
35
+ .default(false)
36
+ .describe("If true, target the DataConnect emulator. Run `firebase emulators:start` to start it"),
37
+ }),
38
+ annotations: {
39
+ title: "Execute Firebase Data Connect Query",
40
+ },
41
+ _meta: {
42
+ requiresProject: true,
43
+ requiresAuth: true,
44
+ },
45
+ }, async ({ query, service_id, variables_json: unparsedVariables, use_emulator, auth_token_json: unparsedAuthToken, }, { projectId, config, host }) => {
46
+ const serviceInfo = await (0, load_1.pickService)(projectId, config, service_id || undefined);
47
+ let apiClient;
48
+ if (use_emulator) {
49
+ apiClient = await (0, emulator_1.getDataConnectEmulatorClient)(host);
50
+ }
51
+ else {
52
+ apiClient = dataplane.dataconnectDataplaneClient();
53
+ }
54
+ let executeGraphQL = dataplane.executeGraphQL;
55
+ if (query.startsWith("query")) {
56
+ executeGraphQL = dataplane.executeGraphQLRead;
57
+ }
58
+ const response = await executeGraphQL(apiClient, serviceInfo.serviceName, {
59
+ name: "",
60
+ query,
61
+ variables: (0, converter_1.parseVariables)(unparsedVariables),
62
+ extensions: {
63
+ impersonate: unparsedAuthToken
64
+ ? {
65
+ authClaims: (0, converter_1.parseVariables)(unparsedAuthToken),
66
+ }
67
+ : undefined,
68
+ },
69
+ });
70
+ return (0, converter_1.graphqlResponseToToolResponse)(response.body);
71
+ });
@@ -4,22 +4,12 @@ exports.dataconnectTools = void 0;
4
4
  const generate_operation_1 = require("./generate_operation");
5
5
  const generate_schema_1 = require("./generate_schema");
6
6
  const list_services_1 = require("./list_services");
7
- const get_schema_1 = require("./get_schema");
8
- const get_connector_1 = require("./get_connector");
9
- const execute_graphql_1 = require("./execute_graphql");
10
- const execute_graphql_read_1 = require("./execute_graphql_read");
11
- const execute_query_1 = require("./execute_query");
12
- const execute_mutation_1 = require("./execute_mutation");
13
7
  const compile_1 = require("./compile");
8
+ const execute_1 = require("./execute");
14
9
  exports.dataconnectTools = [
15
10
  compile_1.compile,
16
- list_services_1.list_services,
17
11
  generate_schema_1.generate_schema,
18
12
  generate_operation_1.generate_operation,
19
- get_schema_1.get_schema,
20
- get_connector_1.get_connectors,
21
- execute_graphql_1.execute_graphql,
22
- execute_graphql_read_1.execute_graphql_read,
23
- execute_mutation_1.execute_mutation,
24
- execute_query_1.execute_query,
13
+ list_services_1.list_services,
14
+ execute_1.execute,
25
15
  ];
@@ -2,22 +2,119 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.list_services = void 0;
4
4
  const zod_1 = require("zod");
5
+ const path = require("path");
5
6
  const tool_1 = require("../../tool");
6
7
  const util_1 = require("../../util");
7
8
  const client = require("../../../dataconnect/client");
9
+ const load_1 = require("../../../dataconnect/load");
10
+ const js_yaml_1 = require("js-yaml");
11
+ const logger_1 = require("../../../logger");
8
12
  exports.list_services = (0, tool_1.tool)({
9
13
  name: "list_services",
10
- description: "List the Firebase Data Connect services available in the current project.",
14
+ description: "List existing local and backend Firebase Data Connect services",
11
15
  inputSchema: zod_1.z.object({}),
12
16
  annotations: {
13
- title: "List Data Connect Services",
17
+ title: "List existing Firebase Data Connect services",
14
18
  readOnlyHint: true,
15
19
  },
16
20
  _meta: {
17
- requiresProject: true,
18
- requiresAuth: true,
21
+ requiresProject: false,
22
+ requiresAuth: false,
19
23
  },
20
- }, async (_, { projectId }) => {
21
- const services = await client.listAllServices(projectId);
22
- return (0, util_1.toContent)(services, { format: "yaml" });
24
+ }, async (_, { projectId, config }) => {
25
+ const localServiceInfos = await (0, load_1.loadAll)(projectId, config);
26
+ const serviceInfos = new Map();
27
+ for (const l of localServiceInfos) {
28
+ serviceInfos.set(`locations/${l.dataConnectYaml.location}/services/${l.dataConnectYaml.serviceId}`, { local: l });
29
+ }
30
+ if (projectId) {
31
+ try {
32
+ const [services, schemas, connectors] = await Promise.all([
33
+ client.listAllServices(projectId),
34
+ client.listSchemas(`projects/${projectId}/locations/-/services/-`),
35
+ client.listConnectors(`projects/${projectId}/locations/-/services/-`),
36
+ ]);
37
+ console.log(services, schemas, connectors);
38
+ for (const s of services) {
39
+ const k = s.name.split("/").slice(2, 6).join("/");
40
+ const st = serviceInfos.get(k) || {};
41
+ st.deployed = st.deployed || {};
42
+ st.deployed.service = s;
43
+ serviceInfos.set(k, st);
44
+ }
45
+ for (const s of schemas) {
46
+ const k = s.name.split("/").slice(2, 6).join("/");
47
+ const st = serviceInfos.get(k) || {};
48
+ st.deployed = st.deployed || {};
49
+ st.deployed.schemas = st.deployed.schemas || [];
50
+ st.deployed.schemas.push(s);
51
+ serviceInfos.set(k, st);
52
+ }
53
+ for (const s of connectors) {
54
+ const k = s.name.split("/").slice(2, 6).join("/");
55
+ const st = serviceInfos.get(k) || {};
56
+ st.deployed = st.deployed || {};
57
+ st.deployed.connectors = st.deployed.connectors || [];
58
+ st.deployed.connectors.push(s);
59
+ serviceInfos.set(k, st);
60
+ }
61
+ }
62
+ catch (e) {
63
+ logger_1.logger.debug("cannot fetch dataconnect resources in the backend", e);
64
+ }
65
+ }
66
+ const localServices = Array.from(serviceInfos.values()).filter((s) => s.local);
67
+ const remoteOnlyServices = Array.from(serviceInfos.values()).filter((s) => !s.local);
68
+ const output = [];
69
+ function includeDeployedServiceInfo(deployed) {
70
+ var _a, _b;
71
+ if ((_a = deployed.schemas) === null || _a === void 0 ? void 0 : _a.length) {
72
+ output.push(`### Schemas`);
73
+ for (const s of deployed.schemas) {
74
+ clearCCFEFields(s);
75
+ output.push((0, js_yaml_1.dump)(s));
76
+ }
77
+ }
78
+ if ((_b = deployed.connectors) === null || _b === void 0 ? void 0 : _b.length) {
79
+ output.push(`### Connectors`);
80
+ for (const c of deployed.connectors) {
81
+ clearCCFEFields(c);
82
+ output.push((0, js_yaml_1.dump)(c));
83
+ }
84
+ }
85
+ }
86
+ if (localServices.length) {
87
+ output.push(`# Local Data Connect Sources`);
88
+ for (const s of localServices) {
89
+ const local = s.local;
90
+ output.push((0, js_yaml_1.dump)(local.dataConnectYaml));
91
+ const schemaDir = path.join(local.sourceDirectory, local.dataConnectYaml.schema.source);
92
+ output.push(`You can find all of schema sources under ${schemaDir}/`);
93
+ if (s.deployed) {
94
+ output.push(`It's already deployed in the backend:\n`);
95
+ includeDeployedServiceInfo(s.deployed);
96
+ }
97
+ }
98
+ }
99
+ if (remoteOnlyServices.length) {
100
+ output.push(`# Data Connect Services in project ${projectId}`);
101
+ for (const s of remoteOnlyServices) {
102
+ if (s.deployed) {
103
+ includeDeployedServiceInfo(s.deployed);
104
+ }
105
+ }
106
+ }
107
+ output.push(`\n# What's next?`);
108
+ if (!localServices.length) {
109
+ output.push(`- There is no local Data Connect service in the local workspace. Consider use the \`firebase_init\` MCP tool to setup one.`);
110
+ }
111
+ output.push(`- You can use the \`dataconnect_compile\` tool to compile all local Data Connect schemas and query sources.`);
112
+ output.push(`- You run \`firebase deploy\` in command line to deploy the Data Connect schemas, connector and perform SQL migrations.`);
113
+ return (0, util_1.toContent)(output.join("\n"));
23
114
  });
115
+ function clearCCFEFields(r) {
116
+ const fieldsToClear = ["updateTime", "uid", "etag"];
117
+ for (const k of fieldsToClear) {
118
+ delete r[k];
119
+ }
120
+ }
package/lib/mcp/util.js CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.cleanSchema = exports.checkFeatureActive = exports.commandExistsSync = exports.mcpError = exports.toContent = void 0;
4
- const child_process_1 = require("child_process");
3
+ exports.cleanSchema = exports.checkFeatureActive = exports.mcpError = exports.toContent = void 0;
5
4
  const js_yaml_1 = require("js-yaml");
6
- const os_1 = require("os");
7
5
  const api_1 = require("../api");
8
6
  const ensureApiEnabled_1 = require("../ensureApiEnabled");
9
7
  const timeout_1 = require("../timeout");
@@ -41,20 +39,6 @@ function mcpError(message, code) {
41
39
  };
42
40
  }
43
41
  exports.mcpError = mcpError;
44
- function commandExistsSync(command) {
45
- try {
46
- const isWindows = (0, os_1.platform)() === "win32";
47
- const commandToCheck = isWindows
48
- ? `where "${command}" > nul 2> nul`
49
- : `which "${command}" > /dev/null 2> /dev/null`;
50
- (0, child_process_1.execSync)(commandToCheck);
51
- return true;
52
- }
53
- catch (error) {
54
- return false;
55
- }
56
- }
57
- exports.commandExistsSync = commandExistsSync;
58
42
  const SERVER_FEATURE_APIS = {
59
43
  core: "",
60
44
  firestore: (0, api_1.firestoreOrigin)(),
@@ -21,11 +21,12 @@ class FunctionsServer {
21
21
  const config = projectConfig.normalizeAndValidate(options.config.src.functions);
22
22
  const backends = [];
23
23
  for (const cfg of config) {
24
- const functionsDir = path.join(options.config.projectDir, cfg.source);
24
+ const localCfg = projectConfig.requireLocal(cfg, "Remote sources are not supported in the Functions emulator.");
25
+ const functionsDir = path.join(options.config.projectDir, localCfg.source);
25
26
  backends.push({
26
27
  functionsDir,
27
- codebase: cfg.codebase,
28
- runtime: cfg.runtime,
28
+ codebase: localCfg.codebase,
29
+ runtime: localCfg.runtime,
29
30
  env: {},
30
31
  secretEnv: [],
31
32
  });
package/lib/track.js CHANGED
@@ -8,6 +8,19 @@ const configstore_1 = require("./configstore");
8
8
  const logger_1 = require("./logger");
9
9
  const env_1 = require("./env");
10
10
  const pkg = require("../package.json");
11
+ function detectAIAgent() {
12
+ if (process.env.CODEX_SANDBOX)
13
+ return "codex_cli";
14
+ if (process.env.CLAUDECODE)
15
+ return "claude_code";
16
+ if (process.env.GEMINI_CLI)
17
+ return "gemini_cli";
18
+ if (process.env.CURSOR_AGENT)
19
+ return "cursor";
20
+ if (process.env.OPENCODE)
21
+ return "open_code";
22
+ return "unknown";
23
+ }
11
24
  exports.GA4_PROPERTIES = {
12
25
  cli: {
13
26
  measurementId: process.env.FIREBASE_CLI_GA4_MEASUREMENT_ID || "G-PDN0QWHQJR",
@@ -45,6 +58,9 @@ const GA4_USER_PROPS = {
45
58
  is_firebase_studio: {
46
59
  value: (0, env_1.isFirebaseStudio)().toString(),
47
60
  },
61
+ ai_agent: {
62
+ value: detectAIAgent(),
63
+ },
48
64
  };
49
65
  async function trackGA4(eventName, params, duration = 1) {
50
66
  const session = cliSession();
package/lib/unzip.js CHANGED
@@ -57,6 +57,9 @@ const extractEntriesFromBuffer = async (data, outputDir) => {
57
57
  logger_1.logger.debug(`[unzip] Entry: ${entry.fileName} (compressed_size=${entry.compressedSize} bytes, uncompressed_size=${entry.uncompressedSize} bytes)`);
58
58
  entry.fileName = entry.fileName.replace(/\//g, path.sep);
59
59
  const outputFilePath = path.normalize(path.join(outputDir, entry.fileName));
60
+ if (!isChildDir(outputDir, outputFilePath)) {
61
+ throw new error_1.FirebaseError(`ZIP contained an entry for ${outputFilePath}, a path outside of ${outputDir}`);
62
+ }
60
63
  logger_1.logger.debug(`[unzip] Processing entry: ${entry.fileName}`);
61
64
  if (entry.fileName.endsWith(path.sep)) {
62
65
  logger_1.logger.debug(`[unzip] mkdir: ${outputFilePath}`);
@@ -82,6 +85,16 @@ const extractEntriesFromBuffer = async (data, outputDir) => {
82
85
  position += entry.headerSize + entry.compressedSize + dataDescriptorSize;
83
86
  }
84
87
  };
88
+ function isChildDir(parentDir, potentialChild) {
89
+ try {
90
+ const resolvedParent = path.resolve(parentDir);
91
+ const resolvedChild = path.resolve(potentialChild);
92
+ return resolvedChild.startsWith(resolvedParent) && resolvedChild !== resolvedParent;
93
+ }
94
+ catch (error) {
95
+ return false;
96
+ }
97
+ }
85
98
  const unzip = async (inputPath, outputDir) => {
86
99
  const data = await fs.promises.readFile(inputPath);
87
100
  await extractEntriesFromBuffer(data, outputDir);
package/lib/utils.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.openInBrowserPopup = exports.openInBrowser = exports.connectableHostname = exports.randomInt = exports.debounce = exports.last = exports.cloneDeep = exports.groupBy = exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.sleep = exports.promiseWithSpinner = exports.tryParse = exports.promiseProps = exports.withTimeout = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledError = exports.logLabeledWarning = exports.logWarningToStderr = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.setVSCodeEnvVars = exports.getInheritedOption = exports.consoleUrl = exports.vscodeEnvVars = exports.envOverrides = exports.IS_WINDOWS = void 0;
4
- exports.newUniqueId = exports.deepEqual = exports.promptForDirectory = exports.updateOrCreateGitignore = exports.readSecretValue = exports.generatePassword = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = exports.getHostnameFromUrl = void 0;
4
+ exports.commandExistsSync = exports.newUniqueId = exports.deepEqual = exports.promptForDirectory = exports.updateOrCreateGitignore = exports.readSecretValue = exports.generatePassword = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = exports.getHostnameFromUrl = void 0;
5
5
  const fs = require("fs-extra");
6
6
  const tty = require("tty");
7
7
  const path = require("node:path");
@@ -24,6 +24,8 @@ const prompt_1 = require("./prompt");
24
24
  const templates_1 = require("./templates");
25
25
  const vsCodeUtils_1 = require("./vsCodeUtils");
26
26
  const fsutils_1 = require("./fsutils");
27
+ const node_os_1 = require("node:os");
28
+ const node_child_process_1 = require("node:child_process");
27
29
  exports.IS_WINDOWS = process.platform === "win32";
28
30
  const SUCCESS_CHAR = exports.IS_WINDOWS ? "+" : "✔";
29
31
  const WARNING_CHAR = exports.IS_WINDOWS ? "!" : "⚠";
@@ -657,3 +659,17 @@ function newUniqueId(recommended, existingIDs) {
657
659
  return id;
658
660
  }
659
661
  exports.newUniqueId = newUniqueId;
662
+ function commandExistsSync(command) {
663
+ try {
664
+ const isWindows = (0, node_os_1.platform)() === "win32";
665
+ const commandToCheck = isWindows
666
+ ? `where "${command}" > nul 2> nul`
667
+ : `which "${command}" > /dev/null 2> /dev/null`;
668
+ (0, node_child_process_1.execSync)(commandToCheck);
669
+ return true;
670
+ }
671
+ catch (error) {
672
+ return false;
673
+ }
674
+ }
675
+ exports.commandExistsSync = commandExistsSync;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "14.15.1",
3
+ "version": "14.16.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {