firebase-tools 12.9.1 → 13.0.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.
@@ -3,15 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createBackend = exports.getOrCreateBackend = exports.doSetup = void 0;
4
4
  const clc = require("colorette");
5
5
  const utils = require("../../../utils");
6
- const logger_1 = require("../../../logger");
7
- const prompt_1 = require("../../../prompt");
8
- const constants_1 = require("./constants");
9
6
  const repo = require("./repo");
10
7
  const poller = require("../../../operation-poller");
11
- const api_1 = require("../../../api");
12
8
  const gcp = require("../../../gcp/frameworks");
9
+ const api_1 = require("../../../api");
13
10
  const frameworks_1 = require("../../../gcp/frameworks");
14
11
  const error_1 = require("../../../error");
12
+ const logger_1 = require("../../../logger");
13
+ const prompt_1 = require("../../../prompt");
14
+ const constants_1 = require("./constants");
15
15
  const frameworksPollerOptions = {
16
16
  apiOrigin: api_1.frameworksOrigin,
17
17
  apiVersion: frameworks_1.API_VERSION,
@@ -20,12 +20,12 @@ 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 service.");
23
+ utils.logBullet("First we need a few details to create your backend.");
24
24
  await (0, prompt_1.promptOnce)({
25
25
  name: "serviceName",
26
26
  type: "input",
27
27
  default: "acme-inc-web",
28
- message: "Create a name for your service [1-30 characters]",
28
+ message: "Create a name for your backend [1-30 characters]",
29
29
  }, setup.frameworks);
30
30
  await (0, prompt_1.promptOnce)({
31
31
  name: "region",
@@ -36,17 +36,15 @@ async function doSetup(setup, projectId) {
36
36
  choices: constants_1.ALLOWED_REGIONS,
37
37
  }, setup.frameworks);
38
38
  utils.logSuccess(`Region set to ${setup.frameworks.region}.`);
39
- logger_1.logger.info(clc.bold(`\n${clc.white("===")} Deploy Setup`));
40
- await (0, prompt_1.promptOnce)({
41
- name: "deployMethod",
42
- type: "list",
43
- default: constants_1.DEFAULT_DEPLOY_METHOD,
44
- message: "How do you want to deploy",
45
- choices: constants_1.ALLOWED_DEPLOY_METHODS,
46
- }, setup.frameworks);
47
39
  const backend = await getOrCreateBackend(projectId, setup);
48
40
  if (backend) {
49
- utils.logSuccess(`Successfully created a backend: ${backend.name}`);
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();
50
48
  }
51
49
  }
52
50
  exports.doSetup = doSetup;
@@ -61,18 +59,22 @@ function toBackend(cloudBuildConnRepo) {
61
59
  }
62
60
  async function getOrCreateBackend(projectId, setup) {
63
61
  const location = setup.frameworks.region;
64
- const deployMethod = setup.frameworks.deployMethod;
65
62
  try {
66
63
  return await getExistingBackend(projectId, setup, location);
67
64
  }
68
65
  catch (err) {
69
66
  if (err.status === 404) {
70
- logger_1.logger.info("Creating new backend.");
71
- if (deployMethod === "github") {
72
- const cloudBuildConnRepo = await repo.linkGitHubRepository(projectId, location);
73
- const backendDetails = toBackend(cloudBuildConnRepo);
74
- return await createBackend(projectId, location, backendDetails, setup.frameworks.serviceName);
75
- }
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);
76
78
  }
77
79
  else {
78
80
  throw new error_1.FirebaseError(`Failed to get or create a backend using the given initialization details: ${err}`);
@@ -1,20 +1,37 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getOrCreateRepository = exports.getOrCreateConnection = exports.linkGitHubRepository = void 0;
4
- const api_1 = require("../../../api");
5
- const error_1 = require("../../../error");
3
+ exports.listFrameworksConnections = exports.getOrCreateRepository = exports.getOrCreateConnection = exports.createConnection = exports.linkGitHubRepository = exports.parseConnectionName = void 0;
4
+ const clc = require("colorette");
6
5
  const gcb = require("../../../gcp/cloudbuild");
7
- const logger_1 = require("../../../logger");
8
6
  const poller = require("../../../operation-poller");
9
7
  const utils = require("../../../utils");
8
+ const api_1 = require("../../../api");
9
+ const error_1 = require("../../../error");
10
+ const logger_1 = require("../../../logger");
10
11
  const prompt_1 = require("../../../prompt");
12
+ const FRAMEWORKS_CONN_PATTERN = /.+\/frameworks-github-conn-.+$/;
13
+ const FRAMEWORKS_OAUTH_CONN_NAME = "frameworks-github-oauth";
14
+ const CONNECTION_NAME_REGEX = /^projects\/(?<projectId>[^\/]+)\/locations\/(?<location>[^\/]+)\/connections\/(?<id>[^\/]+)$/;
15
+ function parseConnectionName(name) {
16
+ const match = name.match(CONNECTION_NAME_REGEX);
17
+ if (!match || typeof match.groups === undefined) {
18
+ return;
19
+ }
20
+ const { projectId, location, id } = match.groups;
21
+ return {
22
+ projectId,
23
+ location,
24
+ id,
25
+ };
26
+ }
27
+ exports.parseConnectionName = parseConnectionName;
11
28
  const gcbPollerOptions = {
12
29
  apiOrigin: api_1.cloudbuildOrigin,
13
30
  apiVersion: "v2",
14
31
  masterTimeout: 25 * 60 * 1000,
15
32
  maxBackoff: 10000,
16
33
  };
17
- function extractRepoSlugFromURI(remoteUri) {
34
+ function extractRepoSlugFromUri(remoteUri) {
18
35
  const match = /github.com\/(.+).git/.exec(remoteUri);
19
36
  if (!match) {
20
37
  return undefined;
@@ -23,75 +40,123 @@ function extractRepoSlugFromURI(remoteUri) {
23
40
  }
24
41
  function generateRepositoryId(remoteUri) {
25
42
  var _a;
26
- return (_a = extractRepoSlugFromURI(remoteUri)) === null || _a === void 0 ? void 0 : _a.replaceAll("/", "-");
43
+ return (_a = extractRepoSlugFromUri(remoteUri)) === null || _a === void 0 ? void 0 : _a.replaceAll("/", "-");
27
44
  }
28
- function generateConnectionId(location) {
29
- return `frameworks-${location}`;
45
+ function generateConnectionId() {
46
+ const randomHash = Math.random().toString(36).slice(6);
47
+ return `frameworks-github-conn-${randomHash}`;
30
48
  }
31
49
  async function linkGitHubRepository(projectId, location) {
32
- const connectionId = generateConnectionId(location);
33
- await getOrCreateConnection(projectId, location, connectionId);
34
- let remoteUri = await promptRepositoryURI(projectId, location, connectionId);
50
+ var _a, _b, _c;
51
+ logger_1.logger.info(clc.bold(`\n${clc.yellow("===")} Connect a GitHub repository`));
52
+ const existingConns = await listFrameworksConnections(projectId);
53
+ if (existingConns.length < 1) {
54
+ let oauthConn = await getOrCreateConnection(projectId, location, FRAMEWORKS_OAUTH_CONN_NAME);
55
+ while (oauthConn.installationState.stage === "PENDING_USER_OAUTH") {
56
+ oauthConn = await promptConnectionAuth(oauthConn);
57
+ }
58
+ const connectionId = generateConnectionId();
59
+ const conn = await createConnection(projectId, location, connectionId, {
60
+ authorizerCredential: (_a = oauthConn.githubConfig) === null || _a === void 0 ? void 0 : _a.authorizerCredential,
61
+ });
62
+ let refreshedConn = conn;
63
+ while (refreshedConn.installationState.stage !== "COMPLETE") {
64
+ refreshedConn = await promptAppInstall(conn);
65
+ }
66
+ existingConns.push(refreshedConn);
67
+ }
68
+ let { remoteUri, connection } = await promptRepositoryUri(projectId, location, existingConns);
35
69
  while (remoteUri === "") {
36
70
  await utils.openInBrowser("https://github.com/apps/google-cloud-build/installations/new");
37
71
  await (0, prompt_1.promptOnce)({
38
72
  type: "input",
39
73
  message: "Press ENTER once you have finished configuring your installation's access settings.",
40
74
  });
41
- remoteUri = await promptRepositoryURI(projectId, location, connectionId);
75
+ const selection = await promptRepositoryUri(projectId, location, existingConns);
76
+ remoteUri = selection.remoteUri;
77
+ connection = selection.connection;
42
78
  }
79
+ const { id: connectionId } = parseConnectionName(connection.name);
80
+ await getOrCreateConnection(projectId, location, connectionId, {
81
+ authorizerCredential: (_b = connection.githubConfig) === null || _b === void 0 ? void 0 : _b.authorizerCredential,
82
+ appInstallationId: (_c = connection.githubConfig) === null || _c === void 0 ? void 0 : _c.appInstallationId,
83
+ });
43
84
  const repo = await getOrCreateRepository(projectId, location, connectionId, remoteUri);
44
- logger_1.logger.info(`Successfully linked GitHub repository at remote URI ${remoteUri}.`);
85
+ logger_1.logger.info();
86
+ utils.logSuccess(`Successfully linked GitHub repository at remote URI:\n ${remoteUri}`);
45
87
  return repo;
46
88
  }
47
89
  exports.linkGitHubRepository = linkGitHubRepository;
48
- async function promptRepositoryURI(projectId, location, connectionId) {
49
- const resp = await gcb.fetchLinkableRepositories(projectId, location, connectionId);
50
- if (!resp.repositories || resp.repositories.length === 0) {
51
- throw new error_1.FirebaseError("The GitHub App does not have access to any repositories. Please configure " +
52
- "your app installation permissions at https://github.com/settings/installations.");
90
+ async function promptRepositoryUri(projectId, location, connections) {
91
+ const remoteUriToConnection = {};
92
+ for (const conn of connections) {
93
+ const { id } = parseConnectionName(conn.name);
94
+ const resp = await gcb.fetchLinkableRepositories(projectId, location, id);
95
+ if (resp.repositories && resp.repositories.length > 1) {
96
+ for (const repo of resp.repositories) {
97
+ remoteUriToConnection[repo.remoteUri] = conn;
98
+ }
99
+ }
53
100
  }
54
- const choices = resp.repositories.map((repo) => ({
55
- name: extractRepoSlugFromURI(repo.remoteUri) || repo.remoteUri,
56
- value: repo.remoteUri,
101
+ const choices = Object.keys(remoteUriToConnection).map((remoteUri) => ({
102
+ name: extractRepoSlugFromUri(remoteUri) || remoteUri,
103
+ value: remoteUri,
57
104
  }));
58
105
  choices.push({
59
106
  name: "Missing a repo? Select this option to configure your installation's access settings",
60
107
  value: "",
61
108
  });
62
- return await (0, prompt_1.promptOnce)({
109
+ const remoteUri = await (0, prompt_1.promptOnce)({
63
110
  type: "list",
64
- message: "Which of the following repositories would you like to link?",
111
+ message: "Which of the following repositories would you like to deploy?",
65
112
  choices,
66
113
  });
114
+ return { remoteUri, connection: remoteUriToConnection[remoteUri] };
67
115
  }
68
- async function promptConnectionAuth(conn, projectId, location, connectionId) {
69
- logger_1.logger.info(conn.installationState.message);
70
- logger_1.logger.info(conn.installationState.actionUri);
71
- await utils.openInBrowser(conn.installationState.actionUri);
116
+ 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");
72
121
  await (0, prompt_1.promptOnce)({
73
122
  type: "input",
74
- message: "Press any key once you have authorized the app (Cloud Build) to access your GitHub repo.",
123
+ message: "Press Enter once you have authorized the app",
75
124
  });
76
- return await gcb.getConnection(projectId, location, connectionId);
125
+ cleanup();
126
+ const { projectId, location, id } = parseConnectionName(conn.name);
127
+ return await gcb.getConnection(projectId, location, id);
77
128
  }
78
- async function getOrCreateConnection(projectId, location, connectionId) {
129
+ async function promptAppInstall(conn) {
130
+ logger_1.logger.info("Now, install the Cloud Build GitHub app:");
131
+ const targetUri = conn.installationState.actionUri.replace("install_v2", "direct_install_v2");
132
+ logger_1.logger.info(targetUri);
133
+ await utils.openInBrowser(targetUri);
134
+ await (0, prompt_1.promptOnce)({
135
+ type: "input",
136
+ message: "Press Enter once you have installed or configured the Cloud Build GitHub app to access your GitHub repo.",
137
+ });
138
+ const { projectId, location, id } = parseConnectionName(conn.name);
139
+ return await gcb.getConnection(projectId, location, id);
140
+ }
141
+ async function createConnection(projectId, location, connectionId, githubConfig) {
142
+ const op = await gcb.createConnection(projectId, location, connectionId, githubConfig);
143
+ const conn = await poller.pollOperation(Object.assign(Object.assign({}, gcbPollerOptions), { pollerName: `create-${location}-${connectionId}`, operationResourceName: op.name }));
144
+ return conn;
145
+ }
146
+ exports.createConnection = createConnection;
147
+ async function getOrCreateConnection(projectId, location, connectionId, githubConfig) {
79
148
  let conn;
80
149
  try {
81
150
  conn = await gcb.getConnection(projectId, location, connectionId);
82
151
  }
83
152
  catch (err) {
84
153
  if (err.status === 404) {
85
- const op = await gcb.createConnection(projectId, location, connectionId);
86
- conn = await poller.pollOperation(Object.assign(Object.assign({}, gcbPollerOptions), { pollerName: `create-${location}-${connectionId}`, operationResourceName: op.name }));
154
+ conn = await createConnection(projectId, location, connectionId, githubConfig);
87
155
  }
88
156
  else {
89
157
  throw err;
90
158
  }
91
159
  }
92
- while (conn.installationState.stage !== "COMPLETE") {
93
- conn = await promptConnectionAuth(conn, projectId, location, connectionId);
94
- }
95
160
  return conn;
96
161
  }
97
162
  exports.getOrCreateConnection = getOrCreateConnection;
@@ -103,7 +168,7 @@ async function getOrCreateRepository(projectId, location, connectionId, remoteUr
103
168
  let repo;
104
169
  try {
105
170
  repo = await gcb.getRepository(projectId, location, connectionId, repositoryId);
106
- const repoSlug = extractRepoSlugFromURI(repo.remoteUri);
171
+ const repoSlug = extractRepoSlugFromUri(repo.remoteUri);
107
172
  if (repoSlug) {
108
173
  throw new error_1.FirebaseError(`${repoSlug} has already been linked.`);
109
174
  }
@@ -120,3 +185,10 @@ async function getOrCreateRepository(projectId, location, connectionId, remoteUr
120
185
  return repo;
121
186
  }
122
187
  exports.getOrCreateRepository = getOrCreateRepository;
188
+ async function listFrameworksConnections(projectId) {
189
+ const conns = await gcb.listConnections(projectId, "-");
190
+ return conns.filter((conn) => FRAMEWORKS_CONN_PATTERN.test(conn.name) &&
191
+ conn.installationState.stage === "COMPLETE" &&
192
+ !conn.disabled);
193
+ }
194
+ exports.listFrameworksConnections = listFrameworksConnections;
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HTTPS_SENTINEL = void 0;
3
4
  const uuid = require("uuid");
4
- const request = require("request");
5
5
  const encodeFirestoreValue_1 = require("./firestore/encodeFirestoreValue");
6
6
  const utils = require("./utils");
7
+ const apiv2_1 = require("./apiv2");
8
+ exports.HTTPS_SENTINEL = "Request sent to function.";
7
9
  class LocalFunction {
8
10
  constructor(trigger, urls, controller) {
9
11
  this.trigger = trigger;
@@ -27,15 +29,73 @@ class LocalFunction {
27
29
  if (opts.instanceIdToken) {
28
30
  headers["Firebase-Instance-ID-Token"] = opts.instanceIdToken;
29
31
  }
30
- return request.post({
31
- callback: (...args) => this.requestCallBack(...args),
32
- baseUrl: this.url,
33
- uri: "",
34
- body: { data },
35
- json: true,
36
- headers: headers,
32
+ if (!this.url) {
33
+ throw new Error("No URL provided");
34
+ }
35
+ const client = new apiv2_1.Client({ urlPrefix: this.url, auth: false });
36
+ void client
37
+ .post("", data, { headers })
38
+ .then((res) => {
39
+ this.requestCallBack(undefined, res, res.body);
40
+ })
41
+ .catch((err) => {
42
+ this.requestCallBack(err);
37
43
  });
38
44
  }
45
+ constructHttpsFunc() {
46
+ if (!this.url) {
47
+ throw new Error("No URL provided");
48
+ }
49
+ const callClient = new apiv2_1.Client({ urlPrefix: this.url, auth: false });
50
+ const verbFactory = (hasRequestBody, method) => {
51
+ return async (pathOrOptions, options) => {
52
+ const { path, opts } = this.extractArgs(pathOrOptions, options);
53
+ try {
54
+ const res = hasRequestBody
55
+ ? await method(path, opts.body, toClientVerbOptions(opts))
56
+ : await method(path, toClientVerbOptions(opts));
57
+ this.requestCallBack(undefined, res, res.body);
58
+ }
59
+ catch (err) {
60
+ this.requestCallBack(err);
61
+ }
62
+ return exports.HTTPS_SENTINEL;
63
+ };
64
+ };
65
+ const shim = verbFactory(true, (path, json, opts) => {
66
+ const req = Object.assign(opts || {}, {
67
+ path: path,
68
+ body: json,
69
+ method: (opts === null || opts === void 0 ? void 0 : opts.method) || "GET",
70
+ });
71
+ return callClient.request(req);
72
+ });
73
+ const verbs = {
74
+ post: verbFactory(true, (path, json, opts) => callClient.post(path, json, opts)),
75
+ put: verbFactory(true, (path, json, opts) => callClient.put(path, json, opts)),
76
+ patch: verbFactory(true, (path, json, opts) => callClient.patch(path, json, opts)),
77
+ get: verbFactory(false, (path, opts) => callClient.get(path, opts)),
78
+ del: verbFactory(false, (path, opts) => callClient.delete(path, opts)),
79
+ delete: verbFactory(false, (path, opts) => callClient.delete(path, opts)),
80
+ options: verbFactory(false, (path, opts) => callClient.options(path, opts)),
81
+ };
82
+ return Object.assign(shim, verbs);
83
+ }
84
+ extractArgs(pathOrOptions, options) {
85
+ if (!pathOrOptions && !options) {
86
+ return { path: "/", opts: {} };
87
+ }
88
+ if (typeof pathOrOptions === "string") {
89
+ return { path: pathOrOptions, opts: options || {} };
90
+ }
91
+ if (typeof pathOrOptions !== "string" && !!pathOrOptions && !options) {
92
+ return { path: "/", opts: pathOrOptions };
93
+ }
94
+ if (typeof pathOrOptions !== "string" || !options) {
95
+ throw new Error(`Invalid argument combination: Expected a string and/or HttpsOptions, got ${typeof pathOrOptions} and ${typeof options}`);
96
+ }
97
+ return { path: "/", opts: {} };
98
+ }
39
99
  constructAuth(auth, authType) {
40
100
  var _a, _b, _c;
41
101
  if ((auth === null || auth === void 0 ? void 0 : auth.admin) || (auth === null || auth === void 0 ? void 0 : auth.variable)) {
@@ -99,7 +159,7 @@ class LocalFunction {
99
159
  if (err) {
100
160
  return console.warn("\nERROR SENDING REQUEST: " + err);
101
161
  }
102
- const status = response ? response.statusCode + ", " : "";
162
+ const status = response ? response.status + ", " : "";
103
163
  let bodyString = body;
104
164
  if (typeof bodyString === "string") {
105
165
  try {
@@ -207,11 +267,7 @@ class LocalFunction {
207
267
  return (data, opt) => this.constructCallableFunc(data, opt);
208
268
  }
209
269
  else {
210
- return request.defaults({
211
- callback: (...args) => this.requestCallBack(...args),
212
- baseUrl: this.url,
213
- uri: "",
214
- });
270
+ return this.constructHttpsFunc();
215
271
  }
216
272
  }
217
273
  else {
@@ -220,3 +276,10 @@ class LocalFunction {
220
276
  }
221
277
  }
222
278
  exports.default = LocalFunction;
279
+ function toClientVerbOptions(opts) {
280
+ return {
281
+ method: opts.method,
282
+ headers: opts.headers,
283
+ queryParams: opts.qs,
284
+ };
285
+ }
package/lib/utils.js CHANGED
@@ -1,8 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.openInBrowser = exports.connectableHostname = exports.randomInt = exports.debounce = exports.last = exports.cloneDeep = exports.groupBy = exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isVSCodeExtension = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.promiseWithSpinner = exports.setupLoggers = exports.tryParse = exports.tryStringify = exports.promiseProps = exports.withTimeout = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledError = exports.logLabeledWarning = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.getInheritedOption = exports.consoleUrl = exports.envOverrides = void 0;
3
+ exports.openInBrowserPopup = exports.openInBrowser = exports.connectableHostname = exports.randomInt = exports.debounce = exports.last = exports.cloneDeep = exports.groupBy = exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isVSCodeExtension = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.promiseWithSpinner = exports.setupLoggers = exports.tryParse = exports.tryStringify = exports.promiseProps = exports.withTimeout = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledError = exports.logLabeledWarning = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.getInheritedOption = exports.consoleUrl = exports.envOverrides = void 0;
4
+ const fs = require("node:fs");
5
+ const path = require("node:path");
4
6
  const _ = require("lodash");
5
7
  const url = require("url");
8
+ const http = require("http");
6
9
  const clc = require("colorette");
7
10
  const open = require("open");
8
11
  const ora = require("ora");
@@ -12,6 +15,7 @@ const winston = require("winston");
12
15
  const triple_beam_1 = require("triple-beam");
13
16
  const assert_1 = require("assert");
14
17
  const stripAnsi = require("strip-ansi");
18
+ const portfinder_1 = require("portfinder");
15
19
  const configstore_1 = require("./configstore");
16
20
  const error_1 = require("./error");
17
21
  const logger_1 = require("./logger");
@@ -486,3 +490,26 @@ async function openInBrowser(url) {
486
490
  await open(url);
487
491
  }
488
492
  exports.openInBrowser = openInBrowser;
493
+ async function openInBrowserPopup(url, buttonText) {
494
+ const popupPage = fs
495
+ .readFileSync(path.join(__dirname, "../templates/popup.html"), { encoding: "utf-8" })
496
+ .replace("${url}", url)
497
+ .replace("${buttonText}", buttonText);
498
+ const port = await (0, portfinder_1.getPortPromise)();
499
+ const server = http.createServer((req, res) => {
500
+ res.writeHead(200, {
501
+ "Content-Length": popupPage.length,
502
+ "Content-Type": "text/html",
503
+ });
504
+ res.end(popupPage);
505
+ req.socket.destroy();
506
+ });
507
+ server.listen(port);
508
+ const popupPageUri = `http://localhost:${port}`;
509
+ logger_1.logger.info(popupPageUri);
510
+ await openInBrowser(popupPageUri);
511
+ return () => {
512
+ server.close();
513
+ };
514
+ }
515
+ exports.openInBrowserPopup = openInBrowserPopup;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "12.9.1",
3
+ "version": "13.0.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -29,7 +29,7 @@
29
29
  ],
30
30
  "preferGlobal": true,
31
31
  "engines": {
32
- "node": ">=16.13.0 || >=18.0.0"
32
+ "node": ">=18.0.0 || >=20.0.0"
33
33
  },
34
34
  "author": "Firebase (https://firebase.google.com/)",
35
35
  "license": "MIT",
@@ -100,7 +100,6 @@
100
100
  "portfinder": "^1.0.32",
101
101
  "progress": "^2.0.3",
102
102
  "proxy-agent": "^6.3.0",
103
- "request": "^2.87.0",
104
103
  "retry": "^0.13.1",
105
104
  "rimraf": "^3.0.0",
106
105
  "semver": "^7.5.2",
@@ -0,0 +1,64 @@
1
+ <head>
2
+ <style>
3
+ div {
4
+ font-family: sans-serif;
5
+ }
6
+ #popup {
7
+ display: flex;
8
+ flex-direction: column;
9
+ justify-content: center;
10
+ align-items: center;
11
+ height: 100vh;
12
+ width: 100vw;
13
+ text-align: center;
14
+ }
15
+ #popup button {
16
+ display: inline-flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ border-radius: 0.375rem;
20
+ font-size: 1.5rem;
21
+ font-weight: 500;
22
+ height: 2.5rem;
23
+ padding: 0.5rem 1rem;
24
+ background-color: black;
25
+ color: white;
26
+ outline: none;
27
+ transition: background-color 150ms ease-in-out;
28
+ margin-bottom: 20px;
29
+ }
30
+ #popup button:hover {
31
+ background-color: #333;
32
+ }
33
+ #popup button:disabled {
34
+ opacity: 0.5;
35
+ pointer-events: none;
36
+ }
37
+ #message {
38
+ height: 20px;
39
+ margin-bottom: 20px;
40
+ color: gray;
41
+ font-size: 1.5rem;
42
+ }
43
+ </style>
44
+ </head>
45
+ <body>
46
+ <div id="popup">
47
+ <button id="popupBtn" onclick="openPopup()">${buttonText}</button>
48
+ <div id="message">(Close this window after authorizing the app)</div>
49
+ </div>
50
+ <script>
51
+ function openPopup() {
52
+ var w = window.open("${url}", "newwindow", "popup=1");
53
+ var m = document.getElementById("message");
54
+ var d = document.getElementById("popupBtn");
55
+ var timer = setInterval(function () {
56
+ if (w.closed) {
57
+ m.innerText = "You can close this page now";
58
+ d.disabled = true;
59
+ clearInterval(timer);
60
+ }
61
+ }, 1000);
62
+ }
63
+ </script>
64
+ </body>
@@ -1,32 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.command = void 0;
4
- const marked_1 = require("marked");
5
- const TerminalRenderer = require("marked-terminal");
6
- const command_1 = require("../command");
7
- const requireAuth_1 = require("../requireAuth");
8
- const ext_dev_upload_1 = require("./ext-dev-upload");
9
- const utils_1 = require("../utils");
10
- const extensionsHelper_1 = require("../extensions/extensionsHelper");
11
- marked_1.marked.setOptions({
12
- renderer: new TerminalRenderer(),
13
- });
14
- exports.command = new command_1.Command("ext:dev:publish <extensionRef>")
15
- .description(`Deprecated. Use ext:dev:upload instead`)
16
- .option(`-s, --stage <stage>`, `release stage (supports "alpha", "beta", "rc", and "stable")`)
17
- .option(`--repo <repo>`, `Public GitHub repo URI that contains the extension source`)
18
- .option(`--ref <ref>`, `commit hash, branch, or tag to build from the repo (defaults to HEAD)`)
19
- .option(`--root <root>`, `root directory that contains this extension (defaults to last uploaded root or "/" if none set)`)
20
- .withForce()
21
- .help("if you have not previously uploaded a version of this extension, this will " +
22
- "create the extension. If you have previously uploaded a version of this extension, this version must " +
23
- "be greater than previous versions.")
24
- .before(requireAuth_1.requireAuth)
25
- .before(extensionsHelper_1.ensureExtensionsPublisherApiEnabled)
26
- .action(async (extensionRef, options) => {
27
- (0, utils_1.logLabeledWarning)("Extensions", "ext:dev:publish has been deprecated and will be removed in the future. Please use ext:dev:upload instead.");
28
- if (!options.repo && !options.ref && !options.root) {
29
- options.local = true;
30
- }
31
- return (0, ext_dev_upload_1.uploadExtensionAction)(extensionRef, options);
32
- });