firebase-tools 14.2.2 → 14.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/lib/api.js +4 -2
  2. package/lib/apphosting/backend.js +12 -23
  3. package/lib/apphosting/config.js +9 -20
  4. package/lib/apphosting/githubConnections.js +65 -99
  5. package/lib/apphosting/repo.js +22 -36
  6. package/lib/apphosting/secrets/dialogs.js +2 -3
  7. package/lib/apphosting/utils.js +1 -3
  8. package/lib/auth.js +1 -4
  9. package/lib/commands/apphosting-backends-delete.js +5 -5
  10. package/lib/commands/apphosting-secrets-set.js +2 -6
  11. package/lib/commands/apps-create.js +2 -3
  12. package/lib/commands/apps-sdkconfig.js +2 -7
  13. package/lib/commands/database-import.js +4 -6
  14. package/lib/commands/database-remove.js +4 -6
  15. package/lib/commands/database-set.js +6 -6
  16. package/lib/commands/database-update.js +5 -5
  17. package/lib/commands/dataconnect-sql-shell.js +4 -6
  18. package/lib/commands/ext-dev-deprecate.js +1 -0
  19. package/lib/commands/ext-dev-init.js +3 -12
  20. package/lib/commands/ext-dev-register.js +2 -4
  21. package/lib/commands/ext-dev-undeprecate.js +1 -2
  22. package/lib/commands/ext-dev-usage.js +1 -3
  23. package/lib/commands/ext-export.js +1 -2
  24. package/lib/commands/ext-install.js +1 -0
  25. package/lib/commands/ext-sdk-install.js +1 -0
  26. package/lib/commands/ext-update.js +1 -0
  27. package/lib/commands/firestore-backups-delete.js +1 -2
  28. package/lib/commands/firestore-backups-schedules-delete.js +1 -2
  29. package/lib/commands/firestore-databases-delete.js +1 -5
  30. package/lib/commands/firestore-delete.js +6 -6
  31. package/lib/commands/functions-artifacts-setpolicy.js +12 -2
  32. package/lib/commands/functions-config-export.js +5 -9
  33. package/lib/commands/functions-delete.js +5 -5
  34. package/lib/commands/functions-deletegcfartifacts.js +4 -4
  35. package/lib/commands/functions-secrets-destroy.js +8 -10
  36. package/lib/commands/functions-secrets-prune.js +12 -13
  37. package/lib/commands/functions-secrets-set.js +11 -15
  38. package/lib/commands/hosting-channel-create.js +1 -2
  39. package/lib/commands/hosting-channel-delete.js +4 -4
  40. package/lib/commands/hosting-channel-open.js +2 -3
  41. package/lib/commands/hosting-disable.js +5 -5
  42. package/lib/commands/hosting-sites-delete.js +4 -4
  43. package/lib/commands/init.js +41 -58
  44. package/lib/commands/login.js +1 -5
  45. package/lib/commands/logout.js +2 -3
  46. package/lib/commands/open.js +1 -2
  47. package/lib/commands/projects-create.js +1 -2
  48. package/lib/commands/remoteconfig-rollback.js +5 -5
  49. package/lib/commands/use.js +110 -110
  50. package/lib/config.js +1 -2
  51. package/lib/dataconnect/build.js +2 -4
  52. package/lib/dataconnect/client.js +20 -1
  53. package/lib/dataconnect/dataplaneClient.js +16 -1
  54. package/lib/dataconnect/schemaMigration.js +1 -6
  55. package/lib/dataconnect/types.js +5 -1
  56. package/lib/deploy/functions/params.js +10 -17
  57. package/lib/deploy/functions/prompts.js +6 -28
  58. package/lib/emulator/commandUtils.js +4 -13
  59. package/lib/emulator/controller.js +1 -2
  60. package/lib/emulator/downloadableEmulators.js +12 -12
  61. package/lib/emulator/initEmulators.js +9 -20
  62. package/lib/ensureApiEnabled.js +11 -1
  63. package/lib/extensions/askUserForEventsConfig.js +6 -15
  64. package/lib/extensions/askUserForParam.js +23 -52
  65. package/lib/extensions/checkProjectBilling.js +3 -9
  66. package/lib/extensions/diagnose.js +3 -6
  67. package/lib/extensions/extensionsHelper.js +17 -27
  68. package/lib/extensions/manifest.js +1 -2
  69. package/lib/extensions/tos.js +10 -2
  70. package/lib/extensions/utils.js +1 -10
  71. package/lib/frameworks/index.js +1 -2
  72. package/lib/frameworks/next/index.js +7 -4
  73. package/lib/frameworks/vite/index.js +1 -2
  74. package/lib/functions/secrets.js +14 -15
  75. package/lib/gcp/auth.js +29 -1
  76. package/lib/gcp/storage.js +19 -1
  77. package/lib/gif/fdcExperience.js +45 -0
  78. package/lib/hosting/interactive.js +2 -3
  79. package/lib/init/features/account.js +2 -4
  80. package/lib/init/features/database.js +11 -20
  81. package/lib/init/features/dataconnect/index.js +7 -14
  82. package/lib/init/features/dataconnect/sdk.js +15 -22
  83. package/lib/init/features/emulators.js +19 -41
  84. package/lib/init/features/firestore/index.js +2 -6
  85. package/lib/init/features/firestore/indexes.js +18 -31
  86. package/lib/init/features/firestore/rules.js +18 -31
  87. package/lib/init/features/functions/index.js +9 -16
  88. package/lib/init/features/functions/javascript.js +16 -30
  89. package/lib/init/features/functions/npm-dependencies.js +4 -8
  90. package/lib/init/features/functions/python.js +1 -3
  91. package/lib/init/features/functions/typescript.js +24 -43
  92. package/lib/init/features/genkit/index.js +23 -38
  93. package/lib/init/features/hosting/github.js +20 -51
  94. package/lib/init/features/hosting/index.js +36 -57
  95. package/lib/init/features/project.js +6 -16
  96. package/lib/init/features/remoteconfig.js +2 -8
  97. package/lib/init/features/storage.js +1 -3
  98. package/lib/management/apps.js +19 -44
  99. package/lib/management/projects.js +17 -28
  100. package/lib/mcp/index.js +13 -11
  101. package/lib/mcp/tools/auth/{disable_auth_user.js → disable_user.js} +3 -3
  102. package/lib/mcp/tools/auth/{get_auth_user.js → get_user.js} +4 -4
  103. package/lib/mcp/tools/auth/index.js +5 -4
  104. package/lib/mcp/tools/auth/{set_auth_claims.js → set_claims.js} +3 -3
  105. package/lib/mcp/tools/auth/set_sms_region_policy.js +36 -0
  106. package/lib/mcp/tools/core/consult_assistant.js +27 -0
  107. package/lib/mcp/tools/core/index.js +5 -3
  108. package/lib/mcp/tools/dataconnect/converter.js +50 -0
  109. package/lib/mcp/tools/dataconnect/execute_graphql.js +32 -0
  110. package/lib/mcp/tools/dataconnect/execute_graphql_read.js +32 -0
  111. package/lib/mcp/tools/dataconnect/execute_mutation.js +50 -0
  112. package/lib/mcp/tools/dataconnect/execute_query.js +50 -0
  113. package/lib/mcp/tools/dataconnect/generate_operation.js +33 -0
  114. package/lib/mcp/tools/dataconnect/generate_schema.js +25 -0
  115. package/lib/mcp/tools/dataconnect/get_connector.js +31 -0
  116. package/lib/mcp/tools/dataconnect/get_schema.js +31 -0
  117. package/lib/mcp/tools/dataconnect/index.js +20 -2
  118. package/lib/mcp/tools/dataconnect/{list_dataconnect_services.js → list_services.js} +4 -4
  119. package/lib/mcp/tools/{core/get_firebase_directory.js → directory/get_project_directory.js} +5 -5
  120. package/lib/mcp/tools/directory/index.js +6 -0
  121. package/lib/mcp/tools/{core/set_firebase_directory.js → directory/set_project_directory.js} +3 -3
  122. package/lib/mcp/tools/firestore/get_documents.js +2 -2
  123. package/lib/mcp/tools/firestore/{get_firestore_rules.js → get_rules.js} +6 -6
  124. package/lib/mcp/tools/firestore/index.js +2 -2
  125. package/lib/mcp/tools/index.js +26 -8
  126. package/lib/mcp/tools/storage/get_download_url.js +31 -0
  127. package/lib/mcp/tools/storage/get_rules.js +26 -0
  128. package/lib/mcp/tools/storage/index.js +6 -0
  129. package/lib/mcp/types.js +1 -1
  130. package/lib/prompt.js +78 -65
  131. package/lib/requireTosAcceptance.js +4 -0
  132. package/lib/rulesDeploy.js +10 -15
  133. package/lib/track.js +1 -34
  134. package/lib/utils.js +27 -5
  135. package/package.json +2 -3
  136. package/lib/mcp/tools/project/index.js +0 -7
  137. /package/lib/mcp/tools/{project → core}/get_project.js +0 -0
  138. /package/lib/mcp/tools/{project → core}/get_sdk_config.js +0 -0
  139. /package/lib/mcp/tools/{project → core}/list_apps.js +0 -0
package/lib/api.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.cloudRunApiOrigin = exports.hostingApiOrigin = exports.firebaseStorageOrigin = exports.storageOrigin = exports.runtimeconfigOrigin = exports.rulesOrigin = exports.resourceManagerOrigin = exports.remoteConfigApiOrigin = exports.rtdbMetadataOrigin = exports.rtdbManagementOrigin = exports.realtimeOrigin = exports.extensionsTOSOrigin = exports.extensionsPublisherOrigin = exports.extensionsOrigin = exports.iamOrigin = exports.identityOrigin = exports.hostingOrigin = exports.googleOrigin = exports.pubsubOrigin = exports.cloudTasksOrigin = exports.cloudschedulerOrigin = exports.cloudbuildOrigin = exports.functionsDefaultRegion = exports.runOrigin = exports.functionsV2Origin = exports.functionsOrigin = exports.firestoreOrigin = exports.firestoreOriginOrEmulator = exports.firedataOrigin = exports.firebaseExtensionsRegistryOrigin = exports.firebaseApiOrigin = exports.eventarcOrigin = exports.dynamicLinksKey = exports.dynamicLinksOrigin = exports.consoleOrigin = exports.authOrigin = exports.apphostingGitHubAppInstallationURL = exports.apphostingP4SADomain = exports.apphostingOrigin = exports.appDistributionOrigin = exports.artifactRegistryDomain = exports.developerConnectP4SADomain = exports.developerConnectOrigin = exports.containerRegistryDomain = exports.cloudMonitoringOrigin = exports.cloudloggingOrigin = exports.cloudbillingOrigin = exports.clientSecret = exports.clientId = exports.authProxyOrigin = void 0;
4
- exports.setScopes = exports.getScopes = exports.vertexAIOrigin = exports.cloudSQLAdminOrigin = exports.dataConnectLocalConnString = exports.dataconnectP4SADomain = exports.dataconnectOrigin = exports.githubClientSecret = exports.githubClientId = exports.computeOrigin = exports.secretManagerOrigin = exports.githubApiOrigin = exports.githubOrigin = exports.serviceUsageOrigin = void 0;
3
+ 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.cloudCompanionOrigin = exports.cloudbuildOrigin = exports.functionsDefaultRegion = exports.runOrigin = exports.functionsV2Origin = exports.functionsOrigin = exports.firestoreOrigin = exports.firestoreOriginOrEmulator = exports.firedataOrigin = exports.firebaseExtensionsRegistryOrigin = exports.firebaseApiOrigin = exports.eventarcOrigin = exports.dynamicLinksKey = exports.dynamicLinksOrigin = exports.consoleOrigin = exports.authOrigin = exports.apphostingGitHubAppInstallationURL = exports.apphostingP4SADomain = exports.apphostingOrigin = exports.appDistributionOrigin = exports.artifactRegistryDomain = exports.developerConnectP4SADomain = exports.developerConnectOrigin = exports.containerRegistryDomain = exports.cloudMonitoringOrigin = exports.cloudloggingOrigin = exports.cloudbillingOrigin = exports.clientSecret = exports.clientId = exports.authProxyOrigin = void 0;
4
+ exports.setScopes = exports.getScopes = exports.vertexAIOrigin = exports.cloudSQLAdminOrigin = exports.dataConnectLocalConnString = exports.dataconnectP4SADomain = exports.dataconnectOrigin = exports.githubClientSecret = exports.githubClientId = exports.computeOrigin = exports.secretManagerOrigin = exports.githubApiOrigin = exports.githubOrigin = exports.serviceUsageOrigin = exports.cloudRunApiOrigin = void 0;
5
5
  const constants_1 = require("./emulator/constants");
6
6
  const logger_1 = require("./logger");
7
7
  const scopes = require("./scopes");
@@ -70,6 +70,8 @@ const functionsDefaultRegion = () => utils.envOverride("FIREBASE_FUNCTIONS_DEFAU
70
70
  exports.functionsDefaultRegion = functionsDefaultRegion;
71
71
  const cloudbuildOrigin = () => utils.envOverride("FIREBASE_CLOUDBUILD_URL", "https://cloudbuild.googleapis.com");
72
72
  exports.cloudbuildOrigin = cloudbuildOrigin;
73
+ const cloudCompanionOrigin = () => utils.envOverride("CLOUD_COMPANION_URL", "https://cloudaicompanion.googleapis.com");
74
+ exports.cloudCompanionOrigin = cloudCompanionOrigin;
73
75
  const cloudschedulerOrigin = () => utils.envOverride("FIREBASE_CLOUDSCHEDULER_URL", "https://cloudscheduler.googleapis.com");
74
76
  exports.cloudschedulerOrigin = cloudschedulerOrigin;
75
77
  const cloudTasksOrigin = () => utils.envOverride("FIREBASE_CLOUD_TAKS_URL", "https://cloudtasks.googleapis.com");
@@ -62,21 +62,14 @@ async function doSetup(projectId, webAppName, serviceAccount) {
62
62
  await ensureAppHostingComputeServiceAccount(projectId, serviceAccount);
63
63
  const location = await promptLocation(projectId, "Select a primary region to host your backend:\n");
64
64
  const gitRepositoryLink = await githubConnections.linkGitHubRepository(projectId, location);
65
- const rootDir = await (0, prompt_1.promptOnce)({
66
- name: "rootDir",
67
- type: "input",
65
+ const rootDir = await (0, prompt_1.input)({
68
66
  default: "/",
69
67
  message: "Specify your app's root directory relative to your repository",
70
68
  });
71
69
  const branch = await githubConnections.promptGitHubBranch(gitRepositoryLink);
72
70
  (0, utils_1.logSuccess)(`Repo linked successfully!\n`);
73
71
  (0, utils_1.logBullet)(`${clc.yellow("===")} Set up your backend`);
74
- const backendId = await promptNewBackendId(projectId, location, {
75
- name: "backendId",
76
- type: "input",
77
- default: "my-web-app",
78
- message: "Provide a name for your backend [1-30 characters]",
79
- });
72
+ const backendId = await promptNewBackendId(projectId, location);
80
73
  (0, utils_1.logSuccess)(`Name set to ${backendId}\n`);
81
74
  const webApp = await app_1.webApps.getOrCreateWebApp(projectId, webAppName, backendId);
82
75
  if (!webApp) {
@@ -86,9 +79,7 @@ async function doSetup(projectId, webAppName, serviceAccount) {
86
79
  const backend = await createBackend(projectId, location, backendId, gitRepositoryLink, serviceAccount, webApp === null || webApp === void 0 ? void 0 : webApp.id, rootDir);
87
80
  createBackendSpinner.succeed(`Successfully created backend!\n\t${backend.name}\n`);
88
81
  await setDefaultTrafficPolicy(projectId, location, backendId, branch);
89
- const confirmRollout = await (0, prompt_1.promptOnce)({
90
- type: "confirm",
91
- name: "rollout",
82
+ const confirmRollout = await (0, prompt_1.confirm)({
92
83
  default: true,
93
84
  message: "Do you want to deploy now?",
94
85
  });
@@ -158,9 +149,13 @@ async function ensureAppHostingComputeServiceAccount(projectId, serviceAccount)
158
149
  }
159
150
  }
160
151
  exports.ensureAppHostingComputeServiceAccount = ensureAppHostingComputeServiceAccount;
161
- async function promptNewBackendId(projectId, location, prompt) {
152
+ async function promptNewBackendId(projectId, location) {
162
153
  while (true) {
163
- const backendId = await (0, prompt_1.promptOnce)(prompt);
154
+ const backendId = await (0, prompt_1.input)({
155
+ default: "my-web-app",
156
+ message: "Provide a name for your backend [1-30 characters]",
157
+ validate: (s) => s.length >= 1 && s.length <= 30,
158
+ });
164
159
  try {
165
160
  await apphosting.getBackend(projectId, location, backendId);
166
161
  }
@@ -230,9 +225,7 @@ async function promptLocation(projectId, prompt = "Please select a location:") {
230
225
  if (allowedLocations.length === 1) {
231
226
  return allowedLocations[0];
232
227
  }
233
- const location = (await (0, prompt_1.promptOnce)({
234
- name: "location",
235
- type: "list",
228
+ const location = (await (0, prompt_1.select)({
236
229
  default: constants_1.DEFAULT_LOCATION,
237
230
  message: prompt,
238
231
  choices: allowedLocations,
@@ -273,9 +266,7 @@ async function chooseBackends(projectId, backendId, chooseBackendPrompt, force)
273
266
  const { location, id } = apphosting.parseBackendName(backend.name);
274
267
  backendsByDisplay.set(`${id}(${location})`, backend);
275
268
  });
276
- const chosenBackendDisplays = await (0, prompt_1.promptOnce)({
277
- name: "backend",
278
- type: "checkbox",
269
+ const chosenBackendDisplays = await (0, prompt_1.checkbox)({
279
270
  message: chooseBackendPrompt,
280
271
  choices: Array.from(backendsByDisplay.keys(), (name) => {
281
272
  return {
@@ -313,9 +304,7 @@ async function getBackendForAmbiguousLocation(projectId, backendId, locationDisa
313
304
  }
314
305
  const backendsByLocation = new Map();
315
306
  backends.forEach((backend) => backendsByLocation.set(apphosting.parseBackendName(backend.name).location, backend));
316
- const location = await (0, prompt_1.promptOnce)({
317
- name: "location",
318
- type: "list",
307
+ const location = await (0, prompt_1.select)({
319
308
  message: locationDisambugationPrompt,
320
309
  choices: [...backendsByLocation.keys()],
321
310
  });
@@ -113,7 +113,7 @@ async function maybeAddSecretToYaml(secretName, fileName = exports.APPHOSTING_BA
113
113
  return;
114
114
  }
115
115
  if (!path) {
116
- path = await prompt.promptOnce({
116
+ path = await prompt.input({
117
117
  message: `It looks like you don't have an ${fileName} yet. Where would you like to store it?`,
118
118
  default: process.cwd(),
119
119
  });
@@ -127,8 +127,8 @@ async function maybeAddSecretToYaml(secretName, fileName = exports.APPHOSTING_BA
127
127
  dynamicDispatch.store(path, projectYaml);
128
128
  }
129
129
  exports.maybeAddSecretToYaml = maybeAddSecretToYaml;
130
- async function maybeGenerateEmulatorYaml(projectId, repoRoot) {
131
- const basePath = dynamicDispatch.discoverBackendRoot(repoRoot) || repoRoot;
130
+ async function maybeGenerateEmulatorYaml(projectId, backendRoot) {
131
+ const basePath = dynamicDispatch.discoverBackendRoot(backendRoot) || backendRoot;
132
132
  if (fs.fileExistsSync((0, path_1.join)(basePath, exports.APPHOSTING_EMULATORS_YAML_FILE))) {
133
133
  logger_1.logger.debug("apphosting.emulator.yaml already exists, skipping generation and secrets access prompt");
134
134
  return null;
@@ -174,12 +174,9 @@ async function overrideChosenEnv(projectId, env) {
174
174
  if (!names.length) {
175
175
  return {};
176
176
  }
177
- const toOverwrite = await prompt.promptOnce({
178
- type: "checkbox",
177
+ const toOverwrite = await prompt.checkbox({
179
178
  message: "Which environment variables would you like to override?",
180
- choices: names.map((name) => {
181
- return { name };
182
- }),
179
+ choices: names,
183
180
  });
184
181
  if (!projectId && toOverwrite.some((name) => "secret" in env[name])) {
185
182
  throw new error_1.FirebaseError(`Need a project ID to overwrite a secret. Either use ${clc.bold("firebase use")} or pass the ${clc.bold("--project")} flag`);
@@ -187,24 +184,19 @@ async function overrideChosenEnv(projectId, env) {
187
184
  const newEnv = {};
188
185
  for (const name of toOverwrite) {
189
186
  if ("value" in env[name]) {
190
- const newValue = await prompt.promptOnce({
191
- type: "input",
192
- message: `What new value would you like for plaintext ${name}?`,
193
- });
187
+ const newValue = await prompt.input(`What new value would you like for plaintext ${name}?`);
194
188
  newEnv[name] = { variable: name, value: newValue };
195
189
  continue;
196
190
  }
197
191
  let secretRef;
198
192
  let action = "pick-new";
199
193
  while (action === "pick-new") {
200
- secretRef = await prompt.promptOnce({
201
- type: "input",
194
+ secretRef = await prompt.input({
202
195
  message: `What would you like to name the secret reference for ${name}?`,
203
196
  default: suggestedTestKeyName(name),
204
197
  });
205
198
  if (await csm.secretExists(projectId, secretRef)) {
206
- action = await prompt.promptOnce({
207
- type: "list",
199
+ action = await prompt.select({
208
200
  message: "This secret reference already exists, would you like to reuse it or create a new one?",
209
201
  choices: [
210
202
  { name: "Reuse it", value: "reuse" },
@@ -220,10 +212,7 @@ async function overrideChosenEnv(projectId, env) {
220
212
  if (action === "reuse") {
221
213
  continue;
222
214
  }
223
- const secretValue = await prompt.promptOnce({
224
- type: "password",
225
- message: `What new value would you like for secret ${name} [input is hidden]?`,
226
- });
215
+ const secretValue = await prompt.password(`What new value would you like for secret ${name} [input is hidden]?`);
227
216
  await csm.createSecret(projectId, secretRef, { [csm.FIREBASE_MANAGED]: "apphosting" });
228
217
  await csm.addVersion(projectId, secretRef, secretValue);
229
218
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getGitHubCommit = exports.getGitHubBranch = exports.fetchRepositoryCloneUris = exports.listAppHostingConnections = exports.getOrCreateRepository = exports.getOrCreateConnection = exports.createConnection = exports.ensureSecretManagerAdminGrant = exports.promptGitHubBranch = exports.getOrCreateOauthConnection = exports.listValidInstallations = exports.promptGitHubInstallation = exports.getConnectionForInstallation = exports.linkGitHubRepository = exports.getOrCreateGithubConnectionWithSentinel = exports.generateRepositoryId = exports.extractRepoSlugFromUri = exports.parseConnectionName = void 0;
3
+ exports.getGitHubCommit = exports.getGitHubBranch = exports.fetchRepositoryCloneUris = exports.listAppHostingConnections = exports.getOrCreateRepository = exports.getOrCreateConnection = exports.createConnection = exports.ensureSecretManagerAdminGrant = exports.promptGitHubBranch = exports.getOrCreateOauthConnection = exports.listValidInstallations = exports.promptGitHubInstallation = exports.getConnectionForInstallation = exports.linkGitHubRepository = exports.getOrCreateFullyInstalledGithubConnection = exports.generateConnectionId = exports.generateRepositoryId = exports.extractRepoSlugFromUri = exports.parseConnectionName = void 0;
4
4
  const clc = require("colorette");
5
5
  const devConnect = require("../gcp/devConnect");
6
6
  const rm = require("../gcp/resourceManager");
@@ -11,11 +11,9 @@ const prompt_1 = require("../prompt");
11
11
  const getProjectNumber_1 = require("../getProjectNumber");
12
12
  const api_1 = require("../api");
13
13
  const fuzzy = require("fuzzy");
14
- const inquirer = require("inquirer");
15
14
  const apiv2_1 = require("../apiv2");
16
15
  const githubApiClient = new apiv2_1.Client({ urlPrefix: (0, api_1.githubApiOrigin)(), auth: false });
17
16
  const APPHOSTING_CONN_PATTERN = /.+\/apphosting-github-conn-.+$/;
18
- const APPHOSTING_OAUTH_CONN_NAME = "firebase-app-hosting-github-oauth";
19
17
  const CONNECTION_NAME_REGEX = /^projects\/(?<projectId>[^\/]+)\/locations\/(?<location>[^\/]+)\/connections\/(?<id>[^\/]+)$/;
20
18
  function parseConnectionName(name) {
21
19
  const match = CONNECTION_NAME_REGEX.exec(name);
@@ -49,13 +47,14 @@ function generateRepositoryId(remoteUri) {
49
47
  return (_a = extractRepoSlugFromUri(remoteUri)) === null || _a === void 0 ? void 0 : _a.replaceAll("/", "-");
50
48
  }
51
49
  exports.generateRepositoryId = generateRepositoryId;
52
- function generateConnectionId() {
50
+ const generateConnectionId = () => {
53
51
  const randomHash = Math.random().toString(36).slice(6);
54
52
  return `apphosting-github-conn-${randomHash}`;
55
- }
53
+ };
54
+ exports.generateConnectionId = generateConnectionId;
56
55
  const ADD_ACCOUNT_CHOICE = "@ADD_ACCOUNT";
57
56
  const MANAGE_INSTALLATION_CHOICE = "@MANAGE_INSTALLATION";
58
- async function getOrCreateGithubConnectionWithSentinel(projectId, location, createConnectionId) {
57
+ async function getOrCreateFullyInstalledGithubConnection(projectId, location, createConnectionId) {
59
58
  utils.logBullet(clc.bold(`${clc.yellow("===")} Import a GitHub repository`));
60
59
  if (createConnectionId) {
61
60
  try {
@@ -76,10 +75,7 @@ async function getOrCreateGithubConnectionWithSentinel(projectId, location, crea
76
75
  const apphostingGitHubInstallationURL = (0, api_1.apphostingGitHubAppInstallationURL)();
77
76
  utils.logBullet(apphostingGitHubInstallationURL);
78
77
  await utils.openInBrowser(apphostingGitHubInstallationURL);
79
- await (0, prompt_1.promptOnce)({
80
- type: "input",
81
- message: "Press Enter once you have installed or configured the Firebase App Hosting GitHub app to access your GitHub repo.",
82
- });
78
+ await (0, prompt_1.input)("Press Enter once you have installed or configured the Firebase App Hosting GitHub app to access your GitHub repo.");
83
79
  installationId = await promptGitHubInstallation(projectId, location, oauthConn);
84
80
  }
85
81
  const connectionMatchingInstallation = await getConnectionForInstallation(projectId, location, installationId);
@@ -91,15 +87,15 @@ async function getOrCreateGithubConnectionWithSentinel(projectId, location, crea
91
87
  }
92
88
  }
93
89
  if (!createConnectionId) {
94
- createConnectionId = generateConnectionId();
90
+ createConnectionId = (0, exports.generateConnectionId)();
95
91
  }
96
92
  const connection = await createFullyInstalledConnection(projectId, location, createConnectionId, oauthConn, installationId);
97
93
  return connection;
98
94
  }
99
- exports.getOrCreateGithubConnectionWithSentinel = getOrCreateGithubConnectionWithSentinel;
95
+ exports.getOrCreateFullyInstalledGithubConnection = getOrCreateFullyInstalledGithubConnection;
100
96
  async function linkGitHubRepository(projectId, location, createConnectionId) {
101
97
  var _a, _b;
102
- const connection = await getOrCreateGithubConnectionWithSentinel(projectId, location, createConnectionId);
98
+ const connection = await getOrCreateFullyInstalledGithubConnection(projectId, location, createConnectionId);
103
99
  let repoCloneUri;
104
100
  do {
105
101
  if (repoCloneUri === MANAGE_INSTALLATION_CHOICE) {
@@ -127,10 +123,7 @@ async function createFullyInstalledConnection(projectId, location, connectionId,
127
123
  const targetUri = conn.installationState.actionUri;
128
124
  utils.logBullet(targetUri);
129
125
  await utils.openInBrowser(targetUri);
130
- await (0, prompt_1.promptOnce)({
131
- type: "input",
132
- message: "Press Enter once you have installed or configured the Firebase App Hosting GitHub app to access your GitHub repo.",
133
- });
126
+ await (0, prompt_1.input)("Press Enter once you have installed or configured the Firebase App Hosting GitHub app to access your GitHub repo.");
134
127
  conn = await devConnect.getConnection(projectId, location, connectionId);
135
128
  }
136
129
  return conn;
@@ -144,10 +137,7 @@ async function manageInstallation(connection) {
144
137
  }
145
138
  utils.logBullet(targetUri);
146
139
  await utils.openInBrowser(targetUri);
147
- await (0, prompt_1.promptOnce)({
148
- type: "input",
149
- message: "Press Enter once you have installed or configured the Firebase App Hosting GitHub app to access your GitHub repo.",
150
- });
140
+ await (0, prompt_1.input)("Press Enter once you have installed or configured the Firebase App Hosting GitHub app to access your GitHub repo.");
151
141
  }
152
142
  async function getConnectionForInstallation(projectId, location, installationId) {
153
143
  const connections = await listAppHostingConnections(projectId, location);
@@ -164,30 +154,26 @@ async function getConnectionForInstallation(projectId, location, installationId)
164
154
  exports.getConnectionForInstallation = getConnectionForInstallation;
165
155
  async function promptGitHubInstallation(projectId, location, connection) {
166
156
  const installations = await listValidInstallations(projectId, location, connection);
167
- const installationName = await (0, prompt_1.promptOnce)({
168
- type: "autocomplete",
169
- name: "installation",
157
+ const installationName = await (0, prompt_1.search)({
170
158
  message: "Which GitHub account do you want to use?",
171
- source: (_, input = "") => {
172
- return new Promise((resolve) => resolve([
173
- new inquirer.Separator(),
174
- {
175
- name: "Missing an account? Select this option to add a GitHub account",
176
- value: ADD_ACCOUNT_CHOICE,
177
- },
178
- new inquirer.Separator(),
179
- ...fuzzy
180
- .filter(input, installations, {
181
- extract: (installation) => installation.name || "",
182
- })
183
- .map((result) => {
184
- return {
185
- name: result.original.name || "",
186
- value: result.original.id,
187
- };
188
- }),
189
- ]));
190
- },
159
+ source: (input = "") => [
160
+ new prompt_1.Separator(),
161
+ {
162
+ name: "Missing an account? Select this option to add a GitHub account",
163
+ value: ADD_ACCOUNT_CHOICE,
164
+ },
165
+ new prompt_1.Separator(),
166
+ ...fuzzy
167
+ .filter(input, installations, {
168
+ extract: (installation) => installation.name || "",
169
+ })
170
+ .map((result) => {
171
+ return {
172
+ name: result.original.name || "",
173
+ value: result.original.id,
174
+ };
175
+ }),
176
+ ],
191
177
  });
192
178
  return installationName;
193
179
  }
@@ -206,26 +192,17 @@ async function listValidInstallations(projectId, location, connection) {
206
192
  exports.listValidInstallations = listValidInstallations;
207
193
  async function getOrCreateOauthConnection(projectId, location) {
208
194
  let conn;
209
- try {
210
- conn = await devConnect.getConnection(projectId, location, APPHOSTING_OAUTH_CONN_NAME);
211
- }
212
- catch (err) {
213
- if (err.status === 404) {
214
- await ensureSecretManagerAdminGrant(projectId);
215
- conn = await createConnection(projectId, location, APPHOSTING_OAUTH_CONN_NAME);
216
- }
217
- else {
218
- throw err;
219
- }
195
+ const completedConnections = await listAppHostingConnections(projectId, location);
196
+ if (completedConnections.length > 0) {
197
+ return completedConnections[0];
220
198
  }
199
+ await ensureSecretManagerAdminGrant(projectId);
200
+ conn = await createConnection(projectId, location, (0, exports.generateConnectionId)());
221
201
  while (conn.installationState.stage === "PENDING_USER_OAUTH") {
222
202
  utils.logBullet("Please authorize the Firebase GitHub app by visiting this url:");
223
203
  const { url, cleanup } = await utils.openInBrowserPopup(conn.installationState.actionUri, "Authorize the GitHub app");
224
204
  utils.logBullet(`\t${url}`);
225
- await (0, prompt_1.promptOnce)({
226
- type: "input",
227
- message: "Press Enter once you have authorized the GitHub App.",
228
- });
205
+ await (0, prompt_1.input)("Press Enter once you have authorized the GitHub App.");
229
206
  cleanup();
230
207
  const { projectId, location, id } = parseConnectionName(conn.name);
231
208
  conn = await devConnect.getConnection(projectId, location, id);
@@ -236,49 +213,41 @@ async function getOrCreateOauthConnection(projectId, location) {
236
213
  exports.getOrCreateOauthConnection = getOrCreateOauthConnection;
237
214
  async function promptCloneUri(projectId, connection) {
238
215
  const cloneUris = await fetchRepositoryCloneUris(projectId, connection);
239
- const cloneUri = await (0, prompt_1.promptOnce)({
240
- type: "autocomplete",
241
- name: "cloneUri",
216
+ const cloneUri = await (0, prompt_1.search)({
242
217
  message: "Which GitHub repo do you want to deploy?",
243
- source: (_, input = "") => {
244
- return new Promise((resolve) => resolve([
245
- new inquirer.Separator(),
246
- {
247
- name: "Missing a repo? Select this option to configure your GitHub connection settings",
248
- value: MANAGE_INSTALLATION_CHOICE,
249
- },
250
- new inquirer.Separator(),
251
- ...fuzzy
252
- .filter(input, cloneUris, {
253
- extract: (uri) => extractRepoSlugFromUri(uri) || "",
254
- })
255
- .map((result) => {
256
- return {
257
- name: extractRepoSlugFromUri(result.original) || "",
258
- value: result.original,
259
- };
260
- }),
261
- ]));
262
- },
218
+ source: (input = "") => [
219
+ new prompt_1.Separator(),
220
+ {
221
+ name: "Missing a repo? Select this option to configure your GitHub connection settings",
222
+ value: MANAGE_INSTALLATION_CHOICE,
223
+ },
224
+ new prompt_1.Separator(),
225
+ ...fuzzy
226
+ .filter(input, cloneUris, {
227
+ extract: (uri) => extractRepoSlugFromUri(uri) || "",
228
+ })
229
+ .map((result) => {
230
+ return {
231
+ name: extractRepoSlugFromUri(result.original) || "",
232
+ value: result.original,
233
+ };
234
+ }),
235
+ ],
263
236
  });
264
237
  return cloneUri;
265
238
  }
266
239
  async function promptGitHubBranch(repoLink) {
267
240
  const branches = await devConnect.listAllBranches(repoLink.name);
268
- const branch = await (0, prompt_1.promptOnce)({
269
- type: "autocomplete",
270
- name: "branch",
241
+ const branch = await (0, prompt_1.search)({
271
242
  message: "Pick a branch for continuous deployment",
272
- source: (_, input = "") => {
273
- return new Promise((resolve) => resolve([
274
- ...fuzzy.filter(input, Array.from(branches)).map((result) => {
275
- return {
276
- name: result.original,
277
- value: result.original,
278
- };
279
- }),
280
- ]));
281
- },
243
+ source: (input = "") => [
244
+ ...fuzzy.filter(input, Array.from(branches)).map((result) => {
245
+ return {
246
+ name: result.original,
247
+ value: result.original,
248
+ };
249
+ }),
250
+ ],
282
251
  });
283
252
  return branch;
284
253
  }
@@ -292,10 +261,7 @@ async function ensureSecretManagerAdminGrant(projectId) {
292
261
  return;
293
262
  }
294
263
  utils.logBullet("To create a new GitHub connection, Secret Manager Admin role (roles/secretmanager.admin) is required on the Developer Connect Service Agent.");
295
- const grant = await (0, prompt_1.promptOnce)({
296
- type: "confirm",
297
- message: "Grant the required role to the Developer Connect Service Agent?",
298
- });
264
+ const grant = await (0, prompt_1.confirm)("Grant the required role to the Developer Connect Service Agent?");
299
265
  if (!grant) {
300
266
  utils.logBullet("You, or your project administrator, should run the following command to grant the required role:\n\n" +
301
267
  "You, or your project adminstrator, can run the following command to grant the required role manually:\n\n" +
@@ -11,7 +11,6 @@ const error_1 = require("../error");
11
11
  const prompt_1 = require("../prompt");
12
12
  const getProjectNumber_1 = require("../getProjectNumber");
13
13
  const fuzzy = require("fuzzy");
14
- const inquirer = require("inquirer");
15
14
  const APPHOSTING_CONN_PATTERN = /.+\/apphosting-github-conn-.+$/;
16
15
  const APPHOSTING_OAUTH_CONN_NAME = "apphosting-github-oauth";
17
16
  const CONNECTION_NAME_REGEX = /^projects\/(?<projectId>[^\/]+)\/locations\/(?<location>[^\/]+)\/connections\/(?<id>[^\/]+)$/;
@@ -89,10 +88,7 @@ async function createFullyInstalledConnection(projectId, location, connectionId,
89
88
  const targetUri = conn.installationState.actionUri;
90
89
  utils.logBullet(targetUri);
91
90
  await utils.openInBrowser(targetUri);
92
- await (0, prompt_1.promptOnce)({
93
- type: "input",
94
- message: "Press Enter once you have installed or configured the Cloud Build GitHub app to access your GitHub repo.",
95
- });
91
+ await (0, prompt_1.input)("Press Enter once you have installed or configured the Cloud Build GitHub app to access your GitHub repo.");
96
92
  conn = await gcb.getConnection(projectId, location, connectionId);
97
93
  }
98
94
  return conn;
@@ -116,10 +112,7 @@ async function getOrCreateOauthConnection(projectId, location) {
116
112
  utils.logBullet("Sign in to GitHub and authorize Cloud Build GitHub app:");
117
113
  const { url, cleanup } = await utils.openInBrowserPopup(conn.installationState.actionUri, "Authorize the GitHub app");
118
114
  utils.logBullet(`\t${url}`);
119
- await (0, prompt_1.promptOnce)({
120
- type: "input",
121
- message: "Press Enter once you have authorized the app",
122
- });
115
+ await (0, prompt_1.input)("Press Enter once you have authorized the app");
123
116
  cleanup();
124
117
  const { projectId, location, id } = parseConnectionName(conn.name);
125
118
  conn = await gcb.getConnection(projectId, location, id);
@@ -129,30 +122,26 @@ async function getOrCreateOauthConnection(projectId, location) {
129
122
  exports.getOrCreateOauthConnection = getOrCreateOauthConnection;
130
123
  async function promptRepositoryUri(projectId, connections) {
131
124
  const { repos, remoteUriToConnection } = await fetchAllRepositories(projectId, connections);
132
- const remoteUri = await (0, prompt_1.promptOnce)({
133
- type: "autocomplete",
134
- name: "remoteUri",
125
+ const remoteUri = await (0, prompt_1.search)({
135
126
  message: "Which GitHub repo do you want to deploy?",
136
- source: (_, input = "") => {
137
- return new Promise((resolve) => resolve([
138
- new inquirer.Separator(),
139
- {
140
- name: "Missing a repo? Select this option to configure your GitHub connection settings",
141
- value: ADD_CONN_CHOICE,
142
- },
143
- new inquirer.Separator(),
144
- ...fuzzy
145
- .filter(input, repos, {
146
- extract: (repo) => extractRepoSlugFromUri(repo.remoteUri) || "",
147
- })
148
- .map((result) => {
149
- return {
150
- name: extractRepoSlugFromUri(result.original.remoteUri) || "",
151
- value: result.original.remoteUri,
152
- };
153
- }),
154
- ]));
155
- },
127
+ source: (input) => [
128
+ new prompt_1.Separator(),
129
+ {
130
+ name: "Missing a repo? Select this option to configure your GitHub connection settings",
131
+ value: ADD_CONN_CHOICE,
132
+ },
133
+ new prompt_1.Separator(),
134
+ ...fuzzy
135
+ .filter(input !== null && input !== void 0 ? input : "", repos, {
136
+ extract: (repo) => extractRepoSlugFromUri(repo.remoteUri) || "",
137
+ })
138
+ .map((result) => {
139
+ return {
140
+ name: extractRepoSlugFromUri(result.original.remoteUri) || "",
141
+ value: result.original.remoteUri,
142
+ };
143
+ }),
144
+ ],
156
145
  });
157
146
  return { remoteUri, connection: remoteUriToConnection[remoteUri] };
158
147
  }
@@ -164,10 +153,7 @@ async function ensureSecretManagerAdminGrant(projectId) {
164
153
  return;
165
154
  }
166
155
  utils.logBullet("To create a new GitHub connection, Secret Manager Admin role (roles/secretmanager.admin) is required on the Cloud Build Service Agent.");
167
- const grant = await (0, prompt_1.promptOnce)({
168
- type: "confirm",
169
- message: "Grant the required role to the Cloud Build Service Agent?",
170
- });
156
+ const grant = await (0, prompt_1.confirm)("Grant the required role to the Cloud Build Service Agent?");
171
157
  if (!grant) {
172
158
  utils.logBullet("You, or your project administrator, should run the following command to grant the required role:\n\n" +
173
159
  "You, or your project adminstrator, can run the following command to grant the required role manually:\n\n" +
@@ -125,8 +125,7 @@ async function selectBackendServiceAccounts(projectNumber, projectId, options) {
125
125
  accum.add(row.runServiceAccount);
126
126
  return accum;
127
127
  }, new Set());
128
- const chosen = await prompt.promptOnce({
129
- type: "checkbox",
128
+ const chosen = await prompt.checkbox({
130
129
  message: "Which service accounts would you like to grant access? " +
131
130
  "Press Space to select accounts, then Enter to confirm your choices.",
132
131
  choices: [...allAccounts.values()].sort(),
@@ -157,7 +156,7 @@ async function envVarForSecret(secret, trimTestPrefix = false) {
157
156
  }
158
157
  }
159
158
  do {
160
- const test = await prompt.promptOnce({
159
+ const test = await prompt.input({
161
160
  message: "What environment variable name would you like to use?",
162
161
  default: upper,
163
162
  });
@@ -30,9 +30,7 @@ async function promptForAppHostingYaml(apphostingFileNameToPathMap, promptMessag
30
30
  value: apphostingFileNameToPathMap.get(fileName),
31
31
  };
32
32
  });
33
- const fileToExportPath = await prompt.promptOnce({
34
- name: "apphosting-yaml",
35
- type: "list",
33
+ const fileToExportPath = await prompt.select({
36
34
  message: promptMessage,
37
35
  choices: listOptions,
38
36
  });
package/lib/auth.js CHANGED
@@ -299,10 +299,7 @@ async function loginRemotely() {
299
299
  logger_1.logger.info();
300
300
  logger_1.logger.info("3. Paste or enter the authorization code below once you have it:");
301
301
  logger_1.logger.info();
302
- const code = await (0, prompt_1.promptOnce)({
303
- type: "input",
304
- message: "Enter authorization code:",
305
- });
302
+ const code = await (0, prompt_1.input)({ message: "Enter authorization code:" });
306
303
  try {
307
304
  const tokens = await getTokensFromAuthorizationCode(code, `${(0, api_1.authProxyOrigin)()}/complete`, codeVerifier);
308
305
  void (0, track_1.trackGA4)("login", { method: "google_remote" });
@@ -21,12 +21,12 @@ exports.command = new command_1.Command("apphosting:backends:delete <backend>")
21
21
  const backends = await (0, backend_1.chooseBackends)(projectId, backendId, "Please select the backends you'd like to delete:", options.force);
22
22
  utils.logWarning("You are about to permanently delete these backend(s):");
23
23
  (0, apphosting_backends_list_1.printBackendsTable)(backends);
24
- const confirmDeletion = await (0, prompt_1.promptOnce)({
25
- type: "confirm",
26
- name: "force",
27
- default: false,
24
+ const confirmDeletion = await (0, prompt_1.confirm)({
28
25
  message: "Are you sure?",
29
- }, options);
26
+ default: false,
27
+ force: options.force,
28
+ nonInteractive: options.nonInteractive,
29
+ });
30
30
  if (!confirmDeletion) {
31
31
  return;
32
32
  }
@@ -46,9 +46,7 @@ exports.command = new command_1.Command("apphosting:secrets:set <secretName>")
46
46
  if (!created) {
47
47
  return;
48
48
  }
49
- const type = await prompt.promptOnce({
50
- type: "list",
51
- name: "type",
49
+ const type = await prompt.select({
52
50
  message: "Is this secret for production or only local testing?",
53
51
  choices: [
54
52
  { name: "Production", value: "production" },
@@ -56,9 +54,7 @@ exports.command = new command_1.Command("apphosting:secrets:set <secretName>")
56
54
  ],
57
55
  });
58
56
  if (type === "local") {
59
- const emailList = await prompt.promptOnce({
60
- type: "input",
61
- name: "emails",
57
+ const emailList = await prompt.input({
62
58
  message: "Please enter a comma separated list of user or groups who should have access to this secret:",
63
59
  });
64
60
  if (emailList.length) {