firebase-tools 13.11.2 → 13.11.4

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
  }
@@ -200,17 +200,15 @@ function load(client) {
200
200
  client.setup.emulators.pubsub = loadCommand("setup-emulators-pubsub");
201
201
  client.setup.emulators.storage = loadCommand("setup-emulators-storage");
202
202
  client.setup.emulators.ui = loadCommand("setup-emulators-ui");
203
- if (experiments.isEnabled("dataconnect")) {
204
- client.dataconnect = {};
205
- client.setup.emulators.dataconnect = loadCommand("setup-emulators-dataconnect");
206
- client.dataconnect.services = {};
207
- client.dataconnect.services.list = loadCommand("dataconnect-services-list");
208
- client.dataconnect.sql = {};
209
- client.dataconnect.sql.diff = loadCommand("dataconnect-sql-diff");
210
- client.dataconnect.sql.migrate = loadCommand("dataconnect-sql-migrate");
211
- client.dataconnect.sdk = {};
212
- client.dataconnect.sdk.generate = loadCommand("dataconnect-sdk-generate");
213
- }
203
+ client.dataconnect = {};
204
+ client.setup.emulators.dataconnect = loadCommand("setup-emulators-dataconnect");
205
+ client.dataconnect.services = {};
206
+ client.dataconnect.services.list = loadCommand("dataconnect-services-list");
207
+ client.dataconnect.sql = {};
208
+ client.dataconnect.sql.diff = loadCommand("dataconnect-sql-diff");
209
+ client.dataconnect.sql.migrate = loadCommand("dataconnect-sql-migrate");
210
+ client.dataconnect.sdk = {};
211
+ client.dataconnect.sdk.generate = loadCommand("dataconnect-sdk-generate");
214
212
  client.target = loadCommand("target");
215
213
  client.target.apply = loadCommand("target-apply");
216
214
  client.target.clear = loadCommand("target-clear");
@@ -76,13 +76,11 @@ if ((0, experiments_1.isEnabled)("genkit")) {
76
76
  checked: false,
77
77
  });
78
78
  }
79
- if ((0, experiments_1.isEnabled)("dataconnect")) {
80
- choices.push({
81
- value: "dataconnect",
82
- name: "Data Connect: Set up a Firebase Data Connect service.",
83
- checked: false,
84
- });
85
- }
79
+ choices.push({
80
+ value: "dataconnect",
81
+ name: "Data Connect: Set up a Firebase Data Connect service.",
82
+ checked: false,
83
+ });
86
84
  const featureNames = choices.map((choice) => choice.value);
87
85
  const DESCRIPTION = `Interactively configure the current directory as a Firebase project or initialize new features in an already configured Firebase project directory.
88
86
 
@@ -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
  });
@@ -12,7 +12,7 @@ const rimraf = require("rimraf");
12
12
  const utils = require("../utils");
13
13
  const JAR_CACHE_DIR = process.env.FIREBASE_CRASHLYTICS_BUILDTOOLS_PATH ||
14
14
  path.join(os.homedir(), ".cache", "firebase", "crashlytics", "buildtools");
15
- const JAR_VERSION = "3.0.0";
15
+ const JAR_VERSION = "3.0.2";
16
16
  const JAR_URL = `https://dl.google.com/android/maven2/com/google/firebase/firebase-crashlytics-buildtools/${JAR_VERSION}/firebase-crashlytics-buildtools-${JAR_VERSION}.jar`;
17
17
  async function fetchBuildtoolsJar() {
18
18
  if (process.env.CRASHLYTICS_LOCAL_JAR) {
@@ -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
+ });
@@ -539,9 +539,6 @@ async function startAll(options, showUI = true, runningTestScript = false) {
539
539
  rc: options.rc,
540
540
  });
541
541
  await startEmulator(dataConnectEmulator);
542
- if (!utils.isVSCodeExtension()) {
543
- await dataConnectEmulator.connectToPostgres();
544
- }
545
542
  }
546
543
  if (listenForEmulator.storage) {
547
544
  const storageAddr = legacyGetFirstAddr(types_1.Emulators.STORAGE);
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DataConnectEmulatorClient = exports.DataConnectEmulator = void 0;
3
+ exports.checkIfDataConnectEmulatorRunningOnAddress = exports.DataConnectEmulatorClient = exports.DataConnectEmulator = exports.dataConnectEmulatorEvents = 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");
@@ -10,10 +11,16 @@ const error_1 = require("../error");
10
11
  const emulatorLogger_1 = require("./emulatorLogger");
11
12
  const types_2 = require("../dataconnect/types");
12
13
  const portUtils_1 = require("./portUtils");
14
+ const apiv2_1 = require("../apiv2");
13
15
  const registry_1 = require("./registry");
16
+ const load_1 = require("../dataconnect/load");
17
+ const utils_1 = require("../utils");
18
+ const events_1 = require("events");
19
+ exports.dataConnectEmulatorEvents = new events_1.EventEmitter();
14
20
  class DataConnectEmulator {
15
21
  constructor(args) {
16
22
  this.args = args;
23
+ this.usingExistingEmulator = false;
17
24
  this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.DATACONNECT);
18
25
  this.emulatorClient = new DataConnectEmulatorClient();
19
26
  }
@@ -32,18 +39,39 @@ class DataConnectEmulator {
32
39
  catch (err) {
33
40
  this.logger.log("DEBUG", `'fdc build' failed with error: ${err.message}`);
34
41
  }
35
- return (0, downloadableEmulators_1.start)(types_1.Emulators.DATACONNECT, {
36
- auto_download: this.args.auto_download,
37
- listen: (0, portUtils_1.listenSpecsToString)(this.args.listen),
38
- config_dir: this.args.configDir,
39
- project_id: this.args.projectId,
40
- service_location: this.args.locationId,
41
- });
42
+ const alreadyRunning = await this.discoverRunningInstance();
43
+ if (alreadyRunning) {
44
+ this.logger.logLabeled("INFO", "Data Connect", "Detected an instance of the emulator already running with your service, reusing it. This emulator will not be shut down at the end of this command.");
45
+ this.usingExistingEmulator = true;
46
+ this.watchUnmanagedInstance();
47
+ }
48
+ else {
49
+ await (0, downloadableEmulators_1.start)(types_1.Emulators.DATACONNECT, {
50
+ auto_download: this.args.auto_download,
51
+ listen: (0, portUtils_1.listenSpecsToString)(this.args.listen),
52
+ config_dir: this.args.configDir,
53
+ service_location: this.args.locationId,
54
+ });
55
+ this.usingExistingEmulator = false;
56
+ }
57
+ if (!(0, utils_1.isVSCodeExtension)()) {
58
+ await this.connectToPostgres();
59
+ }
60
+ return;
42
61
  }
43
- connect() {
62
+ async connect() {
63
+ const emuInfo = await this.emulatorClient.getInfo();
64
+ if (!emuInfo) {
65
+ this.logger.logLabeled("ERROR", "Data Connect", "Could not connect to Data Connect emulator. Check dataconnect-debug.log for more details.");
66
+ return Promise.reject();
67
+ }
44
68
  return Promise.resolve();
45
69
  }
46
- stop() {
70
+ async stop() {
71
+ if (this.usingExistingEmulator) {
72
+ this.logger.logLabeled("INFO", "Data Connect", "Skipping cleanup of Data Connect emulator, as it was not started by this process.");
73
+ return;
74
+ }
47
75
  return (0, downloadableEmulators_1.stop)(types_1.Emulators.DATACONNECT);
48
76
  }
49
77
  getInfo() {
@@ -62,6 +90,8 @@ class DataConnectEmulator {
62
90
  static async generate(args) {
63
91
  const commandInfo = await (0, downloadableEmulators_1.downloadIfNecessary)(types_1.Emulators.DATACONNECT);
64
92
  const cmd = [
93
+ "--logtostderr",
94
+ "-v=2",
65
95
  "generate",
66
96
  `--service_location=${args.locationId}`,
67
97
  `--config_dir=${args.configDir}`,
@@ -78,7 +108,7 @@ class DataConnectEmulator {
78
108
  static async build(args) {
79
109
  var _a;
80
110
  const commandInfo = await (0, downloadableEmulators_1.downloadIfNecessary)(types_1.Emulators.DATACONNECT);
81
- const cmd = ["build", `--config_dir=${args.configDir}`];
111
+ const cmd = ["--logtostderr", "-v=2", "build", `--config_dir=${args.configDir}`];
82
112
  const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8" });
83
113
  if (res.error) {
84
114
  throw new error_1.FirebaseError(`Error starting up Data Connect build: ${res.error.message}`, {
@@ -105,14 +135,58 @@ class DataConnectEmulator {
105
135
  }
106
136
  return (_b = (_a = this.args.rc.getDataconnect()) === null || _a === void 0 ? void 0 : _a.postgres) === null || _b === void 0 ? void 0 : _b.localConnectionString;
107
137
  }
138
+ async discoverRunningInstance() {
139
+ const emuInfo = await this.emulatorClient.getInfo();
140
+ if (!emuInfo) {
141
+ return false;
142
+ }
143
+ const serviceInfo = await (0, load_1.load)(this.args.projectId, this.args.locationId, this.args.configDir);
144
+ const sameService = emuInfo.services.find((s) => serviceInfo.dataConnectYaml.serviceId === s.serviceId);
145
+ if (!sameService) {
146
+ throw new error_1.FirebaseError(`There is a Data Connect emulator already running on ${this.args.listen[0].address}:${this.args.listen[0].port}, but it is emulating a different service. Please stop that instance of the Data Connect emulator, or specify a different port in 'firebase.json'`);
147
+ }
148
+ if (sameService.connectionString &&
149
+ sameService.connectionString !== this.getLocalConectionString()) {
150
+ throw new error_1.FirebaseError(`There is a Data Connect emulator already running, but it is using a different Postgres connection string. Please stop that instance of the Data Connect emulator, or specify a different port in 'firebase.json'`);
151
+ }
152
+ return true;
153
+ }
154
+ watchUnmanagedInstance() {
155
+ return setInterval(async () => {
156
+ if (!this.usingExistingEmulator) {
157
+ return;
158
+ }
159
+ const emuInfo = await this.emulatorClient.getInfo();
160
+ if (!emuInfo) {
161
+ this.logger.logLabeled("INFO", "Data Connect", "The already running emulator seems to have shut down. Starting a new instance of the Data Connect emulator...");
162
+ await this.start();
163
+ exports.dataConnectEmulatorEvents.emit("restart");
164
+ }
165
+ }, 5000);
166
+ }
108
167
  async connectToPostgres(localConnectionString, database, serviceId) {
109
168
  const connectionString = localConnectionString !== null && localConnectionString !== void 0 ? localConnectionString : this.getLocalConectionString();
110
169
  if (!connectionString) {
111
- this.logger.log("DEBUG", "No Postgres connection string found, not connecting to Postgres");
112
- return false;
170
+ const msg = `No Postgres connection string found in '.firebaserc'. The Data Connect emulator will not be able to execute operations.
171
+ Run ${clc.bold("firebase setup:emulators:dataconnect")} to set up a Postgres connection.`;
172
+ throw new error_1.FirebaseError(msg);
113
173
  }
114
- await this.emulatorClient.configureEmulator({ connectionString, database, serviceId });
115
- return true;
174
+ const MAX_RETRIES = 3;
175
+ for (let i = 1; i <= MAX_RETRIES; i++) {
176
+ try {
177
+ this.logger.logLabeled("DEBUG", "Data Connect", `Connecting to ${connectionString}}`);
178
+ await this.emulatorClient.configureEmulator({ connectionString, database, serviceId });
179
+ return true;
180
+ }
181
+ catch (err) {
182
+ if (i === MAX_RETRIES) {
183
+ throw err;
184
+ }
185
+ this.logger.logLabeled("DEBUG", "Data Connect", `Retrying connectToPostgress call (${i} of ${MAX_RETRIES} attempts): ${err}`);
186
+ await new Promise((resolve) => setTimeout(resolve, 800));
187
+ }
188
+ }
189
+ return false;
116
190
  }
117
191
  }
118
192
  exports.DataConnectEmulator = DataConnectEmulator;
@@ -121,6 +195,7 @@ class DataConnectEmulatorClient {
121
195
  this.client = undefined;
122
196
  }
123
197
  async configureEmulator(body) {
198
+ var _a, _b;
124
199
  if (!this.client) {
125
200
  this.client = registry_1.EmulatorRegistry.client(types_1.Emulators.DATACONNECT);
126
201
  }
@@ -130,10 +205,33 @@ class DataConnectEmulatorClient {
130
205
  }
131
206
  catch (err) {
132
207
  if (err.status === 500) {
133
- throw new error_1.FirebaseError(`Data Connect emulator: ${err.context.body.message}`);
208
+ throw new error_1.FirebaseError(`Data Connect emulator: ${(_b = (_a = err === null || err === void 0 ? void 0 : err.context) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.message}`);
134
209
  }
135
210
  throw err;
136
211
  }
137
212
  }
213
+ async getInfo() {
214
+ if (!this.client) {
215
+ this.client = registry_1.EmulatorRegistry.client(types_1.Emulators.DATACONNECT);
216
+ }
217
+ return getInfo(this.client);
218
+ }
138
219
  }
139
220
  exports.DataConnectEmulatorClient = DataConnectEmulatorClient;
221
+ async function checkIfDataConnectEmulatorRunningOnAddress(l) {
222
+ const client = new apiv2_1.Client({
223
+ urlPrefix: `http:/${l.family === "IPv6" ? `[${l.address}]` : l.address}:${l.port}`,
224
+ auth: false,
225
+ });
226
+ return getInfo(client);
227
+ }
228
+ exports.checkIfDataConnectEmulatorRunningOnAddress = checkIfDataConnectEmulatorRunningOnAddress;
229
+ async function getInfo(client) {
230
+ try {
231
+ const res = await client.get("emulator/info");
232
+ return res.body;
233
+ }
234
+ catch (err) {
235
+ return;
236
+ }
237
+ }
@@ -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
  }
@@ -149,7 +149,7 @@ class EmulatorHub extends ExpressBasedEmulator_1.ExpressBasedEmulator {
149
149
  const locatorPath = EmulatorHub.getLocatorFilePath(this.args.projectId);
150
150
  return new Promise((resolve, reject) => {
151
151
  fs.unlink(locatorPath, (e) => {
152
- if (e) {
152
+ if (e && e.code !== "ENOENT") {
153
153
  reject(e);
154
154
  }
155
155
  else {
@@ -11,6 +11,7 @@ const types_1 = require("./types");
11
11
  const constants_1 = require("./constants");
12
12
  const emulatorLogger_1 = require("./emulatorLogger");
13
13
  const node_child_process_1 = require("node:child_process");
14
+ const dataconnectEmulator_1 = require("./dataconnectEmulator");
14
15
  const RESTRICTED_PORTS = new Set([
15
16
  1,
16
17
  7,
@@ -219,6 +220,14 @@ async function resolveHostAndAssignPorts(listenConfig) {
219
220
  available.push(listen);
220
221
  }
221
222
  else {
223
+ if (/^dataconnect/i.exec(name)) {
224
+ const alreadyRunning = await (0, dataconnectEmulator_1.checkIfDataConnectEmulatorRunningOnAddress)(listen);
225
+ if (alreadyRunning) {
226
+ emuLogger.logLabeled("DEBUG", "dataconnect", `Detected already running emulator on ${listen.address}:${listen.port}. Will attempt to reuse it.`);
227
+ }
228
+ available.push(listen);
229
+ continue;
230
+ }
222
231
  if (!portFixed) {
223
232
  if (i > 0) {
224
233
  emuLogger.logLabeled("DEBUG", name, `Port ${p} taken on secondary address ${addr.address}, will keep searching to find a better port.`);
@@ -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;
@@ -92,8 +92,8 @@ exports.ALL_EXPERIMENTS = experiments({
92
92
  public: false,
93
93
  },
94
94
  dataconnect: {
95
- shortDescription: "Enable Data Connect related features.",
96
- fullDescription: "Enable Data Connect related features.",
95
+ shortDescription: "Deprecated. Previosuly, enabled Data Connect related features.",
96
+ fullDescription: "Deprecated. Previously, enabled Data Connect related features.",
97
97
  public: false,
98
98
  },
99
99
  genkit: {
@@ -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.4",
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!