firebase-tools 13.11.2 → 13.11.3

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.
@@ -86,13 +86,13 @@ async function createFullyInstalledConnection(projectId, location, connectionId,
86
86
  authorizerCredential: (_b = oauthConn.githubConfig) === null || _b === void 0 ? void 0 : _b.authorizerCredential,
87
87
  });
88
88
  while (conn.installationState.stage !== "COMPLETE") {
89
- utils.logBullet("Install the Firebase GitHub app to enable access to GitHub repositories");
89
+ utils.logBullet("Install the Firebase App Hosting GitHub app to enable access to GitHub repositories");
90
90
  const targetUri = conn.installationState.actionUri;
91
91
  utils.logBullet(targetUri);
92
92
  await utils.openInBrowser(targetUri);
93
93
  await (0, prompt_1.promptOnce)({
94
94
  type: "input",
95
- message: "Press Enter once you have installed or configured the Firebase GitHub app to access your GitHub repo.",
95
+ message: "Press Enter once you have installed or configured the Firebase App Hosting GitHub app to access your GitHub repo.",
96
96
  });
97
97
  conn = await devConnect.getConnection(projectId, location, connectionId);
98
98
  }
@@ -2,11 +2,31 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.command = void 0;
4
4
  const command_1 = require("../command");
5
- const download_1 = require("../emulator/download");
6
5
  const types_1 = require("../emulator/types");
6
+ const emulators_1 = require("../init/features/emulators");
7
+ const prompt_1 = require("../prompt");
8
+ const logger_1 = require("../logger");
9
+ const downloadableEmulators_1 = require("../emulator/downloadableEmulators");
7
10
  const NAME = types_1.Emulators.DATACONNECT;
8
11
  exports.command = new command_1.Command(`setup:emulators:${NAME}`)
9
12
  .description(`downloads the ${NAME} emulator`)
10
- .action(() => {
11
- return (0, download_1.downloadEmulator)(NAME);
13
+ .action(async (options) => {
14
+ var _a, _b;
15
+ await (0, downloadableEmulators_1.downloadIfNecessary)(NAME);
16
+ if (!options.config) {
17
+ logger_1.logger.info("Not currently in a Firebase project directory. Run this command from a project directory to configure the Data Connect emulator.");
18
+ return;
19
+ }
20
+ if (!options.nonInteractive) {
21
+ const dataconnectEmulatorConfig = options.rc.getDataconnect();
22
+ const defaultConnectionString = (_b = (_a = dataconnectEmulatorConfig === null || dataconnectEmulatorConfig === void 0 ? void 0 : dataconnectEmulatorConfig.postgres) === null || _a === void 0 ? void 0 : _a.localConnectionString) !== null && _b !== void 0 ? _b : emulators_1.DEFAULT_POSTGRES_CONNECTION;
23
+ const localConnectionString = await (0, prompt_1.promptOnce)({
24
+ type: "input",
25
+ name: "localConnectionString",
26
+ message: `What is the connection string of the local Postgres instance you would like to use with the Data Connect emulator?`,
27
+ default: defaultConnectionString,
28
+ });
29
+ options.rc.setDataconnect(localConnectionString);
30
+ }
31
+ logger_1.logger.info("Setup complete!");
12
32
  });
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.upsertConnector = exports.listConnectors = exports.deleteConnector = exports.getConnector = exports.upsertSchema = exports.getSchema = exports.deleteService = exports.createService = exports.listAllServices = exports.listLocations = void 0;
3
+ exports.upsertConnector = exports.listConnectors = exports.deleteConnector = exports.getConnector = exports.deleteSchema = exports.upsertSchema = exports.getSchema = exports.deleteServiceAndChildResources = exports.createService = exports.listAllServices = exports.getService = exports.listLocations = void 0;
4
4
  const api_1 = require("../api");
5
5
  const apiv2_1 = require("../apiv2");
6
6
  const operationPoller = require("../operation-poller");
7
7
  const types = require("./types");
8
8
  const DATACONNECT_API_VERSION = "v1alpha";
9
+ const PAGE_SIZE_MAX = 100;
9
10
  const dataconnectClient = () => new apiv2_1.Client({
10
11
  urlPrefix: (0, api_1.dataconnectOrigin)(),
11
12
  apiVersion: DATACONNECT_API_VERSION,
@@ -17,6 +18,11 @@ async function listLocations(projectId) {
17
18
  return (_c = (_b = (_a = res.body) === null || _a === void 0 ? void 0 : _a.locations) === null || _b === void 0 ? void 0 : _b.map((l) => l.locationId)) !== null && _c !== void 0 ? _c : [];
18
19
  }
19
20
  exports.listLocations = listLocations;
21
+ async function getService(serviceName) {
22
+ const res = await dataconnectClient().get(serviceName);
23
+ return res.body;
24
+ }
25
+ exports.getService = getService;
20
26
  async function listAllServices(projectId) {
21
27
  var _a;
22
28
  const res = await dataconnectClient().get(`/projects/${projectId}/locations/-/services`);
@@ -39,8 +45,8 @@ async function createService(projectId, locationId, serviceId) {
39
45
  return pollRes;
40
46
  }
41
47
  exports.createService = createService;
42
- async function deleteService(projectId, locationId, serviceId) {
43
- const op = await dataconnectClient().delete(`projects/${projectId}/locations/${locationId}/services/${serviceId}`);
48
+ async function deleteService(serviceName) {
49
+ const op = await dataconnectClient().delete(serviceName);
44
50
  const pollRes = await operationPoller.pollOperation({
45
51
  apiOrigin: (0, api_1.dataconnectOrigin)(),
46
52
  apiVersion: DATACONNECT_API_VERSION,
@@ -48,7 +54,20 @@ async function deleteService(projectId, locationId, serviceId) {
48
54
  });
49
55
  return pollRes;
50
56
  }
51
- exports.deleteService = deleteService;
57
+ async function deleteServiceAndChildResources(serviceName) {
58
+ const connectors = await listConnectors(serviceName);
59
+ await Promise.all(connectors.map(async (c) => deleteConnector(c.name)));
60
+ try {
61
+ await deleteSchema(serviceName);
62
+ }
63
+ catch (err) {
64
+ if (err.status !== 404) {
65
+ throw err;
66
+ }
67
+ }
68
+ await deleteService(serviceName);
69
+ }
70
+ exports.deleteServiceAndChildResources = deleteServiceAndChildResources;
52
71
  async function getSchema(serviceName) {
53
72
  try {
54
73
  const res = await dataconnectClient().get(`${serviceName}/schemas/${types.SCHEMA_ID}`);
@@ -79,20 +98,47 @@ async function upsertSchema(schema, validateOnly = false) {
79
98
  });
80
99
  }
81
100
  exports.upsertSchema = upsertSchema;
101
+ async function deleteSchema(serviceName) {
102
+ const op = await dataconnectClient().delete(`${serviceName}/schemas/${types.SCHEMA_ID}`);
103
+ await operationPoller.pollOperation({
104
+ apiOrigin: (0, api_1.dataconnectOrigin)(),
105
+ apiVersion: DATACONNECT_API_VERSION,
106
+ operationResourceName: op.body.name,
107
+ });
108
+ return;
109
+ }
110
+ exports.deleteSchema = deleteSchema;
82
111
  async function getConnector(name) {
83
112
  const res = await dataconnectClient().get(name);
84
113
  return res.body;
85
114
  }
86
115
  exports.getConnector = getConnector;
87
116
  async function deleteConnector(name) {
88
- const res = await dataconnectClient().delete(name);
89
- return res.body;
117
+ const op = await dataconnectClient().delete(name);
118
+ await operationPoller.pollOperation({
119
+ apiOrigin: (0, api_1.dataconnectOrigin)(),
120
+ apiVersion: DATACONNECT_API_VERSION,
121
+ operationResourceName: op.body.name,
122
+ });
123
+ return;
90
124
  }
91
125
  exports.deleteConnector = deleteConnector;
92
126
  async function listConnectors(serviceName) {
93
- var _a;
94
- const res = await dataconnectClient().get(`${serviceName}/connectors`);
95
- return ((_a = res.body) === null || _a === void 0 ? void 0 : _a.connectors) || [];
127
+ const connectors = [];
128
+ const getNextPage = async (pageToken = "") => {
129
+ const res = await dataconnectClient().get(`${serviceName}/connectors`, {
130
+ queryParams: {
131
+ pageSize: PAGE_SIZE_MAX,
132
+ pageToken,
133
+ },
134
+ });
135
+ connectors.push(...res.body.connectors);
136
+ if (res.body.nextPageToken) {
137
+ await getNextPage(res.body.nextPageToken);
138
+ }
139
+ };
140
+ await getNextPage();
141
+ return connectors;
96
142
  }
97
143
  exports.listConnectors = listConnectors;
98
144
  async function upsertConnector(connector) {
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const chai_1 = require("chai");
4
+ const adminSdkConfig_1 = require("./adminSdkConfig");
5
+ describe("adminSdkConfig", () => {
6
+ describe("getProjectAdminSdkConfigOrCached", () => {
7
+ it("should return a fake config for a demo project id", async () => {
8
+ const projectId = "demo-project-1234";
9
+ await (0, chai_1.expect)((0, adminSdkConfig_1.getProjectAdminSdkConfigOrCached)(projectId)).to.eventually.deep.equal({
10
+ projectId: "demo-project-1234",
11
+ databaseURL: "https://demo-project-1234.firebaseio.com",
12
+ storageBucket: "demo-project-1234.appspot.com",
13
+ });
14
+ });
15
+ });
16
+ });
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DataConnectEmulatorClient = exports.DataConnectEmulator = void 0;
4
4
  const childProcess = require("child_process");
5
+ const clc = require("colorette");
5
6
  const api_1 = require("../api");
6
7
  const constants_1 = require("./constants");
7
8
  const downloadableEmulators_1 = require("./downloadableEmulators");
@@ -36,7 +37,6 @@ class DataConnectEmulator {
36
37
  auto_download: this.args.auto_download,
37
38
  listen: (0, portUtils_1.listenSpecsToString)(this.args.listen),
38
39
  config_dir: this.args.configDir,
39
- project_id: this.args.projectId,
40
40
  service_location: this.args.locationId,
41
41
  });
42
42
  }
@@ -62,6 +62,8 @@ class DataConnectEmulator {
62
62
  static async generate(args) {
63
63
  const commandInfo = await (0, downloadableEmulators_1.downloadIfNecessary)(types_1.Emulators.DATACONNECT);
64
64
  const cmd = [
65
+ "--logtostderr",
66
+ "-v=2",
65
67
  "generate",
66
68
  `--service_location=${args.locationId}`,
67
69
  `--config_dir=${args.configDir}`,
@@ -78,7 +80,7 @@ class DataConnectEmulator {
78
80
  static async build(args) {
79
81
  var _a;
80
82
  const commandInfo = await (0, downloadableEmulators_1.downloadIfNecessary)(types_1.Emulators.DATACONNECT);
81
- const cmd = ["build", `--config_dir=${args.configDir}`];
83
+ const cmd = ["--logtostderr", "-v=2", "build", `--config_dir=${args.configDir}`];
82
84
  const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8" });
83
85
  if (res.error) {
84
86
  throw new error_1.FirebaseError(`Error starting up Data Connect build: ${res.error.message}`, {
@@ -108,8 +110,9 @@ class DataConnectEmulator {
108
110
  async connectToPostgres(localConnectionString, database, serviceId) {
109
111
  const connectionString = localConnectionString !== null && localConnectionString !== void 0 ? localConnectionString : this.getLocalConectionString();
110
112
  if (!connectionString) {
111
- this.logger.log("DEBUG", "No Postgres connection string found, not connecting to Postgres");
112
- return false;
113
+ const msg = `No Postgres connection string found in '.firebaserc'. The Data Connect emulator will not be able to execute operations.
114
+ Run ${clc.bold("firebase setup:emulators:dataconnect")} to set up a Postgres connection.`;
115
+ throw new error_1.FirebaseError(msg);
113
116
  }
114
117
  await this.emulatorClient.configureEmulator({ connectionString, database, serviceId });
115
118
  return true;
@@ -46,20 +46,20 @@ const EMULATOR_UPDATE_DETAILS = {
46
46
  },
47
47
  dataconnect: process.platform === "darwin"
48
48
  ? {
49
- version: "1.2.0",
50
- expectedSize: 23954240,
51
- expectedChecksum: "0f250761959519bb5a28fed76ceab2cb",
49
+ version: "1.2.2",
50
+ expectedSize: 24007488,
51
+ expectedChecksum: "c1fb77895203681479ee5dd22d57249f",
52
52
  }
53
53
  : process.platform === "win32"
54
54
  ? {
55
- version: "1.2.0",
56
- expectedSize: 24360960,
57
- expectedChecksum: "168ce32c742e1d26037c52bdbb7d871c",
55
+ version: "1.2.2",
56
+ expectedSize: 24414208,
57
+ expectedChecksum: "7e263c2b2bc9055ead2db8102e883534",
58
58
  }
59
59
  : {
60
- version: "1.2.0",
61
- expectedSize: 23970052,
62
- expectedChecksum: "2ca17e4009a9ebae0f7c983bafff2ee6",
60
+ version: "1.2.2",
61
+ expectedSize: 24023300,
62
+ expectedChecksum: "12467418226ac9657fb64b4d719d0e1d",
63
63
  },
64
64
  };
65
65
  exports.DownloadDetails = {
@@ -139,7 +139,7 @@ exports.DownloadDetails = {
139
139
  expectedChecksum: EMULATOR_UPDATE_DETAILS.dataconnect.expectedChecksum,
140
140
  skipChecksumAndSize: false,
141
141
  namePrefix: "dataconnect-emulator",
142
- auth: true,
142
+ auth: false,
143
143
  },
144
144
  },
145
145
  };
@@ -224,7 +224,7 @@ const Commands = {
224
224
  shell: false,
225
225
  },
226
226
  pubsub: {
227
- binary: getExecPath(types_1.Emulators.PUBSUB),
227
+ binary: `${getExecPath(types_1.Emulators.PUBSUB)}`,
228
228
  args: [],
229
229
  optionalArgs: ["port", "host"],
230
230
  joinArgs: true,
@@ -238,16 +238,14 @@ const Commands = {
238
238
  shell: false,
239
239
  },
240
240
  dataconnect: {
241
- binary: getExecPath(types_1.Emulators.DATACONNECT),
242
- args: ["dev"],
241
+ binary: `${getExecPath(types_1.Emulators.DATACONNECT)}`,
242
+ args: ["--logtostderr", "-v=2", "dev"],
243
243
  optionalArgs: [
244
244
  "listen",
245
245
  "config_dir",
246
- "project_id",
247
246
  "service_location",
248
247
  "disable_sdk_generation",
249
248
  "resolvers_emulator",
250
- "vertex_location",
251
249
  "rpc_retry_count",
252
250
  ],
253
251
  joinArgs: true,
@@ -342,6 +340,9 @@ async function _runBinary(emulator, command, extraEnv) {
342
340
  };
343
341
  if (command.shell && utils.IS_WINDOWS) {
344
342
  opts.shell = true;
343
+ if (command.binary.includes(" ")) {
344
+ command.binary = `"${command.binary}"`;
345
+ }
345
346
  }
346
347
  emulator.instance = childProcess.spawn(command.binary, command.args, opts);
347
348
  }
@@ -109,7 +109,7 @@ class EmulatorRegistry {
109
109
  url.port = info.port.toString();
110
110
  }
111
111
  else {
112
- console.warn(`Cannot determine host and port of ${emulator}`);
112
+ throw new Error(`Cannot determine host and port of ${emulator}`);
113
113
  }
114
114
  return url;
115
115
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.sendFileBytes = void 0;
4
4
  const zlib_1 = require("zlib");
5
5
  const crc_1 = require("../crc");
6
+ const rfc_1 = require("../rfc");
6
7
  function sendFileBytes(md, data, req, res) {
7
8
  let didGunzip = false;
8
9
  if (md.contentEncoding === "gzip") {
@@ -16,7 +17,7 @@ function sendFileBytes(md, data, req, res) {
16
17
  res.setHeader("Accept-Ranges", "bytes");
17
18
  res.setHeader("Content-Type", md.contentType || "application/octet-stream");
18
19
  const fileName = md.name.split("/").pop();
19
- res.setHeader("Content-Disposition", `${md.contentDisposition || "attachment"}; filename=${fileName}`);
20
+ res.setHeader("Content-Disposition", `${md.contentDisposition || "attachment"}; filename*=${(0, rfc_1.encodeRFC5987)(fileName)}`);
20
21
  if (didGunzip) {
21
22
  res.setHeader("Transfer-Encoding", "chunked");
22
23
  }
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Persistence = void 0;
4
4
  const fs_1 = require("fs");
5
- const rimraf = require("rimraf");
5
+ const rimraf_1 = require("rimraf");
6
6
  const fs = require("fs");
7
7
  const fse = require("fs-extra");
8
8
  const path = require("path");
@@ -56,18 +56,10 @@ class Persistence {
56
56
  }
57
57
  this._diskPathMap.delete(fileName);
58
58
  }
59
- deleteAll() {
60
- return new Promise((resolve, reject) => {
61
- rimraf(this._dirPath, (err) => {
62
- if (err) {
63
- reject(err);
64
- }
65
- else {
66
- this._diskPathMap = new Map();
67
- resolve();
68
- }
69
- });
70
- });
59
+ async deleteAll() {
60
+ await (0, rimraf_1.rimraf)(this._dirPath);
61
+ this._diskPathMap = new Map();
62
+ return;
71
63
  }
72
64
  renameFile(oldName, newName) {
73
65
  const oldNameId = this.getDiskFileName(oldName);
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.encodeRFC5987 = void 0;
4
+ function encodeRFC5987(str) {
5
+ return encodeURIComponent(str)
6
+ .replace(/['()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)
7
+ .replace(/%(7C|60|5E)/g, (str, hex) => String.fromCharCode(parseInt(hex, 16)));
8
+ }
9
+ exports.encodeRFC5987 = encodeRFC5987;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const chai_1 = require("chai");
4
+ const firestore_1 = require("../gcp/firestore");
5
+ const proto_1 = require("../gcp/proto");
6
+ const sort = require("./api-sort");
7
+ describe("compareApiBackup", () => {
8
+ it("should compare backups by location", () => {
9
+ const nam5Backup = {
10
+ name: "projects/example/locations/nam5/backups/backupid",
11
+ };
12
+ const usWest1Backup = {
13
+ name: "projects/example/locations/us-west1/backups/backupid",
14
+ };
15
+ (0, chai_1.expect)(sort.compareApiBackup(usWest1Backup, nam5Backup)).to.greaterThanOrEqual(1);
16
+ (0, chai_1.expect)(sort.compareApiBackup(nam5Backup, usWest1Backup)).to.lessThanOrEqual(-1);
17
+ });
18
+ it("should compare backups by snapshotTime (descending) if location is the same", () => {
19
+ const earlierBackup = {
20
+ name: "projects/example/locations/nam5/backups/backupid",
21
+ snapshotTime: "2024-01-01T00:00:00.000000Z",
22
+ };
23
+ const laterBackup = {
24
+ name: "projects/example/locations/nam5/backups/backupid",
25
+ snapshotTime: "2024-02-02T00:00:00.000000Z",
26
+ };
27
+ (0, chai_1.expect)(sort.compareApiBackup(earlierBackup, laterBackup)).to.greaterThanOrEqual(1);
28
+ (0, chai_1.expect)(sort.compareApiBackup(laterBackup, earlierBackup)).to.lessThanOrEqual(-1);
29
+ });
30
+ it("should compare backups by full name if location and snapshotTime are the same", () => {
31
+ const nam5Backup1 = {
32
+ name: "projects/example/locations/nam5/backups/earlier-backupid",
33
+ snapshotTime: "2024-01-01T00:00:00.000000Z",
34
+ };
35
+ const nam5Backup2 = {
36
+ name: "projects/example/locations/nam5/backups/later-backupid",
37
+ snapshotTime: "2024-01-01T00:00:00.000000Z",
38
+ };
39
+ (0, chai_1.expect)(sort.compareApiBackup(nam5Backup2, nam5Backup1)).to.greaterThanOrEqual(1);
40
+ (0, chai_1.expect)(sort.compareApiBackup(nam5Backup1, nam5Backup2)).to.lessThanOrEqual(-1);
41
+ });
42
+ });
43
+ describe("compareApiBackupSchedule", () => {
44
+ it("daily schedules should precede weekly ones", () => {
45
+ const dailySchedule = {
46
+ name: "projects/example/databases/mydatabase/backupSchedules/schedule",
47
+ dailyRecurrence: {},
48
+ retention: (0, proto_1.durationFromSeconds)(60 * 60 * 24),
49
+ };
50
+ const weeklySchedule = {
51
+ name: "projects/example/databases/mydatabase/backupSchedules/schedule",
52
+ weeklyRecurrence: {
53
+ day: firestore_1.DayOfWeek.FRIDAY,
54
+ },
55
+ retention: (0, proto_1.durationFromSeconds)(60 * 60 * 24 * 7),
56
+ };
57
+ (0, chai_1.expect)(sort.compareApiBackupSchedule(weeklySchedule, dailySchedule)).to.greaterThanOrEqual(1);
58
+ (0, chai_1.expect)(sort.compareApiBackup(dailySchedule, weeklySchedule)).to.lessThanOrEqual(-1);
59
+ });
60
+ it("should compare schedules with the same recurrence by name", () => {
61
+ const dailySchedule1 = {
62
+ name: "projects/example/databases/mydatabase/backupSchedules/schedule1",
63
+ dailyRecurrence: {},
64
+ retention: (0, proto_1.durationFromSeconds)(60 * 60 * 24),
65
+ };
66
+ const dailySchedule2 = {
67
+ name: "projects/example/databases/mydatabase/backupSchedules/schedule2",
68
+ dailyRecurrence: {},
69
+ retention: (0, proto_1.durationFromSeconds)(60 * 60 * 24),
70
+ };
71
+ (0, chai_1.expect)(sort.compareApiBackupSchedule(dailySchedule1, dailySchedule2)).to.lessThanOrEqual(-1);
72
+ (0, chai_1.expect)(sort.compareApiBackup(dailySchedule2, dailySchedule1)).to.greaterThanOrEqual(1);
73
+ });
74
+ });
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const chai_1 = require("chai");
4
+ const backupUtils_1 = require("./backupUtils");
5
+ describe("calculateRetention", () => {
6
+ it("should accept minutes", () => {
7
+ (0, chai_1.expect)((0, backupUtils_1.calculateRetention)("5m")).to.eq(300);
8
+ });
9
+ it("should accept hours", () => {
10
+ (0, chai_1.expect)((0, backupUtils_1.calculateRetention)("3h")).to.eq(10800);
11
+ });
12
+ it("should accept days", () => {
13
+ (0, chai_1.expect)((0, backupUtils_1.calculateRetention)("2d")).to.eq(172800);
14
+ });
15
+ it("should accept weeks", () => {
16
+ (0, chai_1.expect)((0, backupUtils_1.calculateRetention)("3w")).to.eq(1814400);
17
+ });
18
+ });
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const chai_1 = require("chai");
4
+ const API = require("./api-types");
5
+ const pretty_print_1 = require("./pretty-print");
6
+ const printer = new pretty_print_1.PrettyPrint();
7
+ describe("prettyIndexString", () => {
8
+ it("should correctly print an order type Index", () => {
9
+ (0, chai_1.expect)(printer.prettyIndexString({
10
+ name: "/projects/project/databases/(default)/collectionGroups/collectionB/indexes/a",
11
+ queryScope: API.QueryScope.COLLECTION,
12
+ fields: [
13
+ { fieldPath: "foo", order: API.Order.ASCENDING },
14
+ { fieldPath: "bar", order: API.Order.DESCENDING },
15
+ ],
16
+ }, false)).to.contain("(foo,ASCENDING) (bar,DESCENDING) ");
17
+ });
18
+ it("should correctly print a contains type Index", () => {
19
+ (0, chai_1.expect)(printer.prettyIndexString({
20
+ name: "/projects/project/databases/(default)/collectionGroups/collectionB/indexes/a",
21
+ queryScope: API.QueryScope.COLLECTION,
22
+ fields: [
23
+ { fieldPath: "foo", order: API.Order.ASCENDING },
24
+ { fieldPath: "baz", arrayConfig: API.ArrayConfig.CONTAINS },
25
+ ],
26
+ }, false)).to.contain("(foo,ASCENDING) (baz,CONTAINS) ");
27
+ });
28
+ it("should correctly print a vector type Index", () => {
29
+ (0, chai_1.expect)(printer.prettyIndexString({
30
+ name: "/projects/project/databases/(default)/collectionGroups/collectionB/indexes/a",
31
+ queryScope: API.QueryScope.COLLECTION,
32
+ fields: [{ fieldPath: "foo", vectorConfig: { dimension: 100, flat: {} } }],
33
+ }, false)).to.contain("(foo,VECTOR<100>) ");
34
+ });
35
+ it("should correctly print a vector type Index with other fields", () => {
36
+ (0, chai_1.expect)(printer.prettyIndexString({
37
+ name: "/projects/project/databases/(default)/collectionGroups/collectionB/indexes/a",
38
+ queryScope: API.QueryScope.COLLECTION,
39
+ fields: [
40
+ { fieldPath: "foo", order: API.Order.ASCENDING },
41
+ { fieldPath: "bar", vectorConfig: { dimension: 200, flat: {} } },
42
+ ],
43
+ }, false)).to.contain("(foo,ASCENDING) (bar,VECTOR<200>) ");
44
+ });
45
+ });
46
+ describe("firebaseConsoleDatabaseUrl", () => {
47
+ it("should provide a console link", () => {
48
+ (0, chai_1.expect)(printer.firebaseConsoleDatabaseUrl("example-project", "example-db")).to.equal("https://console.firebase.google.com/project/example-project/firestore/databases/example-db/data");
49
+ });
50
+ it("should convert (default) to -default-", () => {
51
+ (0, chai_1.expect)(printer.firebaseConsoleDatabaseUrl("example-project", "(default)")).to.equal("https://console.firebase.google.com/project/example-project/firestore/databases/-default-/data");
52
+ });
53
+ });
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const chai_1 = require("chai");
4
+ const util = require("./util");
5
+ describe("IndexNameParsing", () => {
6
+ it("should parse an index name correctly", () => {
7
+ const name = "/projects/myproject/databases/(default)/collectionGroups/collection/indexes/abc123/";
8
+ (0, chai_1.expect)(util.parseIndexName(name)).to.eql({
9
+ projectId: "myproject",
10
+ databaseId: "(default)",
11
+ collectionGroupId: "collection",
12
+ indexId: "abc123",
13
+ });
14
+ });
15
+ it("should parse a field name correctly", () => {
16
+ const name = "/projects/myproject/databases/(default)/collectionGroups/collection/fields/abc123/";
17
+ (0, chai_1.expect)(util.parseFieldName(name)).to.eql({
18
+ projectId: "myproject",
19
+ databaseId: "(default)",
20
+ collectionGroupId: "collection",
21
+ fieldPath: "abc123",
22
+ });
23
+ });
24
+ it("should parse an index name from a named database correctly", () => {
25
+ const name = "/projects/myproject/databases/named-db/collectionGroups/collection/indexes/abc123/";
26
+ (0, chai_1.expect)(util.parseIndexName(name)).to.eql({
27
+ projectId: "myproject",
28
+ databaseId: "named-db",
29
+ collectionGroupId: "collection",
30
+ indexId: "abc123",
31
+ });
32
+ });
33
+ it("should parse a field name from a named database correctly", () => {
34
+ const name = "/projects/myproject/databases/named-db/collectionGroups/collection/fields/abc123/";
35
+ (0, chai_1.expect)(util.parseFieldName(name)).to.eql({
36
+ projectId: "myproject",
37
+ databaseId: "named-db",
38
+ collectionGroupId: "collection",
39
+ fieldPath: "abc123",
40
+ });
41
+ });
42
+ });
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MockFileSystem = void 0;
4
+ class MockFileSystem {
5
+ constructor(fileSys) {
6
+ this.fileSys = fileSys;
7
+ this.existsCache = {};
8
+ this.contentCache = {};
9
+ }
10
+ exists(path) {
11
+ if (!(path in this.existsCache)) {
12
+ this.existsCache[path] = path in this.fileSys;
13
+ }
14
+ return Promise.resolve(this.existsCache[path]);
15
+ }
16
+ read(path) {
17
+ if (!(path in this.contentCache)) {
18
+ if (!(path in this.fileSys)) {
19
+ const err = new Error("File path not found");
20
+ err.cause = "ENOENT";
21
+ throw err;
22
+ }
23
+ else {
24
+ this.contentCache[path] = this.fileSys[path];
25
+ }
26
+ }
27
+ return Promise.resolve(this.contentCache[path]);
28
+ }
29
+ getContentCache(path) {
30
+ return this.contentCache[path];
31
+ }
32
+ getExistsCache(path) {
33
+ return this.existsCache[path];
34
+ }
35
+ }
36
+ exports.MockFileSystem = MockFileSystem;
@@ -7,8 +7,8 @@ const child_process_1 = require("child_process");
7
7
  const cross_spawn_1 = require("cross-spawn");
8
8
  const promises_1 = require("fs/promises");
9
9
  const fs_extra_1 = require("fs-extra");
10
+ const glob_1 = require("glob");
10
11
  const process = require("node:process");
11
- const glob = require("glob");
12
12
  const projectUtils_1 = require("../projectUtils");
13
13
  const config_1 = require("../hosting/config");
14
14
  const api_1 = require("../hosting/api");
@@ -366,11 +366,7 @@ async function prepareFrameworks(purpose, targetNames, context, options, emulato
366
366
  await (0, promises_1.writeFile)((0, path_1.join)(functionsDist, ".env"), `${dotEnvContents}
367
367
  __FIREBASE_FRAMEWORKS_ENTRY__=${frameworksEntry}
368
368
  ${firebaseDefaults ? `__FIREBASE_DEFAULTS__=${JSON.stringify(firebaseDefaults)}\n` : ""}`.trimStart());
369
- const envs = await new Promise((resolve, reject) => glob(getProjectPath(".env.*"), (err, matches) => {
370
- if (err)
371
- reject(err);
372
- resolve(matches);
373
- }));
369
+ const envs = await (0, glob_1.glob)(getProjectPath(".env.*"));
374
370
  await Promise.all(envs.map((path) => (0, promises_1.copyFile)(path, (0, path_1.join)(functionsDist, (0, path_1.basename)(path)))));
375
371
  (0, child_process_1.execSync)(`npm i --omit dev --no-audit`, {
376
372
  cwd: functionsDist,
@@ -6,7 +6,6 @@ const fs_extra_1 = require("fs-extra");
6
6
  const path_1 = require("path");
7
7
  const promises_1 = require("fs/promises");
8
8
  const glob_1 = require("glob");
9
- const glob = require("glob");
10
9
  const semver_1 = require("semver");
11
10
  const utils_1 = require("../utils");
12
11
  const constants_1 = require("./constants");
@@ -226,17 +225,12 @@ function getRoutesWithServerAction(serverReferenceManifest, appPathRoutesManifes
226
225
  }
227
226
  exports.getRoutesWithServerAction = getRoutesWithServerAction;
228
227
  async function getProductionDistDirFiles(sourceDir, distDir) {
229
- const productionDistDirFiles = await new Promise((resolve, reject) => glob("**", {
228
+ return (0, glob_1.glob)("**", {
230
229
  ignore: [(0, path_1.join)("cache", "webpack", "*-development", "**"), (0, path_1.join)("cache", "eslint", "**")],
231
230
  cwd: (0, path_1.join)(sourceDir, distDir),
232
231
  nodir: true,
233
232
  absolute: false,
234
- }, (err, matches) => {
235
- if (err)
236
- reject(err);
237
- resolve(matches);
238
- }));
239
- return productionDistDirFiles;
233
+ });
240
234
  }
241
235
  exports.getProductionDistDirFiles = getProductionDistDirFiles;
242
236
  async function whichNextConfigFile(dir) {
package/lib/listFiles.js CHANGED
@@ -9,7 +9,6 @@ function listFiles(cwd, ignore = []) {
9
9
  follow: true,
10
10
  ignore: ["**/firebase-debug.log", "**/firebase-debug.*.log", ".firebase/*"].concat(ignore),
11
11
  nodir: true,
12
- nosort: true,
13
12
  });
14
13
  }
15
14
  exports.listFiles = listFiles;
package/lib/rc.js CHANGED
@@ -158,6 +158,7 @@ class RC {
158
158
  }
159
159
  setDataconnect(localConnectionString) {
160
160
  this.data.dataconnectEmulatorConfig = { postgres: { localConnectionString } };
161
+ this.save();
161
162
  }
162
163
  save() {
163
164
  if (this.path) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "13.11.2",
3
+ "version": "13.11.3",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -54,6 +54,8 @@
54
54
  ".ts"
55
55
  ],
56
56
  "exclude": [
57
+ "src/**/*.spec.*",
58
+ "src/**/testing/**/*",
57
59
  "src/test/**/*"
58
60
  ]
59
61
  },
@@ -62,7 +64,7 @@
62
64
  "@google-cloud/pubsub": "^4.4.0",
63
65
  "abort-controller": "^3.0.0",
64
66
  "ajv": "^6.12.6",
65
- "archiver": "^5.0.0",
67
+ "archiver": "^7.0.0",
66
68
  "async-lock": "1.3.2",
67
69
  "body-parser": "^1.19.0",
68
70
  "chokidar": "^3.0.2",
@@ -76,7 +78,7 @@
76
78
  "cross-spawn": "^7.0.3",
77
79
  "csv-parse": "^5.0.4",
78
80
  "deep-equal-in-any-order": "^2.0.6",
79
- "exegesis": "^4.1.0",
81
+ "exegesis": "^4.1.2",
80
82
  "exegesis-express": "^4.0.0",
81
83
  "express": "^4.16.4",
82
84
  "filesize": "^6.1.0",
@@ -84,7 +86,7 @@
84
86
  "fs-extra": "^10.1.0",
85
87
  "fuzzy": "^0.1.3",
86
88
  "gaxios": "^6.1.1",
87
- "glob": "^7.1.2",
89
+ "glob": "^10.4.1",
88
90
  "google-auth-library": "^9.7.0",
89
91
  "inquirer": "^8.2.6",
90
92
  "inquirer-autocomplete-prompt": "^2.0.1",
@@ -106,7 +108,7 @@
106
108
  "progress": "^2.0.3",
107
109
  "proxy-agent": "^6.3.0",
108
110
  "retry": "^0.13.1",
109
- "rimraf": "^3.0.0",
111
+ "rimraf": "^5.0.0",
110
112
  "semver": "^7.5.2",
111
113
  "sql-formatter": "^15.3.0",
112
114
  "stream-chain": "^2.2.4",
@@ -115,7 +117,7 @@
115
117
  "superstatic": "^9.0.3",
116
118
  "tar": "^6.1.11",
117
119
  "tcp-port-used": "^1.0.2",
118
- "tmp": "^0.2.1",
120
+ "tmp": "^0.2.3",
119
121
  "triple-beam": "^1.3.0",
120
122
  "universal-analytics": "^0.5.3",
121
123
  "update-notifier-cjs": "^5.1.6",
@@ -1,30 +1,35 @@
1
1
  # # Example mutations for a simple email app
2
2
 
3
- # mutation CreateUser($uid: String, $name: String, $address: String) @auth(level: NO_ACCESS) {
4
- ## <type>_insert lets you create a new row in your table.
3
+ # # Logged in user can create their own account.
4
+ # mutation CreateUser($name: String!, $address: String!) @auth(level: USER) {
5
+ # # <type>_insert lets you create a new row in your table.
5
6
  # user_insert(data: {
6
- # uid: $uid,
7
+ # # Server values let your service populate sensitive data.
8
+ # # Users can only setup their own account.
9
+ # uid_expr: "auth.uid",
7
10
  # name: $name,
8
11
  # address: $address
9
12
  # })
10
13
  # }
11
- # mutation CreateEmail($content: String, $subject: String, $fromUid: String) @auth(level: PUBLIC) {
14
+
15
+ # # Logged in user can send emails from their account.
16
+ # mutation CreateEmail($content: String, $subject: String) @auth(level: USER) {
12
17
  # email_insert(data: {
13
- # text: $content, # The request variable name doesn't have to match the field name.
18
+ # # The request variable name doesn't have to match the field name.
19
+ # text: $content,
14
20
  # subject: $subject,
15
- # fromUid: $fromUid,
16
- ## Server values let your service populate data for you
17
- ## Here, we use sent_date: { today: true } to set 'sent' to today's date.
21
+ # # Server values let your service populate sensitive data.
22
+ # # Users are only allowed to create emails sent from their account.
23
+ # fromUid_expr: "auth.uid",
24
+ # # Server values let your service populate data for you
25
+ # # Here, we use sent_date: { today: true } to set 'sent' to today's date.
18
26
  # sent_date: { today: true }
19
27
  # })
20
28
  # }
21
- # mutation CreateRecipient($emailId: UUID, $uid: String) @auth(level: PUBLIC) {
29
+
30
+ # mutation CreateRecipient($emailId: UUID) @auth(level: USER) {
22
31
  # recipient_insert(data: {
23
32
  # emailId: $emailId,
24
- # userUid: $uid
33
+ # userUid_expr: "auth.uid"
25
34
  # })
26
35
  # }
27
- # mutation DeleteEmail($emailId: UUID, $uid: String) @auth(level: PUBLIC) {
28
- ## <type>_ delete lets you delete rows from your table.
29
- # recipient_delete(key: {emailId: $emailId, userUid: $uid})
30
- # }
@@ -1,51 +1,56 @@
1
1
  # # Example queries for a simple email app.
2
2
 
3
- ## @auth() directives control who can call each operation.
4
- ## Only admins should be able to list all users, so we use NO_ACCESS
5
- # query ListUsers @auth(level: NO_ACCESS) {
6
- # users { uid, name, email: address }
3
+ # # @auth() directives control who can call each operation.
4
+ # # Only admins should be able to list all emails, so we use NO_ACCESS
5
+ # query ListEmails @auth(level: NO_ACCESS) {
6
+ # emails {
7
+ # id, subject, text, sent
8
+ # from {
9
+ # name
10
+ # }
11
+ # }
7
12
  # }
8
13
 
9
- ## Everyone should be able to see their inbox though, so we use PUBLIC
10
- # query ListInbox(
11
- # $uid: String
12
- # ) @auth(level: PUBLIC) {
13
- ## where allows you to filter lists
14
- ## Here, we use it to filter to only emails where this user is one of the recipients.
14
+ # # Only admins should be able to list all users, so we use NO_ACCESS
15
+ # query ListUsers @auth(level: NO_ACCESS) {
16
+ # users { uid, name, address }
17
+ # }
18
+
19
+ # # Logged in users should be able to see their inbox though, so we use USER
20
+ # query ListInbox @auth(level: USER) {
21
+ # # where allows you to filter lists
22
+ # # Here, we use it to filter to only emails that are sent to the logged in user.
15
23
  # emails(where: {
16
24
  # users_via_Recipient: {
17
- # exist: { uid: { eq: $uid }
25
+ # exist: { uid: { eq_expr: "auth.uid" }
18
26
  # }}
19
27
  # }) {
20
28
  # id subject sent
21
29
  # content: text # Select the `text` field but alias it as `content` in the response.
22
- # sender: from { name email: address uid }
23
-
24
- ## <field>_on_<foreign_key_field> makes it easy to grab info from another table
25
- ## Here, we use it to grab all the recipients of the email.
30
+ # sender: from { name address uid }
31
+ # # <field>_on_<foreign_key_field> makes it easy to grab info from another table
32
+ # # Here, we use it to grab all the recipients of the email.
26
33
  # to: recipients_on_email {
27
- # user { name email: address uid }
34
+ # user { name address uid }
28
35
  # }
29
36
  # }
30
37
  # }
31
38
 
32
39
  # query GetUidByEmail($emails: [String!]) @auth(level: PUBLIC) {
33
40
  # users(where: { address: { in: $emails } }) {
34
- # uid email: address
41
+ # uid address
35
42
  # }
36
43
  # }
37
44
 
38
- # query ListSent(
39
- # $uid: String
40
- # ) @auth(level: PUBLIC) {
45
+ # query ListSent($uid: String) @auth(level: PUBLIC) {
41
46
  # emails(where: {
42
- # fromUid: { eq: $uid }
47
+ # from: {uid: {eq: $uid }}
43
48
  # }) {
44
49
  # id subject sent
45
50
  # content: text
46
- # sender: from { name email: address uid }
51
+ # sender: from { name address uid }
47
52
  # to: recipients_on_email {
48
- # user { name email: address uid }
53
+ # user { name address uid }
49
54
  # }
50
55
  # }
51
56
  # }
@@ -1,4 +1,4 @@
1
- ## Example schema for simple email app
1
+ # # Example schema for simple email app
2
2
  # type User @table(key: "uid") {
3
3
  # uid: String!
4
4
  # name: String!