firebase-tools 11.14.1 → 11.14.2
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/bin/firebase.js +3 -3
- package/lib/commands/deploy.js +3 -3
- package/lib/commands/emulators-start.js +20 -16
- package/lib/deploy/functions/params.js +9 -1
- package/lib/deploy/functions/release/fabricator.js +1 -0
- package/lib/deploy/hosting/convertConfig.js +2 -1
- package/lib/deploy/index.js +10 -0
- package/lib/emulator/ExpressBasedEmulator.js +92 -0
- package/lib/emulator/auth/cloudFunctions.js +3 -12
- package/lib/emulator/auth/index.js +2 -2
- package/lib/emulator/auth/utils.js +1 -1
- package/lib/emulator/commandUtils.js +21 -44
- package/lib/emulator/constants.js +2 -9
- package/lib/emulator/controller.js +129 -130
- package/lib/emulator/databaseEmulator.js +3 -8
- package/lib/emulator/dns.js +49 -0
- package/lib/emulator/downloadableEmulators.js +19 -13
- package/lib/emulator/emulatorLogger.js +14 -7
- package/lib/emulator/env.js +35 -0
- package/lib/emulator/eventarcEmulator.js +2 -8
- package/lib/emulator/extensions/postinstall.js +8 -8
- package/lib/emulator/extensionsEmulator.js +7 -7
- package/lib/emulator/firestoreEmulator.js +3 -11
- package/lib/emulator/functionsEmulator.js +24 -60
- package/lib/emulator/functionsEmulatorShared.js +5 -3
- package/lib/emulator/functionsEmulatorShell.js +1 -1
- package/lib/emulator/hub.js +50 -58
- package/lib/emulator/hubClient.js +24 -10
- package/lib/emulator/hubExport.js +3 -11
- package/lib/emulator/portUtils.js +208 -29
- package/lib/emulator/pubsubEmulator.js +11 -12
- package/lib/emulator/registry.js +11 -20
- package/lib/emulator/storage/apis/gcloud.js +3 -6
- package/lib/emulator/storage/cloudFunctions.js +2 -7
- package/lib/emulator/storage/metadata.js +11 -5
- package/lib/emulator/storage/rules/runtime.js +1 -6
- package/lib/emulator/ui.js +6 -7
- package/lib/experiments.js +10 -1
- package/lib/extensions/displayExtensionInfo.js +43 -6
- package/lib/extensions/secretsUtils.js +2 -1
- package/lib/frameworks/angular/index.js +1 -1
- package/lib/frameworks/express/index.js +1 -1
- package/lib/frameworks/index.js +33 -17
- package/lib/frameworks/next/index.js +1 -1
- package/lib/frameworks/nuxt/index.js +1 -1
- package/lib/frameworks/vite/index.js +1 -1
- package/lib/functions/ensureTargeted.js +21 -0
- package/lib/functions/env.js +33 -19
- package/lib/functionsShellCommandAction.js +12 -5
- package/lib/gcp/run.js +0 -1
- package/lib/handlePreviewToggles.js +2 -2
- package/lib/hosting/functionsProxy.js +2 -3
- package/lib/hosting/implicitInit.js +2 -11
- package/lib/hosting/runTags.js +3 -4
- package/lib/serve/functions.js +8 -10
- package/lib/serve/hosting.js +10 -4
- package/lib/serve/index.js +2 -2
- package/lib/utils.js +14 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/templates/hosting/init.js +3 -2
- package/lib/emulator/emulatorServer.js +0 -29
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
const
|
|
3
|
+
exports.resolveHostAndAssignPorts = exports.waitForPortClosed = exports.checkListenable = void 0;
|
|
4
|
+
const clc = require("colorette");
|
|
5
5
|
const tcpport = require("tcp-port-used");
|
|
6
|
-
const
|
|
6
|
+
const node_net_1 = require("node:net");
|
|
7
7
|
const error_1 = require("../error");
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
const
|
|
8
|
+
const utils = require("../utils");
|
|
9
|
+
const dns_1 = require("./dns");
|
|
10
|
+
const types_1 = require("./types");
|
|
11
|
+
const constants_1 = require("./constants");
|
|
12
|
+
const emulatorLogger_1 = require("./emulatorLogger");
|
|
13
|
+
const RESTRICTED_PORTS = new Set([
|
|
11
14
|
1,
|
|
12
15
|
7,
|
|
13
16
|
9,
|
|
@@ -75,11 +78,10 @@ const RESTRICTED_PORTS = [
|
|
|
75
78
|
6668,
|
|
76
79
|
6669,
|
|
77
80
|
6697,
|
|
78
|
-
];
|
|
81
|
+
]);
|
|
79
82
|
function isRestricted(port) {
|
|
80
|
-
return RESTRICTED_PORTS.
|
|
83
|
+
return RESTRICTED_PORTS.has(port);
|
|
81
84
|
}
|
|
82
|
-
exports.isRestricted = isRestricted;
|
|
83
85
|
function suggestUnrestricted(port) {
|
|
84
86
|
if (!isRestricted(port)) {
|
|
85
87
|
return port;
|
|
@@ -90,27 +92,37 @@ function suggestUnrestricted(port) {
|
|
|
90
92
|
}
|
|
91
93
|
return newPort;
|
|
92
94
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
95
|
+
async function checkListenable(arg1, port) {
|
|
96
|
+
const addr = port === undefined ? arg1 : listenSpec(arg1, port);
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
const dummyServer = (0, node_net_1.createServer)(() => {
|
|
99
|
+
});
|
|
100
|
+
dummyServer.once("error", (err) => {
|
|
101
|
+
dummyServer.removeAllListeners();
|
|
102
|
+
const e = err;
|
|
103
|
+
if (e.code === "EADDRINUSE" || e.code === "EACCES") {
|
|
104
|
+
resolve(false);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
reject(e);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
dummyServer.once("listening", () => {
|
|
111
|
+
dummyServer.removeAllListeners();
|
|
112
|
+
dummyServer.close((err) => {
|
|
113
|
+
dummyServer.removeAllListeners();
|
|
114
|
+
if (err) {
|
|
115
|
+
reject(err);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
resolve(true);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
dummyServer.listen({ host: addr.address, port: addr.port, ipv6Only: addr.family === "IPv6" });
|
|
123
|
+
});
|
|
112
124
|
}
|
|
113
|
-
exports.
|
|
125
|
+
exports.checkListenable = checkListenable;
|
|
114
126
|
async function waitForPortClosed(port, host) {
|
|
115
127
|
const interval = 250;
|
|
116
128
|
const timeout = 60000;
|
|
@@ -122,3 +134,170 @@ async function waitForPortClosed(port, host) {
|
|
|
122
134
|
}
|
|
123
135
|
}
|
|
124
136
|
exports.waitForPortClosed = waitForPortClosed;
|
|
137
|
+
const EMULATOR_CAN_LISTEN_ON_PRIMARY_ONLY = {
|
|
138
|
+
database: true,
|
|
139
|
+
firestore: true,
|
|
140
|
+
"firestore.websocket": true,
|
|
141
|
+
pubsub: true,
|
|
142
|
+
hub: false,
|
|
143
|
+
ui: false,
|
|
144
|
+
auth: true,
|
|
145
|
+
eventarc: true,
|
|
146
|
+
extensions: true,
|
|
147
|
+
functions: true,
|
|
148
|
+
logging: true,
|
|
149
|
+
storage: true,
|
|
150
|
+
hosting: true,
|
|
151
|
+
};
|
|
152
|
+
const MAX_PORT = 65535;
|
|
153
|
+
async function resolveHostAndAssignPorts(listenConfig) {
|
|
154
|
+
const lookupForHost = new Map();
|
|
155
|
+
const takenPorts = new Map();
|
|
156
|
+
const result = {};
|
|
157
|
+
const tasks = [];
|
|
158
|
+
for (const name of Object.keys(listenConfig)) {
|
|
159
|
+
const config = listenConfig[name];
|
|
160
|
+
if (!config) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
else if (config instanceof Array) {
|
|
164
|
+
result[name] = config;
|
|
165
|
+
for (const { port } of config) {
|
|
166
|
+
takenPorts.set(port, name);
|
|
167
|
+
}
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const { host, port, portFixed } = config;
|
|
171
|
+
let lookup = lookupForHost.get(host);
|
|
172
|
+
if (!lookup) {
|
|
173
|
+
lookup = dns_1.Resolver.DEFAULT.lookupAll(host);
|
|
174
|
+
lookupForHost.set(host, lookup);
|
|
175
|
+
}
|
|
176
|
+
const findAddrs = lookup.then(async (addrs) => {
|
|
177
|
+
const emuLogger = emulatorLogger_1.EmulatorLogger.forEmulator(name === "firestore.websocket" ? types_1.Emulators.FIRESTORE : name);
|
|
178
|
+
if (addrs.some((addr) => addr.address === dns_1.IPV6_UNSPECIFIED.address)) {
|
|
179
|
+
if (!addrs.some((addr) => addr.address === dns_1.IPV4_UNSPECIFIED.address)) {
|
|
180
|
+
emuLogger.logLabeled("DEBUG", name, `testing listening on IPv4 wildcard in addition to IPv6. To listen on IPv6 only, use "::0" instead.`);
|
|
181
|
+
addrs.push(dns_1.IPV4_UNSPECIFIED);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
for (let p = port; p <= MAX_PORT; p++) {
|
|
185
|
+
if (takenPorts.has(p)) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (!portFixed && RESTRICTED_PORTS.has(p)) {
|
|
189
|
+
emuLogger.logLabeled("DEBUG", name, `portUtils: skipping restricted port ${p}`);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const available = [];
|
|
193
|
+
const unavailable = [];
|
|
194
|
+
let i;
|
|
195
|
+
for (i = 0; i < addrs.length; i++) {
|
|
196
|
+
const addr = addrs[i];
|
|
197
|
+
const listen = listenSpec(addr, p);
|
|
198
|
+
if (await checkListenable(listen)) {
|
|
199
|
+
available.push(listen);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
if (!portFixed) {
|
|
203
|
+
if (i > 0) {
|
|
204
|
+
emuLogger.logLabeled("DEBUG", name, `Port ${p} taken on secondary address ${addr.address}, will keep searching to find a better port.`);
|
|
205
|
+
}
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
unavailable.push(addr.address);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (i === addrs.length) {
|
|
212
|
+
if (unavailable.length > 0) {
|
|
213
|
+
if (unavailable[0] === addrs[0].address) {
|
|
214
|
+
return fixedPortNotAvailable(name, host, port, emuLogger, unavailable);
|
|
215
|
+
}
|
|
216
|
+
warnPartiallyAvailablePort(emuLogger, port, available, unavailable);
|
|
217
|
+
}
|
|
218
|
+
if (takenPorts.has(p)) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
takenPorts.set(p, name);
|
|
222
|
+
if (RESTRICTED_PORTS.has(p)) {
|
|
223
|
+
const suggested = suggestUnrestricted(port);
|
|
224
|
+
emuLogger.logLabeled("WARN", name, `Port ${port} is restricted by some web browsers, including Chrome. You may want to choose a different port such as ${suggested}.`);
|
|
225
|
+
}
|
|
226
|
+
if (p !== port && name !== "firestore.websocket") {
|
|
227
|
+
emuLogger.logLabeled("WARN", `${portDescription(name)} unable to start on port ${port}, starting on ${p} instead.`);
|
|
228
|
+
}
|
|
229
|
+
if (available.length > 1 && EMULATOR_CAN_LISTEN_ON_PRIMARY_ONLY[name]) {
|
|
230
|
+
emuLogger.logLabeled("DEBUG", name, `${portDescription(name)} only supports listening on one address (${available[0].address}). Not listening on ${addrs
|
|
231
|
+
.slice(1)
|
|
232
|
+
.map((s) => s.address)
|
|
233
|
+
.join(",")}`);
|
|
234
|
+
result[name] = [available[0]];
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
result[name] = available;
|
|
238
|
+
}
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return utils.reject(`Could not find any open port in ${port}-${MAX_PORT} for ${portDescription(name)}`, {});
|
|
243
|
+
});
|
|
244
|
+
tasks.push(findAddrs);
|
|
245
|
+
}
|
|
246
|
+
await Promise.all(tasks);
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
exports.resolveHostAndAssignPorts = resolveHostAndAssignPorts;
|
|
250
|
+
function portDescription(name) {
|
|
251
|
+
return name === "firestore.websocket"
|
|
252
|
+
? `websocket server for ${types_1.Emulators.FIRESTORE}`
|
|
253
|
+
: constants_1.Constants.description(name);
|
|
254
|
+
}
|
|
255
|
+
function warnPartiallyAvailablePort(emuLogger, port, available, unavailable) {
|
|
256
|
+
emuLogger.logLabeled("WARN", `Port ${port} is available on ` +
|
|
257
|
+
available.map((s) => s.address).join(",") +
|
|
258
|
+
` but not ${unavailable.join(",")}. This may cause issues with some clients.`);
|
|
259
|
+
emuLogger.logLabeled("WARN", `If you encounter connectivity issues, consider switching to a different port or explicitly specifying ${clc.yellow('"host": "<ip address>"')} instead of hostname in firebase.json`);
|
|
260
|
+
}
|
|
261
|
+
function fixedPortNotAvailable(name, host, port, emuLogger, unavailableAddrs) {
|
|
262
|
+
if (unavailableAddrs.length !== 1 || unavailableAddrs[0] !== host) {
|
|
263
|
+
host = `${host} (${unavailableAddrs.join(",")})`;
|
|
264
|
+
}
|
|
265
|
+
const description = portDescription(name);
|
|
266
|
+
emuLogger.logLabeled("WARN", `Port ${port} is not open on ${host}, could not start ${description}.`);
|
|
267
|
+
if (name === "firestore.websocket") {
|
|
268
|
+
emuLogger.logLabeled("WARN", `To select a different port, specify that port in a firebase.json config file:
|
|
269
|
+
{
|
|
270
|
+
// ...
|
|
271
|
+
"emulators": {
|
|
272
|
+
"${types_1.Emulators.FIRESTORE}": {
|
|
273
|
+
"host": "${clc.yellow("HOST")}",
|
|
274
|
+
...
|
|
275
|
+
"websocketPort": "${clc.yellow("WEBSOCKET_PORT")}"
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}`);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
emuLogger.logLabeled("WARN", `To select a different host/port, specify that host/port in a firebase.json config file:
|
|
282
|
+
{
|
|
283
|
+
// ...
|
|
284
|
+
"emulators": {
|
|
285
|
+
"${emuLogger.name}": {
|
|
286
|
+
"host": "${clc.yellow("HOST")}",
|
|
287
|
+
"port": "${clc.yellow("PORT")}"
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}`);
|
|
291
|
+
}
|
|
292
|
+
return utils.reject(`Could not start ${description}, port taken.`, {});
|
|
293
|
+
}
|
|
294
|
+
function listenSpec(lookup, port) {
|
|
295
|
+
if (lookup.family !== 4 && lookup.family !== 6) {
|
|
296
|
+
throw new Error(`Unsupported address family "${lookup.family}" for address ${lookup.address}.`);
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
address: lookup.address,
|
|
300
|
+
family: lookup.family === 4 ? "IPv4" : "IPv6",
|
|
301
|
+
port: port,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
@@ -4,7 +4,6 @@ exports.PubsubEmulator = void 0;
|
|
|
4
4
|
const uuid = require("uuid");
|
|
5
5
|
const pubsub_1 = require("@google-cloud/pubsub");
|
|
6
6
|
const downloadableEmulators = require("./downloadableEmulators");
|
|
7
|
-
const apiv2_1 = require("../apiv2");
|
|
8
7
|
const emulatorLogger_1 = require("./emulatorLogger");
|
|
9
8
|
const types_1 = require("../emulator/types");
|
|
10
9
|
const constants_1 = require("./constants");
|
|
@@ -14,14 +13,18 @@ class PubsubEmulator {
|
|
|
14
13
|
constructor(args) {
|
|
15
14
|
this.args = args;
|
|
16
15
|
this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.PUBSUB);
|
|
17
|
-
const { host, port } = this.getInfo();
|
|
18
|
-
this.pubsub = new pubsub_1.PubSub({
|
|
19
|
-
apiEndpoint: `${host}:${port}`,
|
|
20
|
-
projectId: this.args.projectId,
|
|
21
|
-
});
|
|
22
16
|
this.triggersForTopic = new Map();
|
|
23
17
|
this.subscriptionForTopic = new Map();
|
|
24
18
|
}
|
|
19
|
+
get pubsub() {
|
|
20
|
+
if (!this._pubsub) {
|
|
21
|
+
this._pubsub = new pubsub_1.PubSub({
|
|
22
|
+
apiEndpoint: registry_1.EmulatorRegistry.url(types_1.Emulators.PUBSUB).host,
|
|
23
|
+
projectId: this.args.projectId,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return this._pubsub;
|
|
27
|
+
}
|
|
25
28
|
async start() {
|
|
26
29
|
return downloadableEmulators.start(types_1.Emulators.PUBSUB, this.args);
|
|
27
30
|
}
|
|
@@ -94,14 +97,10 @@ class PubsubEmulator {
|
|
|
94
97
|
ensureFunctionsClient() {
|
|
95
98
|
if (this.client !== undefined)
|
|
96
99
|
return;
|
|
97
|
-
|
|
98
|
-
if (!funcEmulator) {
|
|
100
|
+
if (!registry_1.EmulatorRegistry.isRunning(types_1.Emulators.FUNCTIONS)) {
|
|
99
101
|
throw new error_1.FirebaseError(`Attempted to execute pubsub trigger but could not find the Functions emulator`);
|
|
100
102
|
}
|
|
101
|
-
this.client =
|
|
102
|
-
urlPrefix: `http://${registry_1.EmulatorRegistry.getInfoHostString(funcEmulator.getInfo())}`,
|
|
103
|
-
auth: false,
|
|
104
|
-
});
|
|
103
|
+
this.client = registry_1.EmulatorRegistry.client(types_1.Emulators.FUNCTIONS);
|
|
105
104
|
}
|
|
106
105
|
createLegacyEventRequestBody(topic, message) {
|
|
107
106
|
return {
|
package/lib/emulator/registry.js
CHANGED
|
@@ -6,6 +6,8 @@ const error_1 = require("../error");
|
|
|
6
6
|
const portUtils = require("./portUtils");
|
|
7
7
|
const constants_1 = require("./constants");
|
|
8
8
|
const emulatorLogger_1 = require("./emulatorLogger");
|
|
9
|
+
const utils_1 = require("../utils");
|
|
10
|
+
const apiv2_1 = require("../apiv2");
|
|
9
11
|
class EmulatorRegistry {
|
|
10
12
|
static async start(instance) {
|
|
11
13
|
const description = constants_1.Constants.description(instance.getName());
|
|
@@ -16,7 +18,7 @@ class EmulatorRegistry {
|
|
|
16
18
|
await instance.start();
|
|
17
19
|
if (instance.getName() !== types_1.Emulators.EXTENSIONS) {
|
|
18
20
|
const info = instance.getInfo();
|
|
19
|
-
await portUtils.waitForPortClosed(info.port, info.host);
|
|
21
|
+
await portUtils.waitForPortClosed(info.port, (0, utils_1.connectableHostname)(info.host));
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
static async stop(name) {
|
|
@@ -74,20 +76,12 @@ class EmulatorRegistry {
|
|
|
74
76
|
return this.INSTANCES.get(emulator);
|
|
75
77
|
}
|
|
76
78
|
static getInfo(emulator) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
var _a;
|
|
80
|
+
const info = (_a = EmulatorRegistry.get(emulator)) === null || _a === void 0 ? void 0 : _a.getInfo();
|
|
81
|
+
if (!info) {
|
|
79
82
|
return undefined;
|
|
80
83
|
}
|
|
81
|
-
return
|
|
82
|
-
}
|
|
83
|
-
static getInfoHostString(info) {
|
|
84
|
-
const { host, port } = info;
|
|
85
|
-
if (host.includes(":")) {
|
|
86
|
-
return `[${host}]:${port}`;
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
return `${host}:${port}`;
|
|
90
|
-
}
|
|
84
|
+
return Object.assign(Object.assign({}, info), { host: (0, utils_1.connectableHostname)(info.host) });
|
|
91
85
|
}
|
|
92
86
|
static url(emulator, req) {
|
|
93
87
|
const url = new URL("http://unknown/");
|
|
@@ -101,13 +95,7 @@ class EmulatorRegistry {
|
|
|
101
95
|
}
|
|
102
96
|
const info = EmulatorRegistry.getInfo(emulator);
|
|
103
97
|
if (info) {
|
|
104
|
-
if (info.host
|
|
105
|
-
url.hostname = "127.0.0.1";
|
|
106
|
-
}
|
|
107
|
-
else if (info.host === "::") {
|
|
108
|
-
url.hostname = "[::1]";
|
|
109
|
-
}
|
|
110
|
-
else if (info.host.includes(":")) {
|
|
98
|
+
if (info.host.includes(":")) {
|
|
111
99
|
url.hostname = `[${info.host}]`;
|
|
112
100
|
}
|
|
113
101
|
else {
|
|
@@ -120,6 +108,9 @@ class EmulatorRegistry {
|
|
|
120
108
|
}
|
|
121
109
|
return url;
|
|
122
110
|
}
|
|
111
|
+
static client(emulator, options = {}) {
|
|
112
|
+
return new apiv2_1.Client(Object.assign({ urlPrefix: EmulatorRegistry.url(emulator).toString(), auth: false }, options));
|
|
113
|
+
}
|
|
123
114
|
static set(emulator, instance) {
|
|
124
115
|
this.INSTANCES.set(emulator, instance);
|
|
125
116
|
}
|
|
@@ -187,7 +187,6 @@ function createCloudEndpoints(emulator) {
|
|
|
187
187
|
return res.json(new metadata_1.CloudStorageObjectMetadata(metadata));
|
|
188
188
|
});
|
|
189
189
|
gcloudStorageAPI.post("/b/:bucketId/o/:objectId/acl", async (req, res) => {
|
|
190
|
-
var _a, _b;
|
|
191
190
|
emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE).log("WARN_ONCE", "Cloud Storage ACLs are not supported in the Storage Emulator. All related methods will succeed, but have no effect.");
|
|
192
191
|
let getObjectResponse;
|
|
193
192
|
try {
|
|
@@ -207,11 +206,13 @@ function createCloudEndpoints(emulator) {
|
|
|
207
206
|
}
|
|
208
207
|
const { metadata } = getObjectResponse;
|
|
209
208
|
metadata.update({});
|
|
209
|
+
const selfLink = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE);
|
|
210
|
+
selfLink.pathname = `/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}/acl/allUsers`;
|
|
210
211
|
return res.json({
|
|
211
212
|
kind: "storage#objectAccessControl",
|
|
212
213
|
object: metadata.name,
|
|
213
214
|
id: `${req.params.bucketId}/${metadata.name}/${metadata.generation}/allUsers`,
|
|
214
|
-
selfLink:
|
|
215
|
+
selfLink: selfLink.toString(),
|
|
215
216
|
bucket: metadata.bucket,
|
|
216
217
|
entity: req.body.entity,
|
|
217
218
|
role: req.body.role,
|
|
@@ -222,10 +223,6 @@ function createCloudEndpoints(emulator) {
|
|
|
222
223
|
gcloudStorageAPI.post("/upload/storage/v1/b/:bucketId/o", async (req, res) => {
|
|
223
224
|
const uploadType = req.query.uploadType || req.header("X-Goog-Upload-Protocol");
|
|
224
225
|
if (uploadType === "resumable") {
|
|
225
|
-
const emulatorInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE);
|
|
226
|
-
if (emulatorInfo === undefined) {
|
|
227
|
-
return res.sendStatus(500);
|
|
228
|
-
}
|
|
229
226
|
const name = getIncomingFileNameFromRequest(req.query, req.body);
|
|
230
227
|
if (name === undefined) {
|
|
231
228
|
res.sendStatus(400);
|
|
@@ -6,7 +6,6 @@ const registry_1 = require("../registry");
|
|
|
6
6
|
const types_1 = require("../types");
|
|
7
7
|
const emulatorLogger_1 = require("../emulatorLogger");
|
|
8
8
|
const metadata_1 = require("./metadata");
|
|
9
|
-
const apiv2_1 = require("../../apiv2");
|
|
10
9
|
const STORAGE_V2_ACTION_MAP = {
|
|
11
10
|
finalize: "finalized",
|
|
12
11
|
metadataUpdate: "metadataUpdated",
|
|
@@ -17,16 +16,12 @@ class StorageCloudFunctions {
|
|
|
17
16
|
constructor(projectId) {
|
|
18
17
|
this.projectId = projectId;
|
|
19
18
|
this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE);
|
|
20
|
-
this.multicastOrigin = "";
|
|
21
19
|
this.multicastPath = "";
|
|
22
20
|
this.enabled = false;
|
|
23
|
-
|
|
24
|
-
if (functionsEmulator) {
|
|
21
|
+
if (registry_1.EmulatorRegistry.isRunning(types_1.Emulators.FUNCTIONS)) {
|
|
25
22
|
this.enabled = true;
|
|
26
|
-
this.functionsEmulatorInfo = functionsEmulator.getInfo();
|
|
27
|
-
this.multicastOrigin = `http://${registry_1.EmulatorRegistry.getInfoHostString(this.functionsEmulatorInfo)}`;
|
|
28
23
|
this.multicastPath = `/functions/projects/${projectId}/trigger_multicast`;
|
|
29
|
-
this.client =
|
|
24
|
+
this.client = registry_1.EmulatorRegistry.client(types_1.Emulators.FUNCTIONS);
|
|
30
25
|
}
|
|
31
26
|
}
|
|
32
27
|
async dispatch(action, object) {
|
|
@@ -237,11 +237,12 @@ class OutgoingFirebaseMetadata {
|
|
|
237
237
|
exports.OutgoingFirebaseMetadata = OutgoingFirebaseMetadata;
|
|
238
238
|
class CloudStorageBucketMetadata {
|
|
239
239
|
constructor(id) {
|
|
240
|
-
var _a, _b;
|
|
241
240
|
this.kind = "storage#bucket";
|
|
242
241
|
this.name = id;
|
|
243
242
|
this.id = id;
|
|
244
|
-
|
|
243
|
+
const selfLink = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE);
|
|
244
|
+
selfLink.pathname = `/v1/b/${this.id}`;
|
|
245
|
+
this.selfLink = selfLink.toString();
|
|
245
246
|
this.timeCreated = toSerializedDate(new Date());
|
|
246
247
|
this.updated = this.timeCreated;
|
|
247
248
|
this.projectNumber = "000000000000";
|
|
@@ -269,7 +270,6 @@ class CloudStorageObjectAccessControlMetadata {
|
|
|
269
270
|
exports.CloudStorageObjectAccessControlMetadata = CloudStorageObjectAccessControlMetadata;
|
|
270
271
|
class CloudStorageObjectMetadata {
|
|
271
272
|
constructor(metadata) {
|
|
272
|
-
var _a, _b, _c, _d;
|
|
273
273
|
this.kind = "storage#object";
|
|
274
274
|
this.name = metadata.name;
|
|
275
275
|
this.bucket = metadata.bucket;
|
|
@@ -311,8 +311,14 @@ class CloudStorageObjectMetadata {
|
|
|
311
311
|
this.crc32c = (0, crc_1.crc32cToString)(metadata.crc32c);
|
|
312
312
|
this.timeStorageClassUpdated = toSerializedDate(metadata.timeCreated);
|
|
313
313
|
this.id = `${metadata.bucket}/${metadata.name}/${metadata.generation}`;
|
|
314
|
-
|
|
315
|
-
|
|
314
|
+
const selfLink = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE);
|
|
315
|
+
selfLink.pathname = `/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}`;
|
|
316
|
+
this.selfLink = selfLink.toString();
|
|
317
|
+
const mediaLink = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE);
|
|
318
|
+
mediaLink.pathname = `/download/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}`;
|
|
319
|
+
mediaLink.searchParams.set("generation", metadata.generation.toString());
|
|
320
|
+
mediaLink.searchParams.set("alt", "media");
|
|
321
|
+
this.mediaLink = mediaLink.toString();
|
|
316
322
|
}
|
|
317
323
|
}
|
|
318
324
|
exports.CloudStorageObjectMetadata = CloudStorageObjectMetadata;
|
|
@@ -15,7 +15,6 @@ const download_1 = require("../../download");
|
|
|
15
15
|
const fs = require("fs-extra");
|
|
16
16
|
const downloadableEmulators_1 = require("../../downloadableEmulators");
|
|
17
17
|
const registry_1 = require("../../registry");
|
|
18
|
-
const apiv2_1 = require("../../../apiv2");
|
|
19
18
|
const lock = new AsyncLock();
|
|
20
19
|
const synchonizationKey = "key";
|
|
21
20
|
class StorageRulesetInstance {
|
|
@@ -301,12 +300,8 @@ function toExpressionValue(obj) {
|
|
|
301
300
|
throw new error_1.FirebaseError(`Cannot convert "${obj}" of type ${typeof obj} for Firebase Storage rules runtime`);
|
|
302
301
|
}
|
|
303
302
|
async function fetchFirestoreDocument(projectId, request) {
|
|
304
|
-
const url = registry_1.EmulatorRegistry.url(types_2.Emulators.FIRESTORE);
|
|
305
303
|
const pathname = `projects/${projectId}${request.context.path}`;
|
|
306
|
-
const client =
|
|
307
|
-
urlPrefix: url.toString(),
|
|
308
|
-
apiVersion: "v1",
|
|
309
|
-
});
|
|
304
|
+
const client = registry_1.EmulatorRegistry.client(types_2.Emulators.FIRESTORE, { apiVersion: "v1", auth: true });
|
|
310
305
|
try {
|
|
311
306
|
const doc = await client.get(pathname);
|
|
312
307
|
const { name, fields } = doc.body;
|
package/lib/emulator/ui.js
CHANGED
|
@@ -7,6 +7,7 @@ const registry_1 = require("./registry");
|
|
|
7
7
|
const error_1 = require("../error");
|
|
8
8
|
const constants_1 = require("./constants");
|
|
9
9
|
const track_1 = require("../track");
|
|
10
|
+
const ExpressBasedEmulator_1 = require("./ExpressBasedEmulator");
|
|
10
11
|
class EmulatorUI {
|
|
11
12
|
constructor(args) {
|
|
12
13
|
this.args = args;
|
|
@@ -15,13 +16,11 @@ class EmulatorUI {
|
|
|
15
16
|
if (!registry_1.EmulatorRegistry.isRunning(types_1.Emulators.HUB)) {
|
|
16
17
|
throw new error_1.FirebaseError(`Cannot start ${constants_1.Constants.description(types_1.Emulators.UI)} without ${constants_1.Constants.description(types_1.Emulators.HUB)}!`);
|
|
17
18
|
}
|
|
18
|
-
const
|
|
19
|
-
const { auto_download: autoDownload, host, port, projectId } = this.args;
|
|
19
|
+
const { auto_download: autoDownload, projectId } = this.args;
|
|
20
20
|
const env = {
|
|
21
|
-
|
|
22
|
-
PORT: port.toString(),
|
|
21
|
+
LISTEN: JSON.stringify(ExpressBasedEmulator_1.ExpressBasedEmulator.listenOptionsFromSpecs(this.args.listen)),
|
|
23
22
|
GCLOUD_PROJECT: projectId,
|
|
24
|
-
[constants_1.Constants.FIREBASE_EMULATOR_HUB]: registry_1.EmulatorRegistry.
|
|
23
|
+
[constants_1.Constants.FIREBASE_EMULATOR_HUB]: registry_1.EmulatorRegistry.url(types_1.Emulators.HUB).host,
|
|
25
24
|
};
|
|
26
25
|
const session = (0, track_1.emulatorSession)();
|
|
27
26
|
if (session) {
|
|
@@ -38,8 +37,8 @@ class EmulatorUI {
|
|
|
38
37
|
getInfo() {
|
|
39
38
|
return {
|
|
40
39
|
name: this.getName(),
|
|
41
|
-
host: this.args.
|
|
42
|
-
port: this.args.port,
|
|
40
|
+
host: this.args.listen[0].address,
|
|
41
|
+
port: this.args.listen[0].port,
|
|
43
42
|
pid: downloadableEmulators.getPID(types_1.Emulators.UI),
|
|
44
43
|
};
|
|
45
44
|
}
|
package/lib/experiments.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.flushToDisk = exports.assertEnabled = exports.setEnabled = exports.isEnabled = exports.experimentNameAutocorrect = exports.isValidExperiment = exports.ALL_EXPERIMENTS = void 0;
|
|
3
|
+
exports.flushToDisk = exports.assertEnabled = exports.enableExperimentsFromCliEnvVariable = exports.setEnabled = exports.isEnabled = exports.experimentNameAutocorrect = exports.isValidExperiment = exports.ALL_EXPERIMENTS = void 0;
|
|
4
4
|
const colorette_1 = require("colorette");
|
|
5
5
|
const leven = require("leven");
|
|
6
6
|
const configstore_1 = require("./configstore");
|
|
@@ -112,6 +112,15 @@ function setEnabled(name, to) {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
exports.setEnabled = setEnabled;
|
|
115
|
+
function enableExperimentsFromCliEnvVariable() {
|
|
116
|
+
const experiments = process.env.FIREBASE_CLI_EXPERIMENTS || "";
|
|
117
|
+
for (const experiment of experiments.split(",")) {
|
|
118
|
+
if (isValidExperiment(experiment)) {
|
|
119
|
+
setEnabled(experiment, true);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
exports.enableExperimentsFromCliEnvVariable = enableExperimentsFromCliEnvVariable;
|
|
115
124
|
function assertEnabled(name, task) {
|
|
116
125
|
if (!isEnabled(name)) {
|
|
117
126
|
throw new error_1.FirebaseError(`Cannot ${task} because the experiment ${(0, colorette_1.bold)(name)} is not enabled. To enable ${(0, colorette_1.bold)(name)} run ${(0, colorette_1.bold)(`firebase experiments:enable ${name}`)}`);
|
|
@@ -9,11 +9,13 @@ const extensionsHelper_1 = require("./extensionsHelper");
|
|
|
9
9
|
const logger_1 = require("../logger");
|
|
10
10
|
const error_1 = require("../error");
|
|
11
11
|
const iam = require("../gcp/iam");
|
|
12
|
+
const secretsUtils_1 = require("./secretsUtils");
|
|
12
13
|
marked.setOptions({
|
|
13
14
|
renderer: new TerminalRenderer(),
|
|
14
15
|
});
|
|
16
|
+
const TASKS_ROLE = "cloudtasks.enqueuer";
|
|
17
|
+
const TASKS_API = "cloudtasks.googleapis.com";
|
|
15
18
|
async function displayExtInfo(extensionName, publisher, spec, published = false) {
|
|
16
|
-
var _a, _b;
|
|
17
19
|
const lines = [];
|
|
18
20
|
lines.push(`**Name**: ${spec.displayName}`);
|
|
19
21
|
if (publisher) {
|
|
@@ -26,13 +28,17 @@ async function displayExtInfo(extensionName, publisher, spec, published = false)
|
|
|
26
28
|
if (spec.license) {
|
|
27
29
|
lines.push(`**License**: ${spec.license}`);
|
|
28
30
|
}
|
|
29
|
-
|
|
31
|
+
if (spec.sourceUrl) {
|
|
32
|
+
lines.push(`**Source code**: ${spec.sourceUrl}`);
|
|
33
|
+
}
|
|
30
34
|
}
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
const apis = impliedApis(spec);
|
|
36
|
+
if (apis.length) {
|
|
37
|
+
lines.push(displayApis(apis));
|
|
33
38
|
}
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
const roles = impliedRoles(spec);
|
|
40
|
+
if (roles.length) {
|
|
41
|
+
lines.push(await displayRoles(roles));
|
|
36
42
|
}
|
|
37
43
|
if (lines.length > 0) {
|
|
38
44
|
utils.logLabeledBullet(extensionsHelper_1.logPrefix, `information about '${clc.bold(extensionName)}':`);
|
|
@@ -73,3 +79,34 @@ function displayApis(apis) {
|
|
|
73
79
|
});
|
|
74
80
|
return "**APIs used by this Extension**:\n" + lines.join("\n");
|
|
75
81
|
}
|
|
82
|
+
function usesTasks(spec) {
|
|
83
|
+
return spec.resources.some((r) => { var _a; return ((_a = r.properties) === null || _a === void 0 ? void 0 : _a.taskQueueTrigger) !== undefined; });
|
|
84
|
+
}
|
|
85
|
+
function impliedRoles(spec) {
|
|
86
|
+
var _a, _b, _c;
|
|
87
|
+
const roles = [];
|
|
88
|
+
if ((0, secretsUtils_1.usesSecrets)(spec) && !((_a = spec.roles) === null || _a === void 0 ? void 0 : _a.some((r) => r.role === secretsUtils_1.SECRET_ROLE))) {
|
|
89
|
+
roles.push({
|
|
90
|
+
role: secretsUtils_1.SECRET_ROLE,
|
|
91
|
+
reason: "Allows the extension to read secret values from Cloud Secret Manager",
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
if (usesTasks(spec) && !((_b = spec.roles) === null || _b === void 0 ? void 0 : _b.some((r) => r.role === TASKS_ROLE))) {
|
|
95
|
+
roles.push({
|
|
96
|
+
role: TASKS_ROLE,
|
|
97
|
+
reason: "Allows the extension to enqueue Cloud Tasks",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return roles.concat((_c = spec.roles) !== null && _c !== void 0 ? _c : []);
|
|
101
|
+
}
|
|
102
|
+
function impliedApis(spec) {
|
|
103
|
+
var _a, _b;
|
|
104
|
+
const apis = [];
|
|
105
|
+
if (usesTasks(spec) && !((_a = spec.apis) === null || _a === void 0 ? void 0 : _a.some((a) => a.apiName === TASKS_API))) {
|
|
106
|
+
apis.push({
|
|
107
|
+
apiName: TASKS_API,
|
|
108
|
+
reason: "Allows the extension to enqueue Cloud Tasks",
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return apis.concat((_b = spec.apis) !== null && _b !== void 0 ? _b : []);
|
|
112
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.prettySecretName = exports.getSecretLabels = exports.getActiveSecrets = exports.getManagedSecrets = exports.grantFirexServiceAgentSecretAdminRole = exports.usesSecrets = exports.ensureSecretManagerApiEnabled = exports.SECRET_LABEL = void 0;
|
|
3
|
+
exports.prettySecretName = exports.getSecretLabels = exports.getActiveSecrets = exports.getManagedSecrets = exports.grantFirexServiceAgentSecretAdminRole = exports.usesSecrets = exports.ensureSecretManagerApiEnabled = exports.SECRET_ROLE = exports.SECRET_LABEL = void 0;
|
|
4
4
|
const getProjectNumber_1 = require("../getProjectNumber");
|
|
5
5
|
const utils = require("../utils");
|
|
6
6
|
const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
@@ -9,6 +9,7 @@ const types_1 = require("./types");
|
|
|
9
9
|
const secretManagerApi = require("../gcp/secretManager");
|
|
10
10
|
const logger_1 = require("../logger");
|
|
11
11
|
exports.SECRET_LABEL = "firebase-extensions-managed";
|
|
12
|
+
exports.SECRET_ROLE = "secretmanager.secretAccessor";
|
|
12
13
|
async function ensureSecretManagerApiEnabled(options) {
|
|
13
14
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
14
15
|
return await (0, ensureApiEnabled_1.ensure)(projectId, "secretmanager.googleapis.com", "extensions", options.markdown);
|