firebase-tools 15.22.3 → 15.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/accountExporter.js +1 -0
- package/lib/accountImporter.js +1 -0
- package/lib/apiv2.js +91 -32
- package/lib/apphosting/backend.js +11 -4
- package/lib/apphosting/localbuilds.js +50 -0
- package/lib/commands/functions-delete.js +5 -3
- package/lib/commands/functions-lifecycle-list.js +94 -0
- package/lib/commands/functions-lifecycle-run.js +29 -0
- package/lib/commands/index.js +3 -0
- package/lib/database/import.js +3 -3
- package/lib/dataconnect/webhook.js +1 -2
- package/lib/deploy/apphosting/prepare.js +100 -1
- package/lib/deploy/functions/backend.js +15 -1
- package/lib/deploy/functions/build.js +28 -0
- package/lib/deploy/functions/prepare.js +91 -2
- package/lib/deploy/functions/prompts.js +62 -0
- package/lib/deploy/functions/release/fabricator.js +79 -4
- package/lib/deploy/functions/release/index.js +42 -23
- package/lib/deploy/functions/release/lifecycle.js +113 -0
- package/lib/deploy/functions/release/planner.js +41 -5
- package/lib/deploy/functions/runtimes/discovery/index.js +4 -3
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +50 -0
- package/lib/deploy/functions/runtimes/node/index.js +1 -2
- package/lib/deploy/functions/runtimes/python/index.js +1 -2
- package/lib/deploy/functions/validate.js +44 -1
- package/lib/deploy/hosting/uploader.js +2 -3
- package/lib/downloadUtils.js +3 -1
- package/lib/emulator/auth/operations.js +18 -8
- package/lib/emulator/downloadableEmulatorInfo.json +31 -31
- package/lib/emulator/storage/apis/gcloud.js +61 -0
- package/lib/emulator/storage/multipart.js +82 -2
- package/lib/emulator/taskQueue.js +3 -11
- package/lib/extensions/extensionsHelper.js +6 -4
- package/lib/firestore/api.js +3 -1
- package/lib/gcp/cloudtasks.js +4 -0
- package/lib/gcp/iam.js +68 -2
- package/lib/gcp/knownRoles.json +99 -0
- package/lib/gcp/resourceManager.js +42 -1
- package/lib/gemini/fdcExperience.js +2 -2
- package/lib/hosting/initMiddleware.js +1 -1
- package/lib/hosting/proxy.js +42 -20
- package/lib/management/apps.js +4 -2
- package/lib/profiler.js +1 -2
- package/lib/streamUtils.js +24 -0
- package/lib/track.js +1 -2
- package/lib/tsconfig.compile.tsbuildinfo +1 -1
- package/lib/tsconfig.publish.tsbuildinfo +1 -1
- package/lib/utils.js +4 -21
- package/package.json +2 -4
package/lib/accountExporter.js
CHANGED
package/lib/accountImporter.js
CHANGED
package/lib/apiv2.js
CHANGED
|
@@ -7,10 +7,8 @@ exports.getAccessToken = getAccessToken;
|
|
|
7
7
|
exports.noKeepAliveAgent = noKeepAliveAgent;
|
|
8
8
|
const url_1 = require("url");
|
|
9
9
|
const stream_1 = require("stream");
|
|
10
|
-
const
|
|
10
|
+
const undici_1 = require("undici");
|
|
11
11
|
const retry = require("retry");
|
|
12
|
-
const abort_controller_1 = require("abort-controller");
|
|
13
|
-
const node_fetch_1 = require("node-fetch");
|
|
14
12
|
const http = require("http");
|
|
15
13
|
const https = require("https");
|
|
16
14
|
const util_1 = require("util");
|
|
@@ -19,6 +17,7 @@ const error_1 = require("./error");
|
|
|
19
17
|
const env_1 = require("./env");
|
|
20
18
|
const logger_1 = require("./logger");
|
|
21
19
|
const responseToError_1 = require("./responseToError");
|
|
20
|
+
const streamUtils_1 = require("./streamUtils");
|
|
22
21
|
const FormData = require("form-data");
|
|
23
22
|
const pkg = require("../package.json");
|
|
24
23
|
const CLI_VERSION = pkg.version;
|
|
@@ -36,7 +35,6 @@ const standardHeaders = () => {
|
|
|
36
35
|
exports.standardHeaders = standardHeaders;
|
|
37
36
|
const GOOG_QUOTA_USER_HEADER = "x-goog-quota-user";
|
|
38
37
|
exports.GOOG_USER_PROJECT_HEADER = "x-goog-user-project";
|
|
39
|
-
const GOOGLE_CLOUD_QUOTA_PROJECT = process.env.GOOGLE_CLOUD_QUOTA_PROJECT;
|
|
40
38
|
exports.CLI_OAUTH_PROJECT_NUMBER = "563584335869";
|
|
41
39
|
let accessToken = "";
|
|
42
40
|
let refreshToken = "";
|
|
@@ -56,11 +54,15 @@ async function getAccessToken() {
|
|
|
56
54
|
return data.access_token;
|
|
57
55
|
}
|
|
58
56
|
function proxyURIFromEnv() {
|
|
59
|
-
|
|
57
|
+
const uri = process.env.HTTPS_PROXY ||
|
|
60
58
|
process.env.https_proxy ||
|
|
61
59
|
process.env.HTTP_PROXY ||
|
|
62
60
|
process.env.http_proxy ||
|
|
63
|
-
undefined
|
|
61
|
+
undefined;
|
|
62
|
+
if (uri === "undefined" || uri === "null") {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
return uri;
|
|
64
66
|
}
|
|
65
67
|
const httpAgentNoKeepAlive = new http.Agent({ keepAlive: false });
|
|
66
68
|
const httpsAgentNoKeepAlive = new https.Agent({ keepAlive: false });
|
|
@@ -147,7 +149,7 @@ class Client {
|
|
|
147
149
|
throw new error_1.FirebaseError("apiv2 will not handle HTTP errors while streaming and you must set `resolveOnHTTPError` and check for res.status >= 400 on your own", { exit: 2 });
|
|
148
150
|
}
|
|
149
151
|
let internalReqOptions = Object.assign(reqOptions, {
|
|
150
|
-
headers: new
|
|
152
|
+
headers: new Headers(reqOptions.headers),
|
|
151
153
|
});
|
|
152
154
|
internalReqOptions = this.addRequestHeaders(internalReqOptions);
|
|
153
155
|
if (this.opts.auth) {
|
|
@@ -176,7 +178,7 @@ class Client {
|
|
|
176
178
|
}
|
|
177
179
|
addRequestHeaders(reqOptions) {
|
|
178
180
|
if (!reqOptions.headers) {
|
|
179
|
-
reqOptions.headers = new
|
|
181
|
+
reqOptions.headers = new Headers();
|
|
180
182
|
}
|
|
181
183
|
for (const [h, v] of Object.entries((0, exports.standardHeaders)())) {
|
|
182
184
|
if (!reqOptions.headers.has(h)) {
|
|
@@ -189,15 +191,15 @@ class Client {
|
|
|
189
191
|
}
|
|
190
192
|
}
|
|
191
193
|
if (!reqOptions.ignoreQuotaProject &&
|
|
192
|
-
GOOGLE_CLOUD_QUOTA_PROJECT &&
|
|
193
|
-
GOOGLE_CLOUD_QUOTA_PROJECT !== "") {
|
|
194
|
-
reqOptions.headers.set(exports.GOOG_USER_PROJECT_HEADER, GOOGLE_CLOUD_QUOTA_PROJECT);
|
|
194
|
+
process.env.GOOGLE_CLOUD_QUOTA_PROJECT &&
|
|
195
|
+
process.env.GOOGLE_CLOUD_QUOTA_PROJECT !== "") {
|
|
196
|
+
reqOptions.headers.set(exports.GOOG_USER_PROJECT_HEADER, process.env.GOOGLE_CLOUD_QUOTA_PROJECT);
|
|
195
197
|
}
|
|
196
198
|
return reqOptions;
|
|
197
199
|
}
|
|
198
200
|
async addAuthHeader(reqOptions) {
|
|
199
201
|
if (!reqOptions.headers) {
|
|
200
|
-
reqOptions.headers = new
|
|
202
|
+
reqOptions.headers = new Headers();
|
|
201
203
|
}
|
|
202
204
|
let token;
|
|
203
205
|
if (isLocalInsecureRequest(this.opts.urlPrefix)) {
|
|
@@ -236,31 +238,21 @@ class Client {
|
|
|
236
238
|
headers: options.headers,
|
|
237
239
|
method: options.method,
|
|
238
240
|
redirect: options.redirect,
|
|
239
|
-
compress: options.compress,
|
|
240
241
|
};
|
|
241
|
-
|
|
242
|
-
|
|
242
|
+
const proxyURI = proxyURIFromEnv();
|
|
243
|
+
if (proxyURI) {
|
|
244
|
+
fetchOptions.dispatcher = new undici_1.ProxyAgent({ uri: proxyURI });
|
|
243
245
|
}
|
|
244
246
|
if (options.signal) {
|
|
245
|
-
|
|
246
|
-
signal.reason = "";
|
|
247
|
-
signal.throwIfAborted = () => {
|
|
248
|
-
throw new error_1.FirebaseError("Aborted");
|
|
249
|
-
};
|
|
250
|
-
fetchOptions.signal = signal;
|
|
247
|
+
fetchOptions.signal = options.signal;
|
|
251
248
|
}
|
|
252
249
|
let reqTimeout;
|
|
253
250
|
if (options.timeout) {
|
|
254
|
-
const controller = new
|
|
251
|
+
const controller = new AbortController();
|
|
255
252
|
reqTimeout = setTimeout(() => {
|
|
256
253
|
controller.abort();
|
|
257
254
|
}, options.timeout);
|
|
258
|
-
|
|
259
|
-
signal.reason = "";
|
|
260
|
-
signal.throwIfAborted = () => {
|
|
261
|
-
throw new error_1.FirebaseError("Aborted");
|
|
262
|
-
};
|
|
263
|
-
fetchOptions.signal = signal;
|
|
255
|
+
fetchOptions.signal = controller.signal;
|
|
264
256
|
}
|
|
265
257
|
let bodyReplayable = true;
|
|
266
258
|
if (typeof options.body === "string" || Buffer.isBuffer(options.body)) {
|
|
@@ -271,6 +263,7 @@ class Client {
|
|
|
271
263
|
}
|
|
272
264
|
else if (isStream(options.body)) {
|
|
273
265
|
fetchOptions.body = options.body;
|
|
266
|
+
fetchOptions.duplex = "half";
|
|
274
267
|
bodyReplayable = false;
|
|
275
268
|
}
|
|
276
269
|
else if (options.body !== undefined) {
|
|
@@ -302,10 +295,39 @@ class Client {
|
|
|
302
295
|
}
|
|
303
296
|
this.logRequest(options);
|
|
304
297
|
try {
|
|
305
|
-
|
|
298
|
+
if (options.compress === false) {
|
|
299
|
+
const undiciOptions = {
|
|
300
|
+
method: fetchOptions.method,
|
|
301
|
+
headers: {},
|
|
302
|
+
decompress: false,
|
|
303
|
+
};
|
|
304
|
+
if (fetchOptions.dispatcher) {
|
|
305
|
+
undiciOptions.dispatcher = fetchOptions.dispatcher;
|
|
306
|
+
}
|
|
307
|
+
if (fetchOptions.signal) {
|
|
308
|
+
undiciOptions.signal = fetchOptions.signal;
|
|
309
|
+
}
|
|
310
|
+
if (fetchOptions.body) {
|
|
311
|
+
undiciOptions.body = fetchOptions.body;
|
|
312
|
+
}
|
|
313
|
+
if (fetchOptions.headers) {
|
|
314
|
+
for (const [key, value] of fetchOptions.headers.entries()) {
|
|
315
|
+
undiciOptions.headers[key] = value;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const undiciRes = await (0, undici_1.request)(fetchURL, undiciOptions);
|
|
319
|
+
res = new UndiciResponseCompat(undiciRes);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
res = await fetch(fetchURL, fetchOptions);
|
|
323
|
+
}
|
|
306
324
|
}
|
|
307
325
|
catch (thrown) {
|
|
308
|
-
const err = thrown
|
|
326
|
+
const err = thrown && typeof thrown === "object" && thrown.cause instanceof Error
|
|
327
|
+
? thrown.cause
|
|
328
|
+
: thrown instanceof Error
|
|
329
|
+
? thrown
|
|
330
|
+
: new Error(thrown);
|
|
309
331
|
logger_1.logger.debug(`*** [apiv2] error from fetch(${fetchURL}, ${JSON.stringify(fetchOptions)}): ${err}`);
|
|
310
332
|
const isAbortError = err.name.includes("AbortError");
|
|
311
333
|
if (isAbortError) {
|
|
@@ -339,7 +361,19 @@ class Client {
|
|
|
339
361
|
body = (await res.text());
|
|
340
362
|
}
|
|
341
363
|
else if (options.responseType === "stream") {
|
|
342
|
-
|
|
364
|
+
if (res.body) {
|
|
365
|
+
if (typeof res.body.getReader === "function") {
|
|
366
|
+
body = stream_1.Readable.fromWeb(res.body);
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
body = res.body;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
const emptyStream = new stream_1.Readable();
|
|
374
|
+
emptyStream.push(null);
|
|
375
|
+
body = emptyStream;
|
|
376
|
+
}
|
|
343
377
|
}
|
|
344
378
|
else {
|
|
345
379
|
throw new error_1.FirebaseError(`Unable to interpret response. Please set responseType.`, {
|
|
@@ -354,7 +388,7 @@ class Client {
|
|
|
354
388
|
isPrematureCloseError(err)) {
|
|
355
389
|
disabledKeepAlive = true;
|
|
356
390
|
fetchOptions.agent = noKeepAliveAgent;
|
|
357
|
-
const closeHeaders = new
|
|
391
|
+
const closeHeaders = new Headers(fetchOptions.headers);
|
|
358
392
|
closeHeaders.set("Connection", "close");
|
|
359
393
|
fetchOptions.headers = closeHeaders;
|
|
360
394
|
logger_1.logger.debug(`*** [apiv2] retrying ${fetchURL} without keep-alive after a premature close error`);
|
|
@@ -435,6 +469,31 @@ function isLocalInsecureRequest(urlPrefix) {
|
|
|
435
469
|
const u = new url_1.URL(urlPrefix);
|
|
436
470
|
return u.protocol === "http:";
|
|
437
471
|
}
|
|
472
|
+
class UndiciResponseCompat {
|
|
473
|
+
constructor(undiciRes) {
|
|
474
|
+
this.undiciRes = undiciRes;
|
|
475
|
+
this.status = undiciRes.statusCode;
|
|
476
|
+
this.ok = undiciRes.statusCode >= 200 && undiciRes.statusCode < 300;
|
|
477
|
+
this.headers = new Headers();
|
|
478
|
+
for (const [key, value] of Object.entries(undiciRes.headers)) {
|
|
479
|
+
if (Array.isArray(value)) {
|
|
480
|
+
for (const v of value) {
|
|
481
|
+
this.headers.append(key, v);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
else if (value !== undefined) {
|
|
485
|
+
this.headers.set(key, String(value));
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
this.body = undiciRes.body;
|
|
489
|
+
}
|
|
490
|
+
async text() {
|
|
491
|
+
if (!this.body) {
|
|
492
|
+
return "";
|
|
493
|
+
}
|
|
494
|
+
return (0, streamUtils_1.streamToString)(this.body);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
438
497
|
function bodyToString(body) {
|
|
439
498
|
if (isStream(body)) {
|
|
440
499
|
return "[stream]";
|
|
@@ -31,7 +31,6 @@ const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
|
31
31
|
const deploymentTool = require("../deploymentTool");
|
|
32
32
|
const app_1 = require("./app");
|
|
33
33
|
const ora = require("ora");
|
|
34
|
-
const node_fetch_1 = require("node-fetch");
|
|
35
34
|
const rollout_1 = require("./rollout");
|
|
36
35
|
const fuzzy = require("fuzzy");
|
|
37
36
|
const experiments_1 = require("../experiments");
|
|
@@ -45,7 +44,7 @@ const apphostingPollerOptions = {
|
|
|
45
44
|
};
|
|
46
45
|
async function tlsReady(url) {
|
|
47
46
|
try {
|
|
48
|
-
await (
|
|
47
|
+
await fetch(url);
|
|
49
48
|
return true;
|
|
50
49
|
}
|
|
51
50
|
catch (err) {
|
|
@@ -213,8 +212,16 @@ async function promptNewBackendId(projectId, location) {
|
|
|
213
212
|
while (true) {
|
|
214
213
|
const backendId = await (0, prompt_1.input)({
|
|
215
214
|
default: "my-web-app",
|
|
216
|
-
message: "Provide a name for your backend [
|
|
217
|
-
validate: (s) =>
|
|
215
|
+
message: "Provide a name for your backend [3-30 characters]",
|
|
216
|
+
validate: (s) => {
|
|
217
|
+
if (!/^[a-z](?:[a-z0-9-]*[a-z0-9])?$/.test(s)) {
|
|
218
|
+
return "Must begin with a letter, can contain only lowercase, digits, hyphens, and cannot end with hyphen";
|
|
219
|
+
}
|
|
220
|
+
else if (s.length < 3 || s.length > 30) {
|
|
221
|
+
return "Must be between 3 and 30 characters";
|
|
222
|
+
}
|
|
223
|
+
return true;
|
|
224
|
+
},
|
|
218
225
|
});
|
|
219
226
|
try {
|
|
220
227
|
await apphosting.getBackend(projectId, location, backendId);
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runUniversalMaker = runUniversalMaker;
|
|
4
4
|
exports.localBuild = localBuild;
|
|
5
|
+
exports.validateLocalBuildNodeVersion = validateLocalBuildNodeVersion;
|
|
5
6
|
const childProcess = require("child_process");
|
|
6
7
|
const fs = require("fs-extra");
|
|
7
8
|
const path = require("path");
|
|
9
|
+
const semver = require("semver");
|
|
8
10
|
const index_1 = require("./secrets/index");
|
|
9
11
|
const prompt_1 = require("../prompt");
|
|
10
12
|
const error_1 = require("../error");
|
|
@@ -138,3 +140,51 @@ async function toProcessEnv(projectId, env) {
|
|
|
138
140
|
}));
|
|
139
141
|
return Object.fromEntries(resolvedEntries);
|
|
140
142
|
}
|
|
143
|
+
function validateLocalBuildNodeVersion(backend, projectRoot) {
|
|
144
|
+
const runtimeValue = backend.runtime?.value ?? "";
|
|
145
|
+
const isLegacyRuntime = runtimeValue === "" || runtimeValue === "nodejs";
|
|
146
|
+
const abiuEnabled = !isLegacyRuntime && !backend.automaticBaseImageUpdatesDisabled;
|
|
147
|
+
if (!abiuEnabled) {
|
|
148
|
+
throw new error_1.FirebaseError(`Local builds are only supported for backends with ABIU (Automatic Base Image Updates) enabled. ` +
|
|
149
|
+
`Your backend is currently configured with a non-ABIU runtime ("${runtimeValue || "unspecified"}"). ` +
|
|
150
|
+
`Please update your backend to a versioned runtime (e.g., nodejs22) to enable local builds.`, { exit: 1 });
|
|
151
|
+
}
|
|
152
|
+
const targetMajorMatch = runtimeValue.match(/^nodejs(\d+)$/);
|
|
153
|
+
if (!targetMajorMatch) {
|
|
154
|
+
(0, utils_1.logLabeledWarning)("apphosting", `Unable to extract Node.js major version from the backend runtime ("${runtimeValue}"). ` +
|
|
155
|
+
`Skipping local Node.js version compatibility checks.`);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const targetMajor = parseInt(targetMajorMatch[1], 10);
|
|
159
|
+
let localNodeVersion;
|
|
160
|
+
try {
|
|
161
|
+
localNodeVersion = childProcess.execSync("node -v", { encoding: "utf8" }).trim();
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
(0, utils_1.logLabeledWarning)("apphosting", `Unable to detect your local Node.js version (is 'node' installed and in your PATH?). ` +
|
|
165
|
+
`Skipping local Node.js version compatibility checks.`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const packageJsonPath = path.join(projectRoot, "package.json");
|
|
169
|
+
const packageJson = fs.readJsonSync(packageJsonPath, { throws: false });
|
|
170
|
+
const enginesNode = packageJson?.engines?.node;
|
|
171
|
+
if (enginesNode) {
|
|
172
|
+
(0, utils_1.logLabeledWarning)("apphosting", `Your package.json specifies Node.js engine "${enginesNode}". ` +
|
|
173
|
+
`Please note that local builds do NOT use the "engines" field to resolve or download Node.js. ` +
|
|
174
|
+
`Instead, your local build uses your host machine's active Node.js version (${localNodeVersion}) to compile the app, ` +
|
|
175
|
+
`and your deployed app will run on the backend's configured ABIU runtime (${runtimeValue}).`);
|
|
176
|
+
const targetRange = `^${targetMajor}.0.0`;
|
|
177
|
+
if (semver.validRange(enginesNode) && !semver.intersects(targetRange, enginesNode)) {
|
|
178
|
+
(0, utils_1.logLabeledWarning)("apphosting", `The Node.js version range specified in your package.json engines ("${enginesNode}") ` +
|
|
179
|
+
`does not satisfy your backend's target ABIU runtime version (Node.js ${targetMajor}). ` +
|
|
180
|
+
`Please update your package.json engines to align with your backend configuration.`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const localMajorMatch = localNodeVersion.match(/^v?(\d+)/);
|
|
184
|
+
const localMajor = localMajorMatch ? parseInt(localMajorMatch[1], 10) : null;
|
|
185
|
+
if (localMajor !== null && localMajor !== targetMajor) {
|
|
186
|
+
(0, utils_1.logLabeledWarning)("apphosting", `Local Node.js version (${localNodeVersion}) does not match your backend's target Node.js version (Node.js ${targetMajor}). ` +
|
|
187
|
+
`This mismatch may cause runtime issues. ` +
|
|
188
|
+
`Please switch your local environment to Node.js ${targetMajor} to ensure build-to-run parity.`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -40,14 +40,15 @@ exports.command = new command_1.Command("functions:delete [filters...]")
|
|
|
40
40
|
if (options.region) {
|
|
41
41
|
existingBackend.endpoints = { [options.region]: existingBackend.endpoints[options.region] };
|
|
42
42
|
}
|
|
43
|
-
const plan = planner.createDeploymentPlan({
|
|
43
|
+
const plan = await planner.createDeploymentPlan({
|
|
44
44
|
wantBackend: backend.empty(),
|
|
45
45
|
haveBackend: existingBackend,
|
|
46
46
|
codebase: "",
|
|
47
|
+
projectId: context.projectId,
|
|
47
48
|
filters: context.filters,
|
|
48
49
|
deleteAll: true,
|
|
49
50
|
});
|
|
50
|
-
const allEpToDelete = Object.values(plan)
|
|
51
|
+
const allEpToDelete = Object.values(plan.regionalChangesets)
|
|
51
52
|
.map((changes) => changes.endpointsToDelete)
|
|
52
53
|
.reduce(functional_1.reduceFlat, [])
|
|
53
54
|
.sort(backend.compareFunctions);
|
|
@@ -80,8 +81,9 @@ exports.command = new command_1.Command("functions:delete [filters...]")
|
|
|
80
81
|
executor: new executor.QueueExecutor({}),
|
|
81
82
|
sources: {},
|
|
82
83
|
projectNumber: options.projectNumber || (await (0, getProjectNumber_1.getProjectNumber)({ projectId: context.projectId })),
|
|
84
|
+
projectId: context.projectId,
|
|
83
85
|
});
|
|
84
|
-
const summary = await fab.applyPlan(plan);
|
|
86
|
+
const summary = await fab.applyPlan({ default: plan });
|
|
85
87
|
await reporter.logAndTrackDeployStats(summary);
|
|
86
88
|
reporter.printErrors(summary);
|
|
87
89
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
exports.loadCodebaseBuild = loadCodebaseBuild;
|
|
5
|
+
const command_1 = require("../command");
|
|
6
|
+
const logger_1 = require("../logger");
|
|
7
|
+
const prepare_1 = require("../deploy/functions/prepare");
|
|
8
|
+
const projectConfig_1 = require("../functions/projectConfig");
|
|
9
|
+
const adminSdkConfig_1 = require("../emulator/adminSdkConfig");
|
|
10
|
+
const projectUtils_1 = require("../projectUtils");
|
|
11
|
+
const error_1 = require("../error");
|
|
12
|
+
const ensureApiEnabled = require("../ensureApiEnabled");
|
|
13
|
+
const api_1 = require("../api");
|
|
14
|
+
const prepareFunctionsUpload_1 = require("../deploy/functions/prepareFunctionsUpload");
|
|
15
|
+
const self = require("./functions-lifecycle-list");
|
|
16
|
+
async function loadCodebaseBuild(codebase, options) {
|
|
17
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
18
|
+
if (!options.config) {
|
|
19
|
+
throw new error_1.FirebaseError("Not in a Firebase project directory (firebase.json not found).");
|
|
20
|
+
}
|
|
21
|
+
const fnConfig = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
|
|
22
|
+
const hasCodebase = fnConfig.some((c) => c.codebase === codebase);
|
|
23
|
+
if (!hasCodebase) {
|
|
24
|
+
throw new error_1.FirebaseError(`Codebase "${codebase}" is not defined in firebase.json.`);
|
|
25
|
+
}
|
|
26
|
+
const firebaseConfig = await (0, adminSdkConfig_1.getProjectAdminSdkConfigOrCached)(projectId);
|
|
27
|
+
if (!firebaseConfig) {
|
|
28
|
+
throw new error_1.FirebaseError("Admin SDK config unexpectedly undefined - have you run firebase init?");
|
|
29
|
+
}
|
|
30
|
+
let runtimeConfig = { firebase: firebaseConfig };
|
|
31
|
+
if (fnConfig.some(projectConfig_1.shouldUseRuntimeConfig)) {
|
|
32
|
+
try {
|
|
33
|
+
const runtimeConfigApiEnabled = await ensureApiEnabled.check(projectId, (0, api_1.runtimeconfigOrigin)(), "runtimeconfig", true);
|
|
34
|
+
if (runtimeConfigApiEnabled) {
|
|
35
|
+
runtimeConfig = { ...runtimeConfig, ...(await (0, prepareFunctionsUpload_1.getFunctionsConfig)(projectId)) };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
logger_1.logger.debug("Could not check Runtime Config API status, assuming disabled:", err);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const wantBuilds = await (0, prepare_1.loadCodebases)(fnConfig, options, firebaseConfig, runtimeConfig, undefined);
|
|
43
|
+
const codebaseBuild = wantBuilds[codebase];
|
|
44
|
+
if (!codebaseBuild) {
|
|
45
|
+
throw new error_1.FirebaseError(`Failed to load build for codebase "${codebase}".`);
|
|
46
|
+
}
|
|
47
|
+
return codebaseBuild;
|
|
48
|
+
}
|
|
49
|
+
exports.command = new command_1.Command("functions:lifecycle:list <codebase>")
|
|
50
|
+
.description("list all the lifecycle hooks defined in a codebase")
|
|
51
|
+
.action(async (codebase, options) => {
|
|
52
|
+
const codebaseBuild = await self.loadCodebaseBuild(codebase, options);
|
|
53
|
+
const hooks = codebaseBuild.lifecycleHooks || {};
|
|
54
|
+
if (Object.keys(hooks).length === 0) {
|
|
55
|
+
logger_1.logger.info(`No lifecycle hooks configured for codebase "${codebase}".`);
|
|
56
|
+
return hooks;
|
|
57
|
+
}
|
|
58
|
+
for (const [event, hook] of Object.entries(hooks)) {
|
|
59
|
+
logger_1.logger.info(`\nEvent: ${event}`);
|
|
60
|
+
if ("task" in hook) {
|
|
61
|
+
logger_1.logger.info(` Action: Task`);
|
|
62
|
+
logger_1.logger.info(` Target Function: ${hook.task.function}`);
|
|
63
|
+
if (hook.task.body) {
|
|
64
|
+
logger_1.logger.info(` Body: ${JSON.stringify(hook.task.body, null, 2).replace(/\n/g, "\n ")}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else if ("call" in hook) {
|
|
68
|
+
logger_1.logger.info(` Action: Call`);
|
|
69
|
+
logger_1.logger.info(` Target Function: ${hook.call.function}`);
|
|
70
|
+
if (hook.call.params) {
|
|
71
|
+
logger_1.logger.info(` Params: ${JSON.stringify(hook.call.params, null, 2).replace(/\n/g, "\n ")}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else if ("http" in hook) {
|
|
75
|
+
logger_1.logger.info(` Action: HTTP`);
|
|
76
|
+
if (hook.http.function) {
|
|
77
|
+
logger_1.logger.info(` Target Function: ${hook.http.function}`);
|
|
78
|
+
}
|
|
79
|
+
if (hook.http.url) {
|
|
80
|
+
logger_1.logger.info(` URL: ${hook.http.url}`);
|
|
81
|
+
}
|
|
82
|
+
if (hook.http.method) {
|
|
83
|
+
logger_1.logger.info(` Method: ${hook.http.method}`);
|
|
84
|
+
}
|
|
85
|
+
if (hook.http.headers) {
|
|
86
|
+
logger_1.logger.info(` Headers: ${JSON.stringify(hook.http.headers, null, 2).replace(/\n/g, "\n ")}`);
|
|
87
|
+
}
|
|
88
|
+
if (hook.http.body) {
|
|
89
|
+
logger_1.logger.info(` Body: ${JSON.stringify(hook.http.body, null, 2).replace(/\n/g, "\n ")}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return hooks;
|
|
94
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const command_1 = require("../command");
|
|
5
|
+
const projectUtils_1 = require("../projectUtils");
|
|
6
|
+
const requirePermissions_1 = require("../requirePermissions");
|
|
7
|
+
const error_1 = require("../error");
|
|
8
|
+
const functions_lifecycle_list_1 = require("./functions-lifecycle-list");
|
|
9
|
+
const backend = require("../deploy/functions/backend");
|
|
10
|
+
const lifecycle_1 = require("../deploy/functions/release/lifecycle");
|
|
11
|
+
exports.command = new command_1.Command("functions:lifecycle:run <hookName> <codebase>")
|
|
12
|
+
.description("run a specific lifecycle hook in isolation")
|
|
13
|
+
.before(requirePermissions_1.requirePermissions, ["cloudfunctions.functions.list", "run.services.list"])
|
|
14
|
+
.action(async (hookName, codebase, options) => {
|
|
15
|
+
if (hookName !== "afterFirstDeploy" && hookName !== "afterRedeploy") {
|
|
16
|
+
throw new error_1.FirebaseError(`Invalid hook name "${hookName}". Supported hooks are "afterFirstDeploy" and "afterRedeploy".`);
|
|
17
|
+
}
|
|
18
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
19
|
+
const codebaseBuild = await (0, functions_lifecycle_list_1.loadCodebaseBuild)(codebase, options);
|
|
20
|
+
const hook = codebaseBuild.lifecycleHooks?.[hookName];
|
|
21
|
+
if (!hook) {
|
|
22
|
+
throw new error_1.FirebaseError(`No lifecycle hook "${hookName}" configured for codebase "${codebase}".`);
|
|
23
|
+
}
|
|
24
|
+
const context = {
|
|
25
|
+
projectId,
|
|
26
|
+
};
|
|
27
|
+
const existingBackend = await backend.existingBackend(context);
|
|
28
|
+
await (0, lifecycle_1.executeHook)(hookName, hook, existingBackend);
|
|
29
|
+
});
|
package/lib/commands/index.js
CHANGED
|
@@ -151,6 +151,9 @@ function load(client) {
|
|
|
151
151
|
client.functions.log = loadCommand("functions-log");
|
|
152
152
|
client.functions.shell = loadCommand("functions-shell");
|
|
153
153
|
client.functions.list = loadCommand("functions-list");
|
|
154
|
+
client.functions.lifecycle = {};
|
|
155
|
+
client.functions.lifecycle.list = loadCommand("functions-lifecycle-list");
|
|
156
|
+
client.functions.lifecycle.run = loadCommand("functions-lifecycle-run");
|
|
154
157
|
if (experiments.isEnabled("deletegcfartifacts")) {
|
|
155
158
|
client.functions.deletegcfartifacts = loadCommand("functions-deletegcfartifacts");
|
|
156
159
|
}
|
package/lib/database/import.js
CHANGED
|
@@ -6,7 +6,6 @@ const Filter = require("stream-json/filters/Filter");
|
|
|
6
6
|
const stream = require("stream");
|
|
7
7
|
const StreamObject = require("stream-json/streamers/StreamObject");
|
|
8
8
|
const apiv2_1 = require("../apiv2");
|
|
9
|
-
const node_fetch_1 = require("node-fetch");
|
|
10
9
|
const error_1 = require("../error");
|
|
11
10
|
const utils_1 = require("../utils");
|
|
12
11
|
class BatchChunks extends stream.Transform {
|
|
@@ -169,8 +168,9 @@ class DatabaseImporter {
|
|
|
169
168
|
}
|
|
170
169
|
catch (err) {
|
|
171
170
|
const isTimeoutErr = err instanceof error_1.FirebaseError &&
|
|
172
|
-
err.original
|
|
173
|
-
|
|
171
|
+
(err.original?.name === "AbortError" ||
|
|
172
|
+
err.original?.code === "ETIMEDOUT" ||
|
|
173
|
+
err.original?.cause?.code === "ETIMEDOUT");
|
|
174
174
|
if (isTimeoutErr) {
|
|
175
175
|
await new Promise((res) => setTimeout(res, this.nonFatalRetryTimeout));
|
|
176
176
|
return await doRequest();
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.port = exports.DEFAULT_PORT = exports.VSCODE_MESSAGE = void 0;
|
|
4
4
|
exports.sendVSCodeMessage = sendVSCodeMessage;
|
|
5
|
-
const node_fetch_1 = require("node-fetch");
|
|
6
5
|
const logger_1 = require("../logger");
|
|
7
6
|
var VSCODE_MESSAGE;
|
|
8
7
|
(function (VSCODE_MESSAGE) {
|
|
@@ -15,7 +14,7 @@ exports.port = process.env.VSCODE_WEBHOOK_PORT || exports.DEFAULT_PORT;
|
|
|
15
14
|
async function sendVSCodeMessage(body) {
|
|
16
15
|
const jsonBody = JSON.stringify(body);
|
|
17
16
|
try {
|
|
18
|
-
return await (
|
|
17
|
+
return await fetch(`http://localhost:${exports.port}/vscode/notify`, {
|
|
19
18
|
method: "POST",
|
|
20
19
|
headers: {
|
|
21
20
|
Accept: "application/json",
|