firebase-tools 13.0.0 → 13.0.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.
@@ -5,8 +5,10 @@ const command_1 = require("../command");
5
5
  const projectUtils_1 = require("../projectUtils");
6
6
  const requireInteractive_1 = require("../requireInteractive");
7
7
  const frameworks_1 = require("../init/features/frameworks");
8
+ const frameworks_2 = require("../gcp/frameworks");
8
9
  exports.command = new command_1.Command("backends:create")
9
10
  .description("Create a backend in a Firebase project")
11
+ .before(frameworks_2.ensureApiEnabled)
10
12
  .before(requireInteractive_1.default)
11
13
  .action(async (options) => {
12
14
  const projectId = (0, projectUtils_1.needProjectId)(options);
@@ -9,6 +9,7 @@ const prompt_1 = require("../prompt");
9
9
  const utils = require("../utils");
10
10
  const logger_1 = require("../logger");
11
11
  const constants_1 = require("../init/features/frameworks/constants");
12
+ const frameworks_1 = require("../gcp/frameworks");
12
13
  const Table = require("cli-table");
13
14
  const COLUMN_LENGTH = 20;
14
15
  const TABLE_HEAD = [
@@ -24,6 +25,7 @@ exports.command = new command_1.Command("backends:delete")
24
25
  .option("-l, --location <location>", "App Backend location", "")
25
26
  .option("-s, --backend <backend>", "Backend Id", "")
26
27
  .withForce()
28
+ .before(frameworks_1.ensureApiEnabled)
27
29
  .action(async (options) => {
28
30
  const projectId = (0, projectUtils_1.needProjectId)(options);
29
31
  let location = options.location;
@@ -6,6 +6,7 @@ const projectUtils_1 = require("../projectUtils");
6
6
  const gcp = require("../gcp/frameworks");
7
7
  const error_1 = require("../error");
8
8
  const logger_1 = require("../logger");
9
+ const frameworks_1 = require("../gcp/frameworks");
9
10
  const Table = require("cli-table");
10
11
  const COLUMN_LENGTH = 20;
11
12
  const TABLE_HEAD = [
@@ -16,17 +17,13 @@ const TABLE_HEAD = [
16
17
  "Created Date",
17
18
  "Updated Date",
18
19
  ];
19
- exports.command = new command_1.Command("backends:get")
20
+ exports.command = new command_1.Command("backends:get <backendId>")
20
21
  .description("Get backend details of a Firebase project")
21
22
  .option("-l, --location <location>", "App Backend location", "-")
22
- .option("-b, --backend <backend>", "Backend Id", "")
23
- .action(async (options) => {
23
+ .before(frameworks_1.ensureApiEnabled)
24
+ .action(async (backendId, options) => {
24
25
  const projectId = (0, projectUtils_1.needProjectId)(options);
25
26
  const location = options.location;
26
- const backendId = options.backend;
27
- if (!backendId) {
28
- throw new error_1.FirebaseError("Backend id can't be empty.");
29
- }
30
27
  let backendsList = [];
31
28
  const table = new Table({
32
29
  head: TABLE_HEAD,
@@ -7,19 +7,14 @@ const gcp = require("../gcp/frameworks");
7
7
  const error_1 = require("../error");
8
8
  const logger_1 = require("../logger");
9
9
  const colorette_1 = require("colorette");
10
+ const frameworks_1 = require("../gcp/frameworks");
10
11
  const Table = require("cli-table");
11
12
  const COLUMN_LENGTH = 20;
12
- const TABLE_HEAD = [
13
- "Backend Id",
14
- "Repository Name",
15
- "Location",
16
- "URL",
17
- "Created Date",
18
- "Updated Date",
19
- ];
13
+ const TABLE_HEAD = ["Backend Id", "Repository", "Location", "URL", "Created Date", "Updated Date"];
20
14
  exports.command = new command_1.Command("backends:list")
21
15
  .description("List backends of a Firebase project.")
22
16
  .option("-l, --location <location>", "App Backend location", "-")
17
+ .before(frameworks_1.ensureApiEnabled)
23
18
  .action(async (options) => {
24
19
  const projectId = (0, projectUtils_1.needProjectId)(options);
25
20
  const location = options.location;
@@ -31,8 +26,8 @@ exports.command = new command_1.Command("backends:list")
31
26
  const backendsList = [];
32
27
  try {
33
28
  const backendsPerRegion = await gcp.listBackends(projectId, location);
34
- backendsList.push(backendsPerRegion);
35
- populateTable(backendsPerRegion, location, table);
29
+ backendsList.push(...backendsPerRegion.backends);
30
+ populateTable(backendsList, table);
36
31
  logger_1.logger.info();
37
32
  logger_1.logger.info(`Backends for project ${(0, colorette_1.bold)(projectId)}`);
38
33
  logger_1.logger.info();
@@ -43,9 +38,9 @@ exports.command = new command_1.Command("backends:list")
43
38
  }
44
39
  return backendsList;
45
40
  });
46
- function populateTable(backendsLists, location, table) {
41
+ function populateTable(backends, table) {
47
42
  var _a;
48
- for (const backend of backendsLists.backends) {
43
+ for (const backend of backends) {
49
44
  const [location, , backendId] = backend.name.split("/").slice(3, 6);
50
45
  const entry = [
51
46
  backendId,
@@ -3,9 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ensureFirestoreTriggerRegion = void 0;
4
4
  const firestore = require("../../../gcp/firestore");
5
5
  const error_1 = require("../../../error");
6
+ const dbCache = new Map();
7
+ async function getDatabase(project, databaseId) {
8
+ const key = `${project}/${databaseId}`;
9
+ if (dbCache.has(key)) {
10
+ return dbCache.get(key);
11
+ }
12
+ const db = await firestore.getDatabase(project, databaseId);
13
+ dbCache.set(key, db);
14
+ return db;
15
+ }
6
16
  async function ensureFirestoreTriggerRegion(endpoint) {
7
17
  var _a;
8
- const db = await firestore.getDatabase(endpoint.project, ((_a = endpoint.eventTrigger.eventFilters) === null || _a === void 0 ? void 0 : _a.database) || "(default)");
18
+ const db = await getDatabase(endpoint.project, ((_a = endpoint.eventTrigger.eventFilters) === null || _a === void 0 ? void 0 : _a.database) || "(default)");
9
19
  const dbRegion = db.locationId;
10
20
  if (!endpoint.eventTrigger.region) {
11
21
  endpoint.eventTrigger.region = dbRegion;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ɵcodegenFunctionsDirectory = exports.shouldUseDevModeHandle = exports.getValidBuildTargets = exports.ɵcodegenPublicDirectory = exports.getDevModeHandle = exports.build = exports.init = exports.discover = exports.docsUrl = exports.type = exports.support = exports.name = void 0;
3
+ exports.ɵcodegenFunctionsDirectory = exports.shouldUseDevModeHandle = exports.getValidBuildTargets = exports.ɵcodegenPublicDirectory = exports.getDevModeHandle = exports.build = exports.init = exports.discover = exports.supportedRange = exports.docsUrl = exports.type = exports.support = exports.name = void 0;
4
4
  const path_1 = require("path");
5
5
  const child_process_1 = require("child_process");
6
6
  const cross_spawn_1 = require("cross-spawn");
@@ -15,16 +15,18 @@ exports.support = "preview";
15
15
  exports.type = 3;
16
16
  exports.docsUrl = "https://firebase.google.com/docs/hosting/frameworks/angular";
17
17
  const DEFAULT_BUILD_SCRIPT = ["ng build"];
18
+ exports.supportedRange = "14 - 17";
18
19
  async function discover(dir) {
19
20
  if (!(await (0, fs_extra_1.pathExists)((0, path_1.join)(dir, "package.json"))))
20
21
  return;
21
22
  if (!(await (0, fs_extra_1.pathExists)((0, path_1.join)(dir, "angular.json"))))
22
23
  return;
23
- return { mayWantBackend: true, publicDirectory: (0, path_1.join)(dir, "src", "assets") };
24
+ const version = (0, utils_2.getAngularVersion)(dir);
25
+ return { mayWantBackend: true, publicDirectory: (0, path_1.join)(dir, "src", "assets"), version };
24
26
  }
25
27
  exports.discover = discover;
26
28
  function init(setup, config) {
27
- (0, child_process_1.execSync)(`npx --yes -p @angular/cli@latest ng new ${setup.projectId} --directory ${setup.hosting.source} --skip-git`, {
29
+ (0, child_process_1.execSync)(`npx --yes -p @angular/cli@"${exports.supportedRange}" ng new ${setup.projectId} --directory ${setup.hosting.source} --skip-git`, {
28
30
  stdio: "inherit",
29
31
  cwd: config.projectDir,
30
32
  });
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getBuildConfig = exports.getServerConfig = exports.getBrowserConfig = exports.getContext = exports.getAllTargets = void 0;
3
+ exports.getAngularVersion = exports.getBuildConfig = exports.getServerConfig = exports.getBrowserConfig = exports.getContext = exports.getAllTargets = void 0;
4
4
  const utils_1 = require("../utils");
5
5
  const error_1 = require("../../error");
6
6
  const path_1 = require("path");
7
7
  const assert_1 = require("assert");
8
8
  const utils_2 = require("../../utils");
9
+ const semver_1 = require("semver");
9
10
  async function localesForTarget(dir, architectHost, target, workspaceProject) {
10
11
  var _a;
11
12
  const { targetStringFromTarget } = (0, utils_1.relativeRequire)(dir, "@angular-devkit/architect");
@@ -182,7 +183,9 @@ async function getContext(dir, targetOrConfiguration) {
182
183
  }
183
184
  }
184
185
  if (deployTarget) {
185
- const options = await architectHost.getOptionsForTarget(deployTarget);
186
+ const options = await architectHost
187
+ .getOptionsForTarget(deployTarget)
188
+ .catch(() => { var _a; return (_a = workspaceProject.targets.get(deployTarget.target)) === null || _a === void 0 ? void 0 : _a.options; });
186
189
  if (!options)
187
190
  throw new error_1.FirebaseError("Unable to get options for ng-deploy.");
188
191
  if (options.buildTarget) {
@@ -201,6 +204,10 @@ async function getContext(dir, targetOrConfiguration) {
201
204
  (0, utils_2.assertIsString)(options.serverTarget);
202
205
  serverTarget = targetFromTargetString(options.serverTarget);
203
206
  }
207
+ if (options.serveTarget) {
208
+ (0, utils_2.assertIsString)(options.serveTarget);
209
+ serveTarget = targetFromTargetString(options.serveTarget);
210
+ }
204
211
  if (options.serveOptimizedImages) {
205
212
  serveOptimizedImages = true;
206
213
  }
@@ -441,3 +448,13 @@ async function getBuildConfig(sourceDir, configuration) {
441
448
  };
442
449
  }
443
450
  exports.getBuildConfig = getBuildConfig;
451
+ function getAngularVersion(cwd) {
452
+ const dependency = (0, utils_1.findDependency)("@angular/core", { cwd, depth: 0, omitDev: false });
453
+ if (!dependency)
454
+ return undefined;
455
+ const angularVersionSemver = (0, semver_1.coerce)(dependency.version);
456
+ if (!angularVersionSemver)
457
+ return dependency.version;
458
+ return angularVersionSemver.toString();
459
+ }
460
+ exports.getAngularVersion = getAngularVersion;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getDevModeHandle = exports.ɵcodegenFunctionsDirectory = exports.ɵcodegenPublicDirectory = exports.build = exports.discover = exports.type = exports.support = exports.name = void 0;
3
+ exports.getDevModeHandle = exports.ɵcodegenFunctionsDirectory = exports.ɵcodegenPublicDirectory = exports.build = exports.discover = exports.supportedRange = exports.type = exports.support = exports.name = void 0;
4
4
  const cross_spawn_1 = require("cross-spawn");
5
5
  const fs_extra_1 = require("fs-extra");
6
6
  const path_1 = require("path");
@@ -10,15 +10,18 @@ const utils_2 = require("./utils");
10
10
  exports.name = "Astro";
11
11
  exports.support = "experimental";
12
12
  exports.type = 2;
13
+ exports.supportedRange = "2 - 3";
13
14
  async function discover(dir) {
14
15
  if (!(0, fs_extra_1.existsSync)((0, path_1.join)(dir, "package.json")))
15
16
  return;
16
- if (!(0, utils_2.getAstroVersion)(dir))
17
+ const version = (0, utils_2.getAstroVersion)(dir);
18
+ if (!version)
17
19
  return;
18
20
  const { output, publicDir: publicDirectory } = await (0, utils_2.getConfig)(dir);
19
21
  return {
20
22
  mayWantBackend: output !== "static",
21
23
  publicDirectory,
24
+ version,
22
25
  };
23
26
  }
24
27
  exports.discover = discover;
@@ -4,6 +4,7 @@ exports.getAstroVersion = exports.getConfig = exports.getBootstrapScript = void
4
4
  const path_1 = require("path");
5
5
  const utils_1 = require("../utils");
6
6
  const semver_1 = require("semver");
7
+ const url_1 = require("url");
7
8
  const { dynamicImport } = require(true && "../../dynamicImport");
8
9
  function getBootstrapScript() {
9
10
  return `const entry = import('./entry.mjs');\nexport const handle = async (req, res) => (await entry).handler(req, res)`;
@@ -26,8 +27,8 @@ async function getConfig(cwd) {
26
27
  config = astroConfig;
27
28
  }
28
29
  return {
29
- outDir: (0, path_1.relative)(cwd, config.outDir.pathname),
30
- publicDir: (0, path_1.relative)(cwd, config.publicDir.pathname),
30
+ outDir: (0, path_1.relative)(cwd, (0, url_1.fileURLToPath)(config.outDir)),
31
+ publicDir: (0, path_1.relative)(cwd, (0, url_1.fileURLToPath)(config.publicDir)),
31
32
  output: config.output,
32
33
  adapter: config.adapter,
33
34
  };
@@ -6,10 +6,10 @@ const experiments = require("../experiments");
6
6
  exports.NPM_COMMAND_TIMEOUT_MILLIES = 10000;
7
7
  exports.SupportLevelWarnings = {
8
8
  ["experimental"]: (framework) => `Thank you for trying our ${clc.italic("experimental")} support for ${framework} on Firebase Hosting.
9
- ${clc.yellow(`While this integration is maintained by Googlers it is not a supported Firebase product.
9
+ ${clc.red(`While this integration is maintained by Googlers it is not a supported Firebase product.
10
10
  Issues filed on GitHub will be addressed on a best-effort basis by maintainers and other community members.`)}`,
11
11
  ["preview"]: (framework) => `Thank you for trying our ${clc.italic("early preview")} of ${framework} support on Firebase Hosting.
12
- ${clc.yellow("During the preview, support is best-effort and breaking changes can be expected. Proceed with caution.")}`,
12
+ ${clc.red("During the preview, support is best-effort and breaking changes can be expected. Proceed with caution.")}`,
13
13
  };
14
14
  exports.DEFAULT_DOCS_URL = "https://firebase.google.com/docs/hosting/frameworks/frameworks-overview";
15
15
  exports.FILE_BUG_URL = "https://github.com/firebase/firebase-tools/issues/new?template=bug_report.md";
@@ -26,11 +26,35 @@ exports.VALID_ENGINES = { node: [16, 18, 20] };
26
26
  exports.VALID_LOCALE_FORMATS = [/^ALL_[a-z]+$/, /^[a-z]+_ALL$/, /^[a-z]+(_[a-z]+)?$/];
27
27
  exports.DEFAULT_REGION = "us-central1";
28
28
  exports.ALLOWED_SSR_REGIONS = [
29
- { name: "us-central1 (Iowa)", value: "us-central1" },
30
- { name: "us-west1 (Oregon)", value: "us-west1" },
31
- { name: "us-east1 (South Carolina)", value: "us-east1" },
32
- { name: "europe-west1 (Belgium)", value: "europe-west1" },
33
- { name: "asia-east1 (Taiwan)", value: "asia-east1" },
29
+ { name: "us-central1 (Iowa)", value: "us-central1", recommended: true },
30
+ { name: "us-east1 (South Carolina)", value: "us-east1", recommended: true },
31
+ { name: "us-east4 (Northern Virginia)", value: "us-east4" },
32
+ { name: "us-west1 (Oregon)", value: "us-west1", recommended: true },
33
+ { name: "us-west2 (Los Angeles)", value: "us-west2" },
34
+ { name: "us-west3 (Salt Lake City)", value: "us-west3" },
35
+ { name: "us-west4 (Las Vegas)", value: "us-west4" },
36
+ { name: "asia-east1 (Taiwan)", value: "asia-east1", recommended: true },
37
+ { name: "asia-east2 (Hong Kong)", value: "asia-east2" },
38
+ { name: "asia-northeast1 (Tokyo)", value: "asia-northeast1" },
39
+ { name: "asia-northeast2 (Osaka)", value: "asia-northeast2" },
40
+ { name: "asia-northeast3 (Seoul)", value: "asia-northeast3" },
41
+ { name: "asia-south1 (Mumbai)", value: "asia-south1" },
42
+ { name: "asia-south2 (Delhi)", value: "asia-south2" },
43
+ { name: "asia-southeast1 (Singapore)", value: "asia-southeast1" },
44
+ { name: "asia-southeast2 (Jakarta)", value: "asia-southeast2" },
45
+ { name: "australia-southeast1 (Sydney)", value: "australia-southeast1" },
46
+ { name: "australia-southeast2 (Melbourne)", value: "australia-southeast2" },
47
+ { name: "europe-central2 (Warsaw)", value: "europe-central2" },
48
+ { name: "europe-north1 (Finland)", value: "europe-north1" },
49
+ { name: "europe-west1 (Belgium)", value: "europe-west1", recommended: true },
50
+ { name: "europe-west2 (London)", value: "europe-west2" },
51
+ { name: "europe-west3 (Frankfurt)", value: "europe-west3" },
52
+ { name: "europe-west4 (Netherlands)", value: "europe-west4" },
53
+ { name: "europe-west6 (Zurich)", value: "europe-west6" },
54
+ { name: "northamerica-northeast1 (Montreal)", value: "northamerica-northeast1" },
55
+ { name: "northamerica-northeast2 (Toronto)", value: "northamerica-northeast2" },
56
+ { name: "southamerica-east1 (São Paulo)", value: "southamerica-east1" },
57
+ { name: "southamerica-west1 (Santiago)", value: "southamerica-west1" },
34
58
  ];
35
59
  exports.I18N_ROOT = "/";
36
60
  function GET_DEFAULT_BUILD_TARGETS() {
@@ -119,7 +119,7 @@ async function prepareFrameworks(purpose, targetNames, context, options, emulato
119
119
  throw new error_1.FirebaseError(`Hosting config for site ${site} places server-side content in region ${ssrRegion} which is not known. Valid regions are ${validRegions}`);
120
120
  }
121
121
  const getProjectPath = (...args) => (0, path_1.join)(projectRoot, source, ...args);
122
- const functionId = `ssr${site.toLowerCase().replace(/-/g, "")}`;
122
+ const functionId = `ssr${site.toLowerCase().replace(/-/g, "").substring(0, 20)}`;
123
123
  const usesFirebaseAdminSdk = !!(0, utils_1.findDependency)("firebase-admin", { cwd: getProjectPath() });
124
124
  const usesFirebaseJsSdk = !!(0, utils_1.findDependency)("@firebase/app", { cwd: getProjectPath() });
125
125
  if (usesFirebaseAdminSdk) {
@@ -190,8 +190,8 @@ async function prepareFrameworks(purpose, targetNames, context, options, emulato
190
190
  throw new error_1.FirebaseError((0, utils_1.frameworksCallToAction)("Unable to detect the web framework in use, check firebase-debug.log for more info."));
191
191
  }
192
192
  const { framework, mayWantBackend, publicDirectory } = results;
193
- const { build, ɵcodegenPublicDirectory, ɵcodegenFunctionsDirectory: codegenProdModeFunctionsDirectory, getDevModeHandle, name, support, docsUrl, getValidBuildTargets = constants_2.GET_DEFAULT_BUILD_TARGETS, shouldUseDevModeHandle = constants_2.DEFAULT_SHOULD_USE_DEV_MODE_HANDLE, } = frameworks_1.WebFrameworks[framework];
194
- logger_1.logger.info(`\n${(0, utils_1.frameworksCallToAction)(constants_2.SupportLevelWarnings[support](name), docsUrl, " ")}\n`);
193
+ const { build, ɵcodegenPublicDirectory, ɵcodegenFunctionsDirectory: codegenProdModeFunctionsDirectory, getDevModeHandle, name, support, docsUrl, supportedRange, getValidBuildTargets = constants_2.GET_DEFAULT_BUILD_TARGETS, shouldUseDevModeHandle = constants_2.DEFAULT_SHOULD_USE_DEV_MODE_HANDLE, } = frameworks_1.WebFrameworks[framework];
194
+ logger_1.logger.info(`\n${(0, utils_1.frameworksCallToAction)(constants_2.SupportLevelWarnings[support](name), docsUrl, " ", name, results.version, supportedRange, results.vite)}\n`);
195
195
  const hostingEmulatorInfo = emulators.find((e) => e.name === types_1.Emulators.HOSTING);
196
196
  const validBuildTargets = await getValidBuildTargets(purpose, getProjectPath());
197
197
  const frameworksBuildTarget = (0, utils_1.getFrameworksBuildTarget)(purpose, validBuildTargets);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getDevModeHandle = exports.ɵcodegenFunctionsDirectory = exports.ɵcodegenPublicDirectory = exports.init = exports.build = exports.discover = exports.docsUrl = exports.type = exports.support = exports.name = void 0;
3
+ exports.getDevModeHandle = exports.ɵcodegenFunctionsDirectory = exports.ɵcodegenPublicDirectory = exports.init = exports.build = exports.discover = exports.docsUrl = exports.type = exports.support = exports.name = exports.supportedRange = void 0;
4
4
  const child_process_1 = require("child_process");
5
5
  const cross_spawn_1 = require("cross-spawn");
6
6
  const promises_1 = require("fs/promises");
@@ -25,6 +25,7 @@ const api_1 = require("../../hosting/api");
25
25
  const logger_1 = require("../../logger");
26
26
  const DEFAULT_BUILD_SCRIPT = ["next build"];
27
27
  const PUBLIC_DIR = "public";
28
+ exports.supportedRange = "12 - 14.0";
28
29
  exports.name = "Next.js";
29
30
  exports.support = "preview";
30
31
  exports.type = 2;
@@ -38,9 +39,10 @@ function getReactVersion(cwd) {
38
39
  async function discover(dir) {
39
40
  if (!(await (0, fs_extra_1.pathExists)((0, path_1.join)(dir, "package.json"))))
40
41
  return;
41
- if (!(await (0, fs_extra_1.pathExists)("next.config.js")) && !(0, utils_2.getNextVersion)(dir))
42
+ const version = (0, utils_2.getNextVersion)(dir);
43
+ if (!(await (0, fs_extra_1.pathExists)("next.config.js")) && !version)
42
44
  return;
43
- return { mayWantBackend: true, publicDirectory: (0, path_1.join)(dir, PUBLIC_DIR) };
45
+ return { mayWantBackend: true, publicDirectory: (0, path_1.join)(dir, PUBLIC_DIR), version };
44
46
  }
45
47
  exports.discover = discover;
46
48
  async function build(dir) {
@@ -186,7 +188,7 @@ async function init(setup, config) {
186
188
  message: "What language would you like to use?",
187
189
  choices: ["JavaScript", "TypeScript"],
188
190
  });
189
- (0, child_process_1.execSync)(`npx --yes create-next-app@latest -e hello-world ${setup.hosting.source} --use-npm ${language === "TypeScript" ? "--ts" : "--js"}`, { stdio: "inherit", cwd: config.projectDir });
191
+ (0, child_process_1.execSync)(`npx --yes create-next-app@"${exports.supportedRange}" -e hello-world ${setup.hosting.source} --use-npm ${language === "TypeScript" ? "--ts" : "--js"}`, { stdio: "inherit", cwd: config.projectDir });
190
192
  }
191
193
  exports.init = init;
192
194
  async function ɵcodegenPublicDirectory(sourceDir, destDir, _, context) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getConfig = exports.getDevModeHandle = exports.ɵcodegenFunctionsDirectory = exports.ɵcodegenPublicDirectory = exports.build = exports.discover = exports.type = exports.support = exports.name = void 0;
3
+ exports.init = exports.getConfig = exports.getDevModeHandle = exports.ɵcodegenFunctionsDirectory = exports.ɵcodegenPublicDirectory = exports.build = exports.discover = exports.supportedRange = exports.type = exports.support = exports.name = void 0;
4
4
  const fs_extra_1 = require("fs-extra");
5
5
  const promises_1 = require("fs/promises");
6
6
  const path_1 = require("path");
@@ -11,20 +11,22 @@ const utils_2 = require("./utils");
11
11
  exports.name = "Nuxt";
12
12
  exports.support = "experimental";
13
13
  exports.type = 4;
14
+ exports.supportedRange = "3";
14
15
  const utils_3 = require("./utils");
15
16
  const error_1 = require("../../error");
17
+ const child_process_1 = require("child_process");
16
18
  const DEFAULT_BUILD_SCRIPT = ["nuxt build", "nuxi build"];
17
19
  async function discover(dir) {
18
20
  if (!(await (0, fs_extra_1.pathExists)((0, path_1.join)(dir, "package.json"))))
19
21
  return;
20
22
  const anyConfigFileExists = await (0, utils_3.nuxtConfigFilesExist)(dir);
21
- const nuxtVersion = (0, utils_2.getNuxtVersion)(dir);
22
- if (!anyConfigFileExists && !nuxtVersion)
23
+ const version = (0, utils_2.getNuxtVersion)(dir);
24
+ if (!anyConfigFileExists && !version)
23
25
  return;
24
- if (nuxtVersion && (0, semver_1.lt)(nuxtVersion, "3.0.0-0"))
26
+ if (version && (0, semver_1.lt)(version, "3.0.0-0"))
25
27
  return;
26
28
  const { dir: { public: publicDirectory }, ssr: mayWantBackend, } = await getConfig(dir);
27
- return { publicDirectory, mayWantBackend };
29
+ return { publicDirectory, mayWantBackend, version };
28
30
  }
29
31
  exports.discover = discover;
30
32
  async function build(cwd) {
@@ -90,3 +92,11 @@ async function getConfig(dir) {
90
92
  return await loadNuxtConfig(dir);
91
93
  }
92
94
  exports.getConfig = getConfig;
95
+ function init(setup, config) {
96
+ (0, child_process_1.execSync)(`npx --yes nuxi@"${exports.supportedRange}" init ${setup.hosting.source}`, {
97
+ stdio: "inherit",
98
+ cwd: config.projectDir,
99
+ });
100
+ return Promise.resolve();
101
+ }
102
+ exports.init = init;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getDevModeHandle = exports.ɵcodegenFunctionsDirectory = exports.ɵcodegenPublicDirectory = exports.build = exports.discover = exports.type = exports.support = exports.name = void 0;
3
+ exports.getDevModeHandle = exports.ɵcodegenFunctionsDirectory = exports.ɵcodegenPublicDirectory = exports.build = exports.discover = exports.supportedRange = exports.type = exports.support = exports.name = void 0;
4
4
  const fs_extra_1 = require("fs-extra");
5
5
  const promises_1 = require("fs/promises");
6
6
  const path_1 = require("path");
@@ -12,6 +12,7 @@ const cross_spawn_1 = require("cross-spawn");
12
12
  exports.name = "Nuxt";
13
13
  exports.support = "experimental";
14
14
  exports.type = 2;
15
+ exports.supportedRange = "2";
15
16
  async function getAndLoadNuxt(options) {
16
17
  const nuxt = await (0, utils_1.relativeRequire)(options.rootDir, "nuxt/dist/nuxt.js");
17
18
  const app = await nuxt.loadNuxt(options);
@@ -21,11 +22,11 @@ async function getAndLoadNuxt(options) {
21
22
  async function discover(rootDir) {
22
23
  if (!(await (0, fs_extra_1.pathExists)((0, path_1.join)(rootDir, "package.json"))))
23
24
  return;
24
- const nuxtVersion = (0, utils_2.getNuxtVersion)(rootDir);
25
- if (!nuxtVersion || (nuxtVersion && (0, semver_1.gte)(nuxtVersion, "3.0.0-0")))
25
+ const version = (0, utils_2.getNuxtVersion)(rootDir);
26
+ if (!version || (version && (0, semver_1.gte)(version, "3.0.0-0")))
26
27
  return;
27
28
  const { app } = await getAndLoadNuxt({ rootDir, for: "build" });
28
- return { mayWantBackend: true, publicDirectory: app.options.dir.static };
29
+ return { mayWantBackend: true, publicDirectory: app.options.dir.static, version };
29
30
  }
30
31
  exports.discover = discover;
31
32
  async function build(rootDir) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ɵcodegenFunctionsDirectory = exports.ɵcodegenPublicDirectory = exports.build = exports.getDevModeHandle = exports.discover = exports.type = exports.support = exports.name = void 0;
3
+ exports.ɵcodegenFunctionsDirectory = exports.ɵcodegenPublicDirectory = exports.build = exports.supportedRange = exports.getDevModeHandle = exports.discover = exports.type = exports.support = exports.name = void 0;
4
4
  const fs_extra_1 = require("fs-extra");
5
5
  const path_1 = require("path");
6
6
  const vite_1 = require("../vite");
@@ -12,6 +12,7 @@ exports.type = 2;
12
12
  exports.discover = (0, vite_1.viteDiscoverWithNpmDependency)("@sveltejs/kit");
13
13
  var vite_2 = require("../vite");
14
14
  Object.defineProperty(exports, "getDevModeHandle", { enumerable: true, get: function () { return vite_2.getDevModeHandle; } });
15
+ Object.defineProperty(exports, "supportedRange", { enumerable: true, get: function () { return vite_2.supportedRange; } });
15
16
  async function build(root) {
16
17
  var _a;
17
18
  const config = await getConfig(root);
@@ -7,6 +7,7 @@ const promises_1 = require("fs/promises");
7
7
  const http_1 = require("http");
8
8
  const cross_spawn_1 = require("cross-spawn");
9
9
  const clc = require("colorette");
10
+ const semver_1 = require("semver");
10
11
  const logger_1 = require("../logger");
11
12
  const error_1 = require("../error");
12
13
  const fsutils_1 = require("../fsutils");
@@ -206,20 +207,23 @@ function relativeRequire(dir, mod) {
206
207
  }
207
208
  }
208
209
  exports.relativeRequire = relativeRequire;
209
- function conjoinOptions(opts, conjunction = "and", separator = ",") {
210
- if (!opts.length)
211
- return;
210
+ function conjoinOptions(_opts, conjunction = "and", separator = ",") {
211
+ if (!_opts.length)
212
+ return "";
213
+ const opts = _opts.map((it) => it.toString().trim());
212
214
  if (opts.length === 1)
213
- return opts[0].toString();
215
+ return opts[0];
214
216
  if (opts.length === 2)
215
- return `${opts[0].toString()} ${conjunction} ${opts[1].toString()}`;
216
- const lastElement = opts.slice(-1)[0].toString();
217
- const allButLast = opts.slice(0, -1).map((it) => it.toString());
217
+ return `${opts[0]} ${conjunction} ${opts[1]}`;
218
+ const lastElement = opts.slice(-1)[0];
219
+ const allButLast = opts.slice(0, -1);
218
220
  return `${allButLast.join(`${separator} `)}${separator} ${conjunction} ${lastElement}`;
219
221
  }
220
222
  exports.conjoinOptions = conjoinOptions;
221
- function frameworksCallToAction(message, docsUrl = constants_1.DEFAULT_DOCS_URL, prefix = "") {
222
- return `${prefix}${message}
223
+ function frameworksCallToAction(message, docsUrl = constants_1.DEFAULT_DOCS_URL, prefix = "", framework, version, supportedRange, vite = false) {
224
+ return `${prefix}${message}${framework && supportedRange && (!version || !(0, semver_1.satisfies)(version, supportedRange))
225
+ ? clc.yellow(`\n${prefix}The integration is known to work with ${vite ? "Vite" : framework} version ${clc.italic(conjoinOptions(supportedRange.split("||")))}. You may encounter errors.`)
226
+ : ``}
223
227
 
224
228
  ${prefix}${clc.bold("Documentation:")} ${docsUrl}
225
229
  ${prefix}${clc.bold("File a bug:")} ${constants_1.FILE_BUG_URL}
@@ -1,16 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getDevModeHandle = exports.ɵcodegenPublicDirectory = exports.build = exports.discover = exports.vitePluginDiscover = exports.viteDiscoverWithNpmDependency = exports.init = exports.initViteTemplate = exports.DEFAULT_BUILD_SCRIPT = exports.type = exports.support = exports.name = void 0;
3
+ exports.getDevModeHandle = exports.ɵcodegenPublicDirectory = exports.build = exports.discover = exports.vitePluginDiscover = exports.viteDiscoverWithNpmDependency = exports.init = exports.initViteTemplate = exports.DEFAULT_BUILD_SCRIPT = exports.supportedRange = exports.type = exports.support = exports.name = void 0;
4
4
  const child_process_1 = require("child_process");
5
5
  const cross_spawn_1 = require("cross-spawn");
6
6
  const fs_1 = require("fs");
7
7
  const fs_extra_1 = require("fs-extra");
8
8
  const path_1 = require("path");
9
+ const strip_ansi_1 = require("strip-ansi");
9
10
  const prompt_1 = require("../../prompt");
10
11
  const utils_1 = require("../utils");
11
12
  exports.name = "Vite";
12
13
  exports.support = "experimental";
13
14
  exports.type = 4;
15
+ exports.supportedRange = "3 - 5";
14
16
  exports.DEFAULT_BUILD_SCRIPT = ["vite build", "tsc && vite build"];
15
17
  const initViteTemplate = (template) => async (setup, config) => await init(setup, config, template);
16
18
  exports.initViteTemplate = initViteTemplate;
@@ -24,7 +26,7 @@ async function init(setup, config, baseTemplate = "vanilla") {
24
26
  { name: "TypeScript", value: `${baseTemplate}-ts` },
25
27
  ],
26
28
  });
27
- (0, child_process_1.execSync)(`npm create vite@latest ${setup.hosting.source} --yes -- --template ${template}`, {
29
+ (0, child_process_1.execSync)(`npm create vite@"${exports.supportedRange}" ${setup.hosting.source} --yes -- --template ${template}`, {
28
30
  stdio: "inherit",
29
31
  cwd: config.projectDir,
30
32
  });
@@ -36,6 +38,7 @@ exports.viteDiscoverWithNpmDependency = viteDiscoverWithNpmDependency;
36
38
  const vitePluginDiscover = (plugin) => async (dir) => await discover(dir, plugin);
37
39
  exports.vitePluginDiscover = vitePluginDiscover;
38
40
  async function discover(dir, plugin, npmDependency) {
41
+ var _a;
39
42
  if (!(0, fs_1.existsSync)((0, path_1.join)(dir, "package.json")))
40
43
  return;
41
44
  const additionalDep = npmDependency && (0, utils_1.findDependency)(npmDependency, { cwd: dir, depth: 0, omitDev: false });
@@ -45,14 +48,24 @@ async function discover(dir, plugin, npmDependency) {
45
48
  (0, fs_extra_1.pathExists)((0, path_1.join)(dir, "vite.config.ts")),
46
49
  ]);
47
50
  const anyConfigFileExists = configFilesExist.some((it) => it);
48
- if (!anyConfigFileExists && !(0, utils_1.findDependency)("vite", { cwd: dir, depth, omitDev: false }))
51
+ const version = (_a = (0, utils_1.findDependency)("vite", {
52
+ cwd: dir,
53
+ depth,
54
+ omitDev: false,
55
+ })) === null || _a === void 0 ? void 0 : _a.version;
56
+ if (!anyConfigFileExists && !version)
49
57
  return;
50
58
  if (npmDependency && !additionalDep)
51
59
  return;
52
60
  const { appType, publicDir: publicDirectory, plugins } = await getConfig(dir);
53
61
  if (plugin && !plugins.find(({ name }) => name === plugin))
54
62
  return;
55
- return { mayWantBackend: appType !== "spa", publicDirectory };
63
+ return {
64
+ mayWantBackend: appType !== "spa",
65
+ publicDirectory,
66
+ version,
67
+ vite: true,
68
+ };
56
69
  }
57
70
  exports.discover = discover;
58
71
  async function build(root) {
@@ -77,7 +90,8 @@ async function getDevModeHandle(dir) {
77
90
  const serve = (0, cross_spawn_1.spawn)(cli, [], { cwd: dir });
78
91
  serve.stdout.on("data", (data) => {
79
92
  process.stdout.write(data);
80
- const match = data.toString().match(/(http:\/\/.+:\d+)/);
93
+ const dataWithoutAnsiCodes = (0, strip_ansi_1.default)(data.toString());
94
+ const match = dataWithoutAnsiCodes.match(/(http:\/\/.+:\d+)/);
81
95
  if (match)
82
96
  resolve(match[1]);
83
97
  });
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deleteRepository = exports.getRepository = exports.createRepository = exports.fetchLinkableRepositories = exports.deleteConnection = exports.listConnections = exports.getConnection = exports.createConnection = void 0;
3
+ exports.serviceAgentEmail = exports.deleteRepository = exports.getRepository = exports.createRepository = exports.fetchLinkableRepositories = exports.deleteConnection = exports.listConnections = exports.getConnection = exports.createConnection = void 0;
4
4
  const apiv2_1 = require("../apiv2");
5
5
  const api_1 = require("../api");
6
6
  const PAGE_SIZE_MAX = 100;
@@ -69,3 +69,7 @@ async function deleteRepository(projectId, location, connectionId, repositoryId)
69
69
  return res.body;
70
70
  }
71
71
  exports.deleteRepository = deleteRepository;
72
+ function serviceAgentEmail(projectNumber) {
73
+ return `service-${projectNumber}@gcp-sa-cloudbuild.iam.gserviceaccount.com`;
74
+ }
75
+ exports.serviceAgentEmail = serviceAgentEmail;
@@ -1,8 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createBuild = exports.deleteBackend = exports.listBackends = exports.getBackend = exports.createBackend = exports.API_VERSION = void 0;
3
+ exports.ensureApiEnabled = exports.createBuild = exports.deleteBackend = exports.listBackends = exports.getBackend = exports.createBackend = exports.API_VERSION = exports.API_HOST = void 0;
4
4
  const apiv2_1 = require("../apiv2");
5
+ const projectUtils_1 = require("../projectUtils");
5
6
  const api_1 = require("../api");
7
+ const ensureApiEnabled_1 = require("../ensureApiEnabled");
8
+ exports.API_HOST = new URL(api_1.frameworksOrigin).host;
6
9
  exports.API_VERSION = "v1alpha";
7
10
  const client = new apiv2_1.Client({
8
11
  urlPrefix: api_1.frameworksOrigin,
@@ -38,3 +41,8 @@ async function createBuild(projectId, location, backendId, buildInput) {
38
41
  return res.body;
39
42
  }
40
43
  exports.createBuild = createBuild;
44
+ async function ensureApiEnabled(options) {
45
+ const projectId = (0, projectUtils_1.needProjectId)(options);
46
+ return await (0, ensureApiEnabled_1.ensure)(projectId, exports.API_HOST, "frameworks", true);
47
+ }
48
+ exports.ensureApiEnabled = ensureApiEnabled;
@@ -1,17 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createBackend = exports.getOrCreateBackend = exports.doSetup = void 0;
3
+ exports.createBackend = exports.onboardBackend = exports.doSetup = void 0;
4
4
  const clc = require("colorette");
5
- const utils = require("../../../utils");
6
5
  const repo = require("./repo");
7
6
  const poller = require("../../../operation-poller");
8
7
  const gcp = require("../../../gcp/frameworks");
8
+ const utils_1 = require("../../../utils");
9
9
  const api_1 = require("../../../api");
10
10
  const frameworks_1 = require("../../../gcp/frameworks");
11
11
  const error_1 = require("../../../error");
12
- const logger_1 = require("../../../logger");
13
12
  const prompt_1 = require("../../../prompt");
14
13
  const constants_1 = require("./constants");
14
+ const ensureApiEnabled_1 = require("../../../ensureApiEnabled");
15
15
  const frameworksPollerOptions = {
16
16
  apiOrigin: api_1.frameworksOrigin,
17
17
  apiVersion: frameworks_1.API_VERSION,
@@ -20,36 +20,52 @@ const frameworksPollerOptions = {
20
20
  };
21
21
  async function doSetup(setup, projectId) {
22
22
  setup.frameworks = {};
23
- utils.logBullet("First we need a few details to create your backend.");
24
- await (0, prompt_1.promptOnce)({
25
- name: "serviceName",
26
- type: "input",
27
- default: "acme-inc-web",
28
- message: "Create a name for your backend [1-30 characters]",
29
- }, setup.frameworks);
30
- await (0, prompt_1.promptOnce)({
23
+ await Promise.all([
24
+ (0, ensureApiEnabled_1.ensure)(projectId, "cloudbuild.googleapis.com", "frameworks", true),
25
+ (0, ensureApiEnabled_1.ensure)(projectId, "secretmanager.googleapis.com", "frameworks", true),
26
+ (0, ensureApiEnabled_1.ensure)(projectId, "run.googleapis.com", "frameworks", true),
27
+ (0, ensureApiEnabled_1.ensure)(projectId, "artifactregistry.googleapis.com", "frameworks", true),
28
+ ]);
29
+ (0, utils_1.logBullet)("First we need a few details to create your backend.");
30
+ const location = await (0, prompt_1.promptOnce)({
31
31
  name: "region",
32
32
  type: "list",
33
33
  default: constants_1.DEFAULT_REGION,
34
34
  message: "Please select a region " +
35
35
  `(${clc.yellow("info")}: Your region determines where your backend is located):\n`,
36
36
  choices: constants_1.ALLOWED_REGIONS,
37
- }, setup.frameworks);
38
- utils.logSuccess(`Region set to ${setup.frameworks.region}.`);
39
- const backend = await getOrCreateBackend(projectId, setup);
37
+ });
38
+ (0, utils_1.logSuccess)(`Region set to ${location}.\n`);
39
+ let backendId;
40
+ while (true) {
41
+ backendId = await (0, prompt_1.promptOnce)({
42
+ name: "backendId",
43
+ type: "input",
44
+ default: "acme-inc-web",
45
+ message: "Create a name for your backend [1-30 characters]",
46
+ });
47
+ try {
48
+ await gcp.getBackend(projectId, location, backendId);
49
+ }
50
+ catch (err) {
51
+ if (err.status === 404) {
52
+ break;
53
+ }
54
+ throw new error_1.FirebaseError(`Failed to check if backend with id ${backendId} already exists in ${location}`, { original: err });
55
+ }
56
+ (0, utils_1.logWarning)(`Backend with id ${backendId} already exists in ${location}`);
57
+ }
58
+ const backend = await onboardBackend(projectId, location, backendId);
40
59
  if (backend) {
41
- logger_1.logger.info();
42
- utils.logSuccess(`Successfully created backend:\n ${backend.name}`);
43
- logger_1.logger.info();
44
- utils.logSuccess(`Your site is being deployed at:\n https://${backend.uri}`);
45
- logger_1.logger.info();
46
- utils.logSuccess(`View the rollout status by running:\n firebase backends:get --backend=${backend.name}`);
47
- logger_1.logger.info();
60
+ (0, utils_1.logSuccess)(`Successfully created backend:\n\t${backend.name}`);
61
+ (0, utils_1.logSuccess)(`Your site is being deployed at:\n\thttps://${backend.uri}`);
62
+ (0, utils_1.logSuccess)(`View the rollout status by running:\n\tfirebase backends:get ${backendId} --project ${projectId}`);
48
63
  }
49
64
  }
50
65
  exports.doSetup = doSetup;
51
66
  function toBackend(cloudBuildConnRepo) {
52
67
  return {
68
+ servingLocality: "GLOBAL_ACCESS",
53
69
  codebase: {
54
70
  repository: `${cloudBuildConnRepo.name}`,
55
71
  rootDirectory: "/",
@@ -57,57 +73,19 @@ function toBackend(cloudBuildConnRepo) {
57
73
  labels: {},
58
74
  };
59
75
  }
60
- async function getOrCreateBackend(projectId, setup) {
61
- const location = setup.frameworks.region;
62
- try {
63
- return await getExistingBackend(projectId, setup, location);
64
- }
65
- catch (err) {
66
- if (err.status === 404) {
67
- const cloudBuildConnRepo = await repo.linkGitHubRepository(projectId, location);
68
- logger_1.logger.info();
69
- await (0, prompt_1.promptOnce)({
70
- name: "branchName",
71
- type: "input",
72
- default: "main",
73
- message: "Which branch do you want to deploy?",
74
- }, setup.frameworks);
75
- const backendDetails = toBackend(cloudBuildConnRepo);
76
- logger_1.logger.info(clc.bold(`\n${clc.white("===")} Creating your backend`));
77
- return await createBackend(projectId, location, backendDetails, setup.frameworks.serviceName);
78
- }
79
- else {
80
- throw new error_1.FirebaseError(`Failed to get or create a backend using the given initialization details: ${err}`);
81
- }
82
- }
83
- return undefined;
84
- }
85
- exports.getOrCreateBackend = getOrCreateBackend;
86
- async function getExistingBackend(projectId, setup, location) {
87
- let backend = await gcp.getBackend(projectId, location, setup.frameworks.serviceName);
88
- while (backend) {
89
- setup.frameworks.serviceName = undefined;
90
- await (0, prompt_1.promptOnce)({
91
- name: "existingBackend",
92
- type: "confirm",
93
- default: true,
94
- message: "A backend already exists for the given serviceName, do you want to use existing backend? (yes/no)",
95
- }, setup.frameworks);
96
- if (setup.frameworks.existingBackend) {
97
- logger_1.logger.info("Using the existing backend.");
98
- return backend;
99
- }
100
- await (0, prompt_1.promptOnce)({
101
- name: "serviceName",
102
- type: "input",
103
- default: "acme-inc-web",
104
- message: "Please enter a new service name [1-30 characters]",
105
- }, setup.frameworks);
106
- backend = await gcp.getBackend(projectId, location, setup.frameworks.serviceName);
107
- setup.frameworks.existingBackend = undefined;
108
- }
109
- return backend;
76
+ async function onboardBackend(projectId, location, backendId) {
77
+ const cloudBuildConnRepo = await repo.linkGitHubRepository(projectId, location);
78
+ const barnchName = await (0, prompt_1.promptOnce)({
79
+ name: "branchName",
80
+ type: "input",
81
+ default: "main",
82
+ message: "Which branch do you want to deploy?",
83
+ });
84
+ void barnchName;
85
+ const backendDetails = toBackend(cloudBuildConnRepo);
86
+ return await createBackend(projectId, location, backendDetails, backendId);
110
87
  }
88
+ exports.onboardBackend = onboardBackend;
111
89
  async function createBackend(projectId, location, backendReqBoby, backendId) {
112
90
  const op = await gcp.createBackend(projectId, location, backendReqBoby, backendId);
113
91
  const backend = await poller.pollOperation(Object.assign(Object.assign({}, frameworksPollerOptions), { pollerName: `create-${projectId}-${location}-${backendId}`, operationResourceName: op.name }));
@@ -3,12 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.listFrameworksConnections = exports.getOrCreateRepository = exports.getOrCreateConnection = exports.createConnection = exports.linkGitHubRepository = exports.parseConnectionName = void 0;
4
4
  const clc = require("colorette");
5
5
  const gcb = require("../../../gcp/cloudbuild");
6
+ const rm = require("../../../gcp/resourceManager");
6
7
  const poller = require("../../../operation-poller");
7
8
  const utils = require("../../../utils");
8
9
  const api_1 = require("../../../api");
9
10
  const error_1 = require("../../../error");
10
- const logger_1 = require("../../../logger");
11
11
  const prompt_1 = require("../../../prompt");
12
+ const getProjectNumber_1 = require("../../../getProjectNumber");
12
13
  const FRAMEWORKS_CONN_PATTERN = /.+\/frameworks-github-conn-.+$/;
13
14
  const FRAMEWORKS_OAUTH_CONN_NAME = "frameworks-github-oauth";
14
15
  const CONNECTION_NAME_REGEX = /^projects\/(?<projectId>[^\/]+)\/locations\/(?<location>[^\/]+)\/connections\/(?<id>[^\/]+)$/;
@@ -48,9 +49,13 @@ function generateConnectionId() {
48
49
  }
49
50
  async function linkGitHubRepository(projectId, location) {
50
51
  var _a, _b, _c;
51
- logger_1.logger.info(clc.bold(`\n${clc.yellow("===")} Connect a GitHub repository`));
52
+ utils.logBullet(clc.bold(`${clc.yellow("===")} Set up a GitHub connection`));
52
53
  const existingConns = await listFrameworksConnections(projectId);
53
54
  if (existingConns.length < 1) {
55
+ const grantSuccess = await promptSecretManagerAdminGrant(projectId);
56
+ if (!grantSuccess) {
57
+ throw new error_1.FirebaseError("Insufficient IAM permissions to create a new connection to GitHub");
58
+ }
54
59
  let oauthConn = await getOrCreateConnection(projectId, location, FRAMEWORKS_OAUTH_CONN_NAME);
55
60
  while (oauthConn.installationState.stage === "PENDING_USER_OAUTH") {
56
61
  oauthConn = await promptConnectionAuth(oauthConn);
@@ -82,8 +87,8 @@ async function linkGitHubRepository(projectId, location) {
82
87
  appInstallationId: (_c = connection.githubConfig) === null || _c === void 0 ? void 0 : _c.appInstallationId,
83
88
  });
84
89
  const repo = await getOrCreateRepository(projectId, location, connectionId, remoteUri);
85
- logger_1.logger.info();
86
- utils.logSuccess(`Successfully linked GitHub repository at remote URI:\n ${remoteUri}`);
90
+ utils.logSuccess(`Successfully linked GitHub repository at remote URI`);
91
+ utils.logSuccess(`\t${remoteUri}`);
87
92
  return repo;
88
93
  }
89
94
  exports.linkGitHubRepository = linkGitHubRepository;
@@ -92,7 +97,7 @@ async function promptRepositoryUri(projectId, location, connections) {
92
97
  for (const conn of connections) {
93
98
  const { id } = parseConnectionName(conn.name);
94
99
  const resp = await gcb.fetchLinkableRepositories(projectId, location, id);
95
- if (resp.repositories && resp.repositories.length > 1) {
100
+ if (resp.repositories && resp.repositories.length > 0) {
96
101
  for (const repo of resp.repositories) {
97
102
  remoteUriToConnection[repo.remoteUri] = conn;
98
103
  }
@@ -113,11 +118,35 @@ async function promptRepositoryUri(projectId, location, connections) {
113
118
  });
114
119
  return { remoteUri, connection: remoteUriToConnection[remoteUri] };
115
120
  }
121
+ async function promptSecretManagerAdminGrant(projectId) {
122
+ const projectNumber = await (0, getProjectNumber_1.getProjectNumber)({ projectId });
123
+ const cbsaEmail = gcb.serviceAgentEmail(projectNumber);
124
+ const alreadyGranted = await rm.serviceAccountHasRoles(projectId, cbsaEmail, ["roles/secretmanager.admin"], true);
125
+ if (alreadyGranted) {
126
+ return true;
127
+ }
128
+ utils.logBullet("To create a new GitHub connection, Secret Manager Admin role (roles/secretmanager.admin) is required on the Cloud Build Service Agent.");
129
+ const grant = await (0, prompt_1.promptOnce)({
130
+ type: "confirm",
131
+ message: "Grant the required role to the Cloud Build Service Agent?",
132
+ });
133
+ if (!grant) {
134
+ utils.logBullet("You, or your project administrator, should run the following command to grant the required role:\n\n" +
135
+ "You, or your project adminstrator, can run the following command to grant the required role manually:\n\n" +
136
+ `\tgcloud projects add-iam-policy-binding ${projectId} \\\n` +
137
+ `\t --member="serviceAccount:${cbsaEmail} \\\n` +
138
+ `\t --role="roles/secretmanager.admin\n`);
139
+ return false;
140
+ }
141
+ await rm.addServiceAccountToRoles(projectId, cbsaEmail, ["roles/secretmanager.admin"], true);
142
+ utils.logSuccess("Successfully granted the required role to the Cloud Build Service Agent!");
143
+ return true;
144
+ }
116
145
  async function promptConnectionAuth(conn) {
117
- logger_1.logger.info("You must authorize the Cloud Build GitHub app.");
118
- logger_1.logger.info();
119
- logger_1.logger.info("First, sign in to GitHub and authorize Cloud Build GitHub app:");
120
- const cleanup = await utils.openInBrowserPopup(conn.installationState.actionUri, "Authorize the GitHub app");
146
+ utils.logBullet("You must authorize the Cloud Build GitHub app.");
147
+ utils.logBullet("Sign in to GitHub and authorize Cloud Build GitHub app:");
148
+ const { url, cleanup } = await utils.openInBrowserPopup(conn.installationState.actionUri, "Authorize the GitHub app");
149
+ utils.logBullet(`\t${url}`);
121
150
  await (0, prompt_1.promptOnce)({
122
151
  type: "input",
123
152
  message: "Press Enter once you have authorized the app",
@@ -127,9 +156,9 @@ async function promptConnectionAuth(conn) {
127
156
  return await gcb.getConnection(projectId, location, id);
128
157
  }
129
158
  async function promptAppInstall(conn) {
130
- logger_1.logger.info("Now, install the Cloud Build GitHub app:");
159
+ utils.logBullet("Install the Cloud Build GitHub app to enable access to GitHub repositories");
131
160
  const targetUri = conn.installationState.actionUri.replace("install_v2", "direct_install_v2");
132
- logger_1.logger.info(targetUri);
161
+ utils.logBullet(targetUri);
133
162
  await utils.openInBrowser(targetUri);
134
163
  await (0, prompt_1.promptOnce)({
135
164
  type: "input",
@@ -168,10 +197,6 @@ async function getOrCreateRepository(projectId, location, connectionId, remoteUr
168
197
  let repo;
169
198
  try {
170
199
  repo = await gcb.getRepository(projectId, location, connectionId, repositoryId);
171
- const repoSlug = extractRepoSlugFromUri(repo.remoteUri);
172
- if (repoSlug) {
173
- throw new error_1.FirebaseError(`${repoSlug} has already been linked.`);
174
- }
175
200
  }
176
201
  catch (err) {
177
202
  if (err.status === 404) {
@@ -124,7 +124,7 @@ async function doSetup(setup, config, options) {
124
124
  type: "list",
125
125
  message: "In which region would you like to host server-side content, if applicable?",
126
126
  default: constants_1.DEFAULT_REGION,
127
- choices: constants_1.ALLOWED_SSR_REGIONS,
127
+ choices: constants_1.ALLOWED_SSR_REGIONS.filter((region) => region.recommended),
128
128
  }, setup.hosting);
129
129
  setup.config.hosting = {
130
130
  source: setup.hosting.source,
package/lib/utils.js CHANGED
@@ -506,10 +506,12 @@ async function openInBrowserPopup(url, buttonText) {
506
506
  });
507
507
  server.listen(port);
508
508
  const popupPageUri = `http://localhost:${port}`;
509
- logger_1.logger.info(popupPageUri);
510
509
  await openInBrowser(popupPageUri);
511
- return () => {
512
- server.close();
510
+ return {
511
+ url: popupPageUri,
512
+ cleanup: () => {
513
+ server.close();
514
+ },
513
515
  };
514
516
  }
515
517
  exports.openInBrowserPopup = openInBrowserPopup;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "13.0.0",
3
+ "version": "13.0.1",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {