firebase-tools 14.21.0 → 14.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,6 +15,7 @@ const error_1 = require("../error");
15
15
  const colorette_1 = require("colorette");
16
16
  const interactive_1 = require("../hosting/interactive");
17
17
  const utils_1 = require("../utils");
18
+ const api_1 = require("../hosting/api");
18
19
  exports.VALID_DEPLOY_TARGETS = [
19
20
  "database",
20
21
  "storage",
@@ -107,7 +108,7 @@ exports.command = new command_1.Command("deploy")
107
108
  await (0, requireDatabaseInstance_1.requireDatabaseInstance)(options);
108
109
  }
109
110
  if (options.filteredTargets.includes("hosting")) {
110
- let createSite = false;
111
+ let shouldCreateSite = false;
111
112
  try {
112
113
  await (0, requireHostingSite_1.requireHostingSite)(options);
113
114
  }
@@ -119,17 +120,18 @@ exports.command = new command_1.Command("deploy")
119
120
  throw err;
120
121
  }
121
122
  else if (err === getDefaultHostingSite_1.errNoDefaultSite) {
122
- createSite = true;
123
+ shouldCreateSite = true;
123
124
  }
124
125
  }
125
- if (!createSite) {
126
+ if (!shouldCreateSite) {
126
127
  return;
127
128
  }
128
129
  if (options.nonInteractive) {
129
130
  throw new error_1.FirebaseError(`Unable to deploy to Hosting as there is no Hosting site. Use ${(0, colorette_1.bold)("firebase hosting:sites:create")} to create a site.`);
130
131
  }
131
132
  (0, utils_1.logBullet)("No Hosting site detected.");
132
- await (0, interactive_1.interactiveCreateHostingSite)("", "", options);
133
+ const siteId = await (0, interactive_1.pickHostingSiteName)("", options);
134
+ await (0, api_1.createSite)(options.project, siteId);
133
135
  }
134
136
  })
135
137
  .before(checkValidTargetFilters_1.checkValidTargetFilters)
@@ -8,6 +8,7 @@ const utils_1 = require("../utils");
8
8
  const logger_1 = require("../logger");
9
9
  const projectUtils_1 = require("../projectUtils");
10
10
  const requirePermissions_1 = require("../requirePermissions");
11
+ const api_1 = require("../hosting/api");
11
12
  const error_1 = require("../error");
12
13
  const LOG_TAG = "hosting:sites";
13
14
  exports.command = new command_1.Command("hosting:sites:create [siteId]")
@@ -18,10 +19,10 @@ exports.command = new command_1.Command("hosting:sites:create [siteId]")
18
19
  const projectId = (0, projectUtils_1.needProjectId)(options);
19
20
  const appId = options.app;
20
21
  if (options.nonInteractive && !siteId) {
21
- throw new error_1.FirebaseError(`${(0, colorette_1.bold)(siteId)} is required in a non-interactive environment`);
22
+ throw new error_1.FirebaseError(`${(0, colorette_1.bold)("siteId")} is required in a non-interactive environment`);
22
23
  }
23
- const site = await (0, interactive_1.interactiveCreateHostingSite)(siteId, appId, options);
24
- siteId = (0, utils_1.last)(site.name.split("/"));
24
+ siteId = await (0, interactive_1.pickHostingSiteName)(siteId !== null && siteId !== void 0 ? siteId : "", options);
25
+ const site = await (0, api_1.createSite)(projectId, siteId, appId);
25
26
  logger_1.logger.info();
26
27
  (0, utils_1.logLabeledSuccess)(LOG_TAG, `Site ${(0, colorette_1.bold)(siteId)} has been created in project ${(0, colorette_1.bold)(projectId)}.`);
27
28
  if (appId) {
@@ -168,18 +168,9 @@ async function resolveParams(params, firebaseConfig, userEnvs, nonInteractive, i
168
168
  paramValues[param.name] = userEnvs[param.name];
169
169
  }
170
170
  const [needSecret, needPrompt] = (0, functional_1.partition)(outstanding, (param) => param.type === "secret");
171
- if (nonInteractive && needSecret.length > 0) {
172
- const secretNames = needSecret.map((p) => p.name).join(", ");
173
- const commands = needSecret
174
- .map((p) => `\tfirebase functions:secrets:set ${p.name}${p.format === "json" ? " --format=json --data-file <file.json>" : ""}`)
175
- .join("\n");
176
- throw new error_1.FirebaseError(`In non-interactive mode but have no value for the following secrets: ${secretNames}\n\n` +
177
- "Set these secrets before deploying:\n" +
178
- commands);
179
- }
180
171
  if (!isEmulator) {
181
172
  for (const param of needSecret) {
182
- await handleSecret(param, firebaseConfig.projectId);
173
+ await handleSecret(param, firebaseConfig.projectId, nonInteractive);
183
174
  }
184
175
  }
185
176
  if (nonInteractive && needPrompt.length > 0) {
@@ -230,9 +221,14 @@ function populateDefaultParams(config) {
230
221
  }
231
222
  return defaultParams;
232
223
  }
233
- async function handleSecret(secretParam, projectId) {
224
+ async function handleSecret(secretParam, projectId, nonInteractive) {
234
225
  const metadata = await secretManager.getSecretMetadata(projectId, secretParam.name, "latest");
235
226
  if (!metadata.secret) {
227
+ if (nonInteractive) {
228
+ throw new error_1.FirebaseError(`In non-interactive mode but have no value for the secret: ${secretParam.name}\n\n` +
229
+ "Set this secret before deploying:\n" +
230
+ `\tfirebase functions:secrets:set ${secretParam.name}${secretParam.format === "json" ? " --format=json --data-file <file.json>" : ""}`);
231
+ }
236
232
  const promptMessage = `This secret will be stored in Cloud Secret Manager (https://cloud.google.com/secret-manager/pricing) as ${secretParam.name}. Enter ${secretParam.format === "json" ? "a JSON value" : "a value"} for ${secretParam.label || secretParam.name}:`;
237
233
  const secretValue = await (0, prompt_1.password)({
238
234
  message: promptMessage,
@@ -26,7 +26,7 @@ async function discover(dir) {
26
26
  }
27
27
  exports.discover = discover;
28
28
  function init(setup, config) {
29
- (0, child_process_1.execSync)(`npx --yes -p @angular/cli@"${exports.supportedRange}" 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.featureInfo.hosting.source} --skip-git`, {
30
30
  stdio: "inherit",
31
31
  cwd: config.projectDir,
32
32
  });
@@ -34,7 +34,7 @@ function init(setup, config) {
34
34
  `--project-name=${projectName}`,
35
35
  "--overwrite",
36
36
  "--platforms=web",
37
- setup.hosting.source,
37
+ setup.featureInfo.hosting.source,
38
38
  ], { stdio: "inherit", cwd: config.projectDir });
39
39
  if (result.status !== 0)
40
40
  throw new error_1.FirebaseError("We were not able to create your flutter app, create the application yourself https://docs.flutter.dev/get-started/test-drive?tab=terminal before trying again.");
@@ -214,7 +214,7 @@ async function init(setup, config) {
214
214
  ],
215
215
  });
216
216
  (0, child_process_1.execSync)(`npx --yes create-next-app@"${exports.supportedRange}" -e hello-world ` +
217
- `${setup.hosting.source} --use-npm --${language}`, { stdio: "inherit", cwd: config.projectDir });
217
+ `${setup.featureInfo.hosting.source} --use-npm --${language}`, { stdio: "inherit", cwd: config.projectDir });
218
218
  }
219
219
  exports.init = init;
220
220
  async function ɵcodegenPublicDirectory(sourceDir, destDir, _, context) {
@@ -93,7 +93,7 @@ async function getConfig(cwd) {
93
93
  }
94
94
  exports.getConfig = getConfig;
95
95
  function init(setup, config) {
96
- (0, child_process_1.execSync)(`npx --yes nuxi@"${exports.supportedRange}" init ${setup.hosting.source}`, {
96
+ (0, child_process_1.execSync)(`npx --yes nuxi@"${exports.supportedRange}" init ${setup.featureInfo.hosting.source}`, {
97
97
  stdio: "inherit",
98
98
  cwd: config.projectDir,
99
99
  });
@@ -25,11 +25,14 @@ async function init(setup, config, baseTemplate = "vanilla") {
25
25
  { name: "TypeScript", value: `${baseTemplate}-ts` },
26
26
  ],
27
27
  });
28
- (0, child_process_1.execSync)(`npm create vite@"${exports.supportedRange}" ${setup.hosting.source} --yes -- --template ${template}`, {
28
+ (0, child_process_1.execSync)(`npm create vite@"${exports.supportedRange}" ${setup.featureInfo.hosting.source} --yes -- --template ${template}`, {
29
29
  stdio: "inherit",
30
30
  cwd: config.projectDir,
31
31
  });
32
- (0, child_process_1.execSync)(`npm install`, { stdio: "inherit", cwd: (0, path_1.join)(config.projectDir, setup.hosting.source) });
32
+ (0, child_process_1.execSync)(`npm install`, {
33
+ stdio: "inherit",
34
+ cwd: (0, path_1.join)(config.projectDir, setup.featureInfo.hosting.source),
35
+ });
33
36
  }
34
37
  exports.init = init;
35
38
  const viteDiscoverWithNpmDependency = (dep) => async (dir) => await discover(dir, undefined, dep);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.interactiveCreateHostingSite = void 0;
3
+ exports.pickHostingSiteName = void 0;
4
4
  const error_1 = require("../error");
5
5
  const utils_1 = require("../utils");
6
6
  const projectUtils_1 = require("../projectUtils");
@@ -9,11 +9,11 @@ const prompt_1 = require("../prompt");
9
9
  const nameSuggestion = new RegExp("try something like `(.+)`");
10
10
  const prompt = "Please provide an unique, URL-friendly id for your site. Your site's URL will be <site-id>.web.app. " +
11
11
  'We recommend using letters, numbers, and hyphens (e.g. "{project-id}-{random-hash}"):';
12
- async function interactiveCreateHostingSite(siteId, appId, options) {
12
+ async function pickHostingSiteName(siteId, options) {
13
13
  const projectId = (0, projectUtils_1.needProjectId)(options);
14
14
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
15
15
  let id = siteId;
16
- let newSite;
16
+ let nameConfirmed = false;
17
17
  let suggestion;
18
18
  if (!id) {
19
19
  const attempt = await trySiteID(projectNumber, projectId);
@@ -24,7 +24,7 @@ async function interactiveCreateHostingSite(siteId, appId, options) {
24
24
  suggestion = attempt.suggestion;
25
25
  }
26
26
  }
27
- while (!newSite) {
27
+ while (!nameConfirmed) {
28
28
  if (!id || suggestion) {
29
29
  id = await (0, prompt_1.input)({
30
30
  message: prompt,
@@ -32,24 +32,16 @@ async function interactiveCreateHostingSite(siteId, appId, options) {
32
32
  default: suggestion,
33
33
  });
34
34
  }
35
- try {
36
- newSite = await (0, api_1.createSite)(projectNumber, id, appId);
37
- }
38
- catch (err) {
39
- if (!(err instanceof error_1.FirebaseError)) {
40
- throw err;
41
- }
42
- if (options.nonInteractive) {
43
- throw err;
44
- }
35
+ const attempt = await trySiteID(projectNumber, id, options.nonInteractive);
36
+ nameConfirmed = attempt.available;
37
+ suggestion = attempt.suggestion;
38
+ if (!nameConfirmed)
45
39
  id = "";
46
- suggestion = getSuggestionFromError(err);
47
- }
48
40
  }
49
- return newSite;
41
+ return id;
50
42
  }
51
- exports.interactiveCreateHostingSite = interactiveCreateHostingSite;
52
- async function trySiteID(projectNumber, id) {
43
+ exports.pickHostingSiteName = pickHostingSiteName;
44
+ async function trySiteID(projectNumber, id, nonInteractive = false) {
53
45
  try {
54
46
  await (0, api_1.createSite)(projectNumber, id, "", true);
55
47
  return { available: true };
@@ -58,6 +50,9 @@ async function trySiteID(projectNumber, id) {
58
50
  if (!(err instanceof error_1.FirebaseError)) {
59
51
  throw err;
60
52
  }
53
+ if (nonInteractive) {
54
+ throw err;
55
+ }
61
56
  const suggestion = getSuggestionFromError(err);
62
57
  return { available: false, suggestion };
63
58
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.doSetup = void 0;
3
+ exports.actuate = exports.askQuestions = void 0;
4
4
  const clc = require("colorette");
5
5
  const node_fs_1 = require("node:fs");
6
6
  const path_1 = require("path");
@@ -15,13 +15,16 @@ const getDefaultHostingSite_1 = require("../../../getDefaultHostingSite");
15
15
  const utils_1 = require("../../../utils");
16
16
  const interactive_1 = require("../../../hosting/interactive");
17
17
  const templates_1 = require("../../../templates");
18
+ const error_1 = require("../../../error");
19
+ const api_1 = require("../../../hosting/api");
18
20
  const INDEX_TEMPLATE = (0, templates_1.readTemplateSync)("init/hosting/index.html");
19
21
  const MISSING_TEMPLATE = (0, templates_1.readTemplateSync)("init/hosting/404.html");
20
22
  const DEFAULT_IGNORES = ["firebase.json", "**/.*", "**/node_modules/**"];
21
- async function doSetup(setup, config, options) {
22
- var _a, _b, _c, _d;
23
- var _e, _f, _g;
24
- setup.hosting = {};
23
+ async function askQuestions(setup, config, options) {
24
+ var _a, _b, _c, _d, _e, _f;
25
+ var _g, _h, _j, _k, _l;
26
+ setup.featureInfo = setup.featureInfo || {};
27
+ setup.featureInfo.hosting = {};
25
28
  if (setup.projectId) {
26
29
  let hasHostingSite = true;
27
30
  try {
@@ -33,61 +36,50 @@ async function doSetup(setup, config, options) {
33
36
  }
34
37
  hasHostingSite = false;
35
38
  }
36
- if (!hasHostingSite) {
37
- const confirmCreate = await (0, prompt_1.confirm)({
39
+ if (!hasHostingSite &&
40
+ (await (0, prompt_1.confirm)({
38
41
  message: "A Firebase Hosting site is required to deploy. Would you like to create one now?",
39
42
  default: true,
40
- });
41
- if (confirmCreate) {
42
- const createOptions = {
43
- projectId: setup.projectId,
44
- nonInteractive: options.nonInteractive,
45
- };
46
- const newSite = await (0, interactive_1.interactiveCreateHostingSite)("", "", createOptions);
47
- logger_1.logger.info();
48
- (0, utils_1.logSuccess)(`Firebase Hosting site ${(0, utils_1.last)(newSite.name.split("/"))} created!`);
49
- logger_1.logger.info();
50
- }
43
+ }))) {
44
+ const createOptions = {
45
+ projectId: setup.projectId,
46
+ nonInteractive: options.nonInteractive,
47
+ };
48
+ setup.featureInfo.hosting.newSiteId = await (0, interactive_1.pickHostingSiteName)("", createOptions);
51
49
  }
52
50
  }
53
- let discoveredFramework = experiments.isEnabled("webframeworks")
54
- ? await (0, frameworks_1.discover)(config.projectDir, false)
55
- : undefined;
56
51
  if (experiments.isEnabled("webframeworks")) {
57
- if (discoveredFramework) {
58
- const name = frameworks_1.WebFrameworks[discoveredFramework.framework].name;
59
- (_a = (_e = setup.hosting).useDiscoveredFramework) !== null && _a !== void 0 ? _a : (_e.useDiscoveredFramework = await (0, prompt_1.confirm)({
60
- message: `Detected an existing ${name} codebase in the current directory, should we use this?`,
52
+ let discoveredFramework = experiments.isEnabled("webframeworks")
53
+ ? await (0, frameworks_1.discover)(config.projectDir, false)
54
+ : undefined;
55
+ if (discoveredFramework &&
56
+ (await (0, prompt_1.confirm)({
57
+ message: `Detected an existing ${frameworks_1.WebFrameworks[discoveredFramework.framework].name} codebase in the current directory, do you want to use this?`,
61
58
  default: true,
62
- }));
63
- }
64
- if (setup.hosting.useDiscoveredFramework) {
65
- setup.hosting.source = ".";
66
- setup.hosting.useWebFrameworks = true;
59
+ }))) {
60
+ setup.featureInfo.hosting.source = ".";
61
+ setup.featureInfo.hosting.useWebFrameworks = true;
62
+ setup.featureInfo.hosting.useDiscoveredFramework = true;
63
+ setup.featureInfo.hosting.webFramework = discoveredFramework.framework;
67
64
  }
68
65
  else {
69
- setup.hosting.useWebFrameworks = await (0, prompt_1.confirm)(`Do you want to use a web framework? (${clc.bold("experimental")})`);
66
+ setup.featureInfo.hosting.useWebFrameworks = await (0, prompt_1.confirm)(`Do you want to use a web framework? (${clc.bold("experimental")})`);
70
67
  }
71
- }
72
- if (setup.hosting.useWebFrameworks) {
73
- (_b = (_f = setup.hosting).source) !== null && _b !== void 0 ? _b : (_f.source = await (0, prompt_1.input)({
74
- message: "What folder would you like to use for your web application's root directory?",
75
- default: "hosting",
76
- }));
77
- if (setup.hosting.source !== ".")
78
- delete setup.hosting.useDiscoveredFramework;
79
- discoveredFramework = await (0, frameworks_1.discover)((0, path_1.join)(config.projectDir, setup.hosting.source));
80
- if (discoveredFramework) {
81
- const name = frameworks_1.WebFrameworks[discoveredFramework.framework].name;
82
- (_c = (_g = setup.hosting).useDiscoveredFramework) !== null && _c !== void 0 ? _c : (_g.useDiscoveredFramework = await (0, prompt_1.confirm)({
83
- message: `Detected an existing ${name} codebase in ${setup.hosting.source}, should we use this?`,
84
- default: true,
68
+ if (setup.featureInfo.hosting.useWebFrameworks) {
69
+ (_a = (_g = setup.featureInfo.hosting).source) !== null && _a !== void 0 ? _a : (_g.source = await (0, prompt_1.input)({
70
+ message: "What folder would you like to use for your web application's root directory?",
71
+ default: "hosting",
85
72
  }));
86
- }
87
- if (setup.hosting.useDiscoveredFramework && discoveredFramework) {
88
- setup.hosting.webFramework = discoveredFramework.framework;
89
- }
90
- else {
73
+ discoveredFramework = await (0, frameworks_1.discover)((0, path_1.join)(config.projectDir, setup.featureInfo.hosting.source));
74
+ if (discoveredFramework) {
75
+ const name = frameworks_1.WebFrameworks[discoveredFramework.framework].name;
76
+ (_b = (_h = setup.featureInfo.hosting).useDiscoveredFramework) !== null && _b !== void 0 ? _b : (_h.useDiscoveredFramework = await (0, prompt_1.confirm)({
77
+ message: `Detected an existing ${name} codebase in ${setup.featureInfo.hosting.source}, should we use this?`,
78
+ default: true,
79
+ }));
80
+ if (setup.featureInfo.hosting.useDiscoveredFramework)
81
+ setup.featureInfo.hosting.webFramework = discoveredFramework.framework;
82
+ }
91
83
  const choices = [];
92
84
  for (const value in frameworks_1.WebFrameworks) {
93
85
  if (frameworks_1.WebFrameworks[value]) {
@@ -96,32 +88,20 @@ async function doSetup(setup, config, options) {
96
88
  choices.push({ name, value });
97
89
  }
98
90
  }
99
- const defaultChoice = (_d = choices.find(({ value }) => value === (discoveredFramework === null || discoveredFramework === void 0 ? void 0 : discoveredFramework.framework))) === null || _d === void 0 ? void 0 : _d.value;
100
- setup.hosting.whichFramework =
101
- setup.hosting.whichFramework ||
91
+ const defaultChoice = (_c = choices.find(({ value }) => value === (discoveredFramework === null || discoveredFramework === void 0 ? void 0 : discoveredFramework.framework))) === null || _c === void 0 ? void 0 : _c.value;
92
+ (_d = (_j = setup.featureInfo.hosting).webFramework) !== null && _d !== void 0 ? _d : (_j.webFramework = await (0, prompt_1.select)({
93
+ message: "Please choose the framework:",
94
+ default: defaultChoice,
95
+ choices,
96
+ }));
97
+ setup.featureInfo.hosting.region =
98
+ setup.featureInfo.hosting.region ||
102
99
  (await (0, prompt_1.select)({
103
- message: "Please choose the framework:",
104
- default: defaultChoice,
105
- choices,
100
+ message: "In which region would you like to host server-side content, if applicable?",
101
+ default: constants_1.DEFAULT_REGION,
102
+ choices: constants_1.ALLOWED_SSR_REGIONS.filter((region) => region.recommended),
106
103
  }));
107
- if (discoveredFramework)
108
- (0, node_fs_1.rmSync)(setup.hosting.source, { recursive: true });
109
- await frameworks_1.WebFrameworks[setup.hosting.whichFramework].init(setup, config);
110
104
  }
111
- setup.hosting.region =
112
- setup.hosting.region ||
113
- (await (0, prompt_1.select)({
114
- message: "In which region would you like to host server-side content, if applicable?",
115
- default: constants_1.DEFAULT_REGION,
116
- choices: constants_1.ALLOWED_SSR_REGIONS.filter((region) => region.recommended),
117
- }));
118
- setup.config.hosting = {
119
- source: setup.hosting.source,
120
- ignore: DEFAULT_IGNORES,
121
- frameworksBackend: {
122
- region: setup.hosting.region,
123
- },
124
- };
125
105
  }
126
106
  else {
127
107
  logger_1.logger.info();
@@ -129,35 +109,58 @@ async function doSetup(setup, config, options) {
129
109
  logger_1.logger.info(`will contain Hosting assets to be uploaded with ${clc.bold("firebase deploy")}. If you`);
130
110
  logger_1.logger.info("have a build process for your assets, use your build's output directory.");
131
111
  logger_1.logger.info();
132
- setup.hosting.public =
133
- setup.hosting.public ||
134
- (await (0, prompt_1.input)({
135
- message: "What do you want to use as your public directory?",
136
- default: "public",
137
- }));
138
- setup.hosting.spa =
139
- setup.hosting.spa ||
140
- (await (0, prompt_1.confirm)("Configure as a single-page app (rewrite all urls to /index.html)?"));
112
+ (_e = (_k = setup.featureInfo.hosting).public) !== null && _e !== void 0 ? _e : (_k.public = await (0, prompt_1.input)({
113
+ message: "What do you want to use as your public directory?",
114
+ default: "public",
115
+ }));
116
+ (_f = (_l = setup.featureInfo.hosting).spa) !== null && _f !== void 0 ? _f : (_l.spa = await (0, prompt_1.confirm)("Configure as a single-page app (rewrite all urls to /index.html)?"));
117
+ }
118
+ if (await (0, prompt_1.confirm)("Set up automatic builds and deploys with GitHub?")) {
119
+ return (0, github_1.initGitHub)(setup);
120
+ }
121
+ }
122
+ exports.askQuestions = askQuestions;
123
+ async function actuate(setup, config, options) {
124
+ var _a;
125
+ const hostingInfo = (_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.hosting;
126
+ if (!hostingInfo) {
127
+ throw new error_1.FirebaseError("Could not find hosting info in setup.featureInfo.hosting. This should not happen.", { exit: 2 });
128
+ }
129
+ if (hostingInfo.newSiteId && setup.projectId) {
130
+ await (0, api_1.createSite)(setup.projectId, hostingInfo.newSiteId);
131
+ logger_1.logger.info();
132
+ (0, utils_1.logSuccess)(`Firebase Hosting site ${hostingInfo.newSiteId} created!`);
133
+ logger_1.logger.info();
134
+ }
135
+ if (hostingInfo.webFramework) {
136
+ if (!hostingInfo.useDiscoveredFramework) {
137
+ if (hostingInfo.source && (0, node_fs_1.existsSync)(hostingInfo.source)) {
138
+ (0, node_fs_1.rmSync)(hostingInfo.source, { recursive: true });
139
+ }
140
+ await frameworks_1.WebFrameworks[hostingInfo.webFramework].init(setup, config);
141
+ }
141
142
  setup.config.hosting = {
142
- public: setup.hosting.public,
143
+ source: hostingInfo.source,
143
144
  ignore: DEFAULT_IGNORES,
145
+ frameworksBackend: {
146
+ region: hostingInfo.region,
147
+ },
144
148
  };
145
149
  }
146
- setup.hosting.github =
147
- setup.hosting.github || (await (0, prompt_1.confirm)("Set up automatic builds and deploys with GitHub?"));
148
- if (!setup.hosting.useWebFrameworks) {
149
- if (setup.hosting.spa) {
150
+ else {
151
+ setup.config.hosting = {
152
+ public: hostingInfo.public,
153
+ ignore: DEFAULT_IGNORES,
154
+ };
155
+ if (hostingInfo.spa) {
150
156
  setup.config.hosting.rewrites = [{ source: "**", destination: "/index.html" }];
151
157
  }
152
158
  else {
153
- await config.askWriteProjectFile(`${setup.hosting.public}/404.html`, MISSING_TEMPLATE);
159
+ await config.askWriteProjectFile(`${hostingInfo.public}/404.html`, MISSING_TEMPLATE, !!options.force);
154
160
  }
155
161
  const c = new apiv2_1.Client({ urlPrefix: "https://www.gstatic.com", auth: false });
156
162
  const response = await c.get("/firebasejs/releases.json");
157
- await config.askWriteProjectFile(`${setup.hosting.public}/index.html`, INDEX_TEMPLATE.replace(/{{VERSION}}/g, response.body.current.version));
158
- }
159
- if (setup.hosting.github) {
160
- return (0, github_1.initGitHub)(setup);
163
+ await config.askWriteProjectFile(`${hostingInfo.public}/index.html`, INDEX_TEMPLATE.replace(/{{VERSION}}/g, response.body.current.version), !!options.force);
161
164
  }
162
165
  }
163
- exports.doSetup = doSetup;
166
+ exports.actuate = actuate;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.aiLogicActuate = exports.aiLogicAskQuestions = exports.aitools = exports.apptestingAcutate = exports.apptestingAskQuestions = exports.genkit = exports.apphosting = exports.dataconnectSdkActuate = exports.dataconnectSdkAskQuestions = exports.dataconnectActuate = exports.dataconnectAskQuestions = exports.hostingGithub = exports.remoteconfig = exports.project = exports.extensions = exports.emulators = exports.storageActuate = exports.storageAskQuestions = exports.hosting = exports.functions = exports.firestoreActuate = exports.firestoreAskQuestions = exports.databaseActuate = exports.databaseAskQuestions = exports.account = void 0;
3
+ exports.aiLogicActuate = exports.aiLogicAskQuestions = exports.aitools = exports.apptestingAcutate = exports.apptestingAskQuestions = exports.genkit = exports.apphosting = exports.dataconnectSdkActuate = exports.dataconnectSdkAskQuestions = exports.dataconnectActuate = exports.dataconnectAskQuestions = exports.hostingGithub = exports.remoteconfig = exports.project = exports.extensions = exports.emulators = exports.storageActuate = exports.storageAskQuestions = exports.hostingActuate = exports.hostingAskQuestions = exports.functions = exports.firestoreActuate = exports.firestoreAskQuestions = exports.databaseActuate = exports.databaseAskQuestions = exports.account = void 0;
4
4
  var account_1 = require("./account");
5
5
  Object.defineProperty(exports, "account", { enumerable: true, get: function () { return account_1.doSetup; } });
6
6
  var database_1 = require("./database");
@@ -12,7 +12,8 @@ Object.defineProperty(exports, "firestoreActuate", { enumerable: true, get: func
12
12
  var functions_1 = require("./functions");
13
13
  Object.defineProperty(exports, "functions", { enumerable: true, get: function () { return functions_1.doSetup; } });
14
14
  var hosting_1 = require("./hosting");
15
- Object.defineProperty(exports, "hosting", { enumerable: true, get: function () { return hosting_1.doSetup; } });
15
+ Object.defineProperty(exports, "hostingAskQuestions", { enumerable: true, get: function () { return hosting_1.askQuestions; } });
16
+ Object.defineProperty(exports, "hostingActuate", { enumerable: true, get: function () { return hosting_1.actuate; } });
16
17
  var storage_1 = require("./storage");
17
18
  Object.defineProperty(exports, "storageAskQuestions", { enumerable: true, get: function () { return storage_1.askQuestions; } });
18
19
  Object.defineProperty(exports, "storageActuate", { enumerable: true, get: function () { return storage_1.actuate; } });
package/lib/init/index.js CHANGED
@@ -30,7 +30,11 @@ const featuresList = [
30
30
  actuate: features.dataconnectSdkActuate,
31
31
  },
32
32
  { name: "functions", doSetup: features.functions },
33
- { name: "hosting", doSetup: features.hosting },
33
+ {
34
+ name: "hosting",
35
+ askQuestions: features.hostingAskQuestions,
36
+ actuate: features.hostingActuate,
37
+ },
34
38
  {
35
39
  name: "storage",
36
40
  askQuestions: features.storageAskQuestions,
@@ -7,7 +7,7 @@ exports.init_backend = (0, resource_1.resource)({
7
7
  name: "backend_init_guide",
8
8
  title: "Firebase Backend Init Guide",
9
9
  description: "guides the coding agent through configuring Firebase backend services in the current project",
10
- }, async (uri) => {
10
+ }, async (uri, ctx) => {
11
11
  return {
12
12
  contents: [
13
13
  {
@@ -24,31 +24,8 @@ The user will likely need to setup Firestore, Authentication, and Hosting. Read
24
24
  3. [Firestore Rules](firebase://guides/init/firestore_rules): read this to setup the \`firestore.rules\` file for securing your database
25
25
  4. [Hosting](firebase://guides/init/hosting): read this if the user would like to deploy to Firebase Hosting
26
26
 
27
- **firebase.json**
28
- The firebase.json file is used to deploy Firebase products with the firebase deploy command.
29
-
30
- Here is an example firebase.json file with Firebase Hosting, Firestore, and Cloud Functions. Note that you do not need entries for services that the user isn't using. Do not remove sections from the user's firebase.json unless the user gives explicit permission. For more information, refer to [firebase.json file documentation](https://firebase.google.com/docs/cli/#the_firebasejson_file)
31
- \`\`\`json
32
- {
33
- "hosting": {
34
- "public": "public",
35
- "ignore": [
36
- "firebase.json",
37
- "**/.*",
38
- "**/node_modules/**"
39
- ]
40
- },
41
- "firestore": {
42
- "rules": "firestore.rules",
43
- "indexes": "firestore.indexes.json"
44
- },
45
- "functions": {
46
- "predeploy": [
47
- "npm --prefix "$RESOURCE_DIR" run lint",
48
- "npm --prefix "$RESOURCE_DIR" run build"
49
- ]
50
- }
51
- }
27
+ Once you are done setting up, ask the user if they would like to deploy.
28
+ If they say yes, run the command '${ctx.firebaseCliCommand} deploy --non-interactive' to do so.
52
29
  \`\`\`
53
30
  `.trim(),
54
31
  },
@@ -1,13 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.init_hosting = void 0;
4
+ const getDefaultHostingSite_1 = require("../../../getDefaultHostingSite");
4
5
  const resource_1 = require("../../resource");
5
6
  exports.init_hosting = (0, resource_1.resource)({
6
7
  uri: "firebase://guides/init/hosting",
7
8
  name: "hosting_init_guide",
8
9
  title: "Firebase Hosting Deployment Guide",
9
10
  description: "guides the coding agent through deploying to Firebase Hosting in the current project",
10
- }, async (uri) => {
11
+ }, async (uri, ctx) => {
12
+ const defaultHostingSite = await (0, getDefaultHostingSite_1.getDefaultHostingSite)(ctx);
11
13
  return {
12
14
  contents: [
13
15
  {
@@ -15,18 +17,21 @@ exports.init_hosting = (0, resource_1.resource)({
15
17
  type: "text",
16
18
  text: `
17
19
  ### Configure Firebase Hosting
20
+ Default hosting site for ${ctx.projectId}: ${defaultHostingSite || "Does not exist"}
21
+ If there is not a default hosting site configured, ask the user what the site ID should be, and suggest ${ctx.projectId} as a good choice.
22
+ Next, use the 'firebase_init' tool to set up hosting. Below is an example of what the arguments to do so look like;
23
+ however, you should change the values to match the user's choices and project structure:
24
+ {
25
+ features: {
26
+ hosting: {
27
+ site_id: ${ctx.projectId},
28
+ public_directory: public,
29
+ }
30
+ }
31
+ }
18
32
 
19
33
  **Security Warning:**
20
34
  - Files included in the public folder of a hosting site are publicly accessible. Do not include sensitive API keys for services other than Firebase in these files.
21
-
22
- **When to Deploy:**
23
- - Introduce Firebase Hosting when developers are ready to deploy their application to production.
24
- - Alternative: Developers can deploy later using the \`/firebase:deploy\` command.
25
-
26
- **Deployment Process:**
27
- - Request developer's permission before implementing Firebase Hosting
28
- - Request developer's permission before deploying Firebase Hosting app to production.
29
- - Configure Firebase Hosting and deploy the application to production
30
35
  `.trim(),
31
36
  },
32
37
  ],
@@ -115,6 +115,25 @@ exports.init = (0, tool_1.tool)("core", {
115
115
  })
116
116
  .optional()
117
117
  .describe("Enable Firebase AI Logic feature for existing app"),
118
+ hosting: zod_1.z
119
+ .object({
120
+ site_id: zod_1.z
121
+ .string()
122
+ .optional()
123
+ .describe("The ID of the hosting site to configure. If omitted and there is a default hosting site, that will be used."),
124
+ public_directory: zod_1.z
125
+ .string()
126
+ .optional()
127
+ .default("public")
128
+ .describe("The directory containing public files that will be served. If using a build tool, this likely should be the output directory of that tool."),
129
+ single_page_app: zod_1.z
130
+ .boolean()
131
+ .optional()
132
+ .default(false)
133
+ .describe("Configure as a single-page app."),
134
+ })
135
+ .optional()
136
+ .describe("Provide this object to initialize Firebase Hosting in this project directory."),
118
137
  }),
119
138
  }),
120
139
  annotations: {
@@ -185,6 +204,14 @@ exports.init = (0, tool_1.tool)("core", {
185
204
  displayName: appData.displayName,
186
205
  };
187
206
  }
207
+ if (features.hosting) {
208
+ featuresList.push("hosting");
209
+ featureInfo.hosting = {
210
+ newSiteId: features.hosting.site_id,
211
+ public: features.hosting.public_directory,
212
+ spa: features.hosting.single_page_app,
213
+ };
214
+ }
188
215
  const setup = {
189
216
  config: config === null || config === void 0 ? void 0 : config.src,
190
217
  rcfile: rc === null || rc === void 0 ? void 0 : rc.data,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "14.21.0",
3
+ "version": "14.22.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {