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.
- package/lib/apphosting/githubConnections.js +2 -2
- package/lib/commands/index.js +9 -11
- package/lib/commands/init.js +5 -7
- package/lib/commands/setup-emulators-dataconnect.js +23 -3
- package/lib/crashlytics/buildToolsJarHelper.js +1 -1
- package/lib/dataconnect/client.js +55 -9
- package/lib/emulator/adminSdkConfig.test.js +16 -0
- package/lib/emulator/controller.js +0 -3
- package/lib/emulator/dataconnectEmulator.js +114 -16
- package/lib/emulator/downloadableEmulators.js +16 -15
- package/lib/emulator/hub.js +1 -1
- package/lib/emulator/portUtils.js +9 -0
- package/lib/emulator/registry.js +1 -1
- package/lib/emulator/storage/apis/shared.js +2 -1
- package/lib/emulator/storage/persistence.js +5 -13
- package/lib/emulator/storage/rfc.js +9 -0
- package/lib/experiments.js +2 -2
- package/lib/firestore/api-sort.test.js +74 -0
- package/lib/firestore/backupUtils.test.js +18 -0
- package/lib/firestore/pretty-print.test.js +53 -0
- package/lib/firestore/util.test.js +42 -0
- package/lib/frameworks/compose/discover/mockFileSystem.js +36 -0
- package/lib/frameworks/index.js +2 -6
- package/lib/frameworks/next/utils.js +2 -8
- package/lib/listFiles.js +0 -1
- package/lib/rc.js +1 -0
- package/package.json +8 -6
- package/templates/init/dataconnect/mutations.gql +19 -14
- package/templates/init/dataconnect/queries.gql +28 -23
- package/templates/init/dataconnect/schema.gql +1 -1
|
@@ -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
|
}
|
package/lib/commands/index.js
CHANGED
|
@@ -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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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");
|
package/lib/commands/init.js
CHANGED
|
@@ -76,13 +76,11 @@ if ((0, experiments_1.isEnabled)("genkit")) {
|
|
|
76
76
|
checked: false,
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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(
|
|
43
|
-
const op = await dataconnectClient().delete(
|
|
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
|
-
|
|
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
|
|
89
|
-
|
|
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
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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.
|
|
50
|
-
expectedSize:
|
|
51
|
-
expectedChecksum: "
|
|
49
|
+
version: "1.2.2",
|
|
50
|
+
expectedSize: 24007488,
|
|
51
|
+
expectedChecksum: "c1fb77895203681479ee5dd22d57249f",
|
|
52
52
|
}
|
|
53
53
|
: process.platform === "win32"
|
|
54
54
|
? {
|
|
55
|
-
version: "1.2.
|
|
56
|
-
expectedSize:
|
|
57
|
-
expectedChecksum: "
|
|
55
|
+
version: "1.2.2",
|
|
56
|
+
expectedSize: 24414208,
|
|
57
|
+
expectedChecksum: "7e263c2b2bc9055ead2db8102e883534",
|
|
58
58
|
}
|
|
59
59
|
: {
|
|
60
|
-
version: "1.2.
|
|
61
|
-
expectedSize:
|
|
62
|
-
expectedChecksum: "
|
|
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:
|
|
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
|
}
|
package/lib/emulator/hub.js
CHANGED
|
@@ -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.`);
|
package/lib/emulator/registry.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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;
|
package/lib/experiments.js
CHANGED
|
@@ -92,8 +92,8 @@ exports.ALL_EXPERIMENTS = experiments({
|
|
|
92
92
|
public: false,
|
|
93
93
|
},
|
|
94
94
|
dataconnect: {
|
|
95
|
-
shortDescription: "
|
|
96
|
-
fullDescription: "
|
|
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;
|
package/lib/frameworks/index.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
}
|
|
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
package/lib/rc.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firebase-tools",
|
|
3
|
-
"version": "13.11.
|
|
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": "^
|
|
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.
|
|
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": "^
|
|
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": "^
|
|
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.
|
|
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
|
-
#
|
|
4
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
#
|
|
18
|
+
# # The request variable name doesn't have to match the field name.
|
|
19
|
+
# text: $content,
|
|
14
20
|
# subject: $subject,
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
29
|
+
|
|
30
|
+
# mutation CreateRecipient($emailId: UUID) @auth(level: USER) {
|
|
22
31
|
# recipient_insert(data: {
|
|
23
32
|
# emailId: $emailId,
|
|
24
|
-
#
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
# query
|
|
6
|
-
#
|
|
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
|
-
|
|
10
|
-
# query
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
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: {
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
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
|
|
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
|
-
#
|
|
47
|
+
# from: {uid: {eq: $uid }}
|
|
43
48
|
# }) {
|
|
44
49
|
# id subject sent
|
|
45
50
|
# content: text
|
|
46
|
-
# sender: from { name
|
|
51
|
+
# sender: from { name address uid }
|
|
47
52
|
# to: recipients_on_email {
|
|
48
|
-
# user { name
|
|
53
|
+
# user { name address uid }
|
|
49
54
|
# }
|
|
50
55
|
# }
|
|
51
56
|
# }
|