firebase-tools 10.7.0 → 10.7.1
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/commands/functions-delete.js +9 -2
- package/lib/deploy/functions/backend.js +21 -1
- package/lib/deploy/functions/checkIam.js +6 -3
- package/lib/deploy/functions/deploy.js +49 -27
- package/lib/deploy/functions/functionsDeployHelper.js +48 -4
- package/lib/deploy/functions/prepare.js +87 -72
- package/lib/deploy/functions/release/fabricator.js +28 -25
- package/lib/deploy/functions/release/index.js +16 -6
- package/lib/deploy/functions/release/planner.js +12 -7
- package/lib/deploy/functions/validate.js +24 -1
- package/lib/deploy/hosting/convertConfig.js +6 -4
- package/lib/emulator/controller.js +12 -9
- package/lib/emulator/functionsEmulator.js +9 -3
- package/lib/emulator/functionsEmulatorShared.js +2 -1
- package/lib/emulator/functionsEmulatorShell.js +2 -3
- package/lib/emulator/pubsubEmulator.js +13 -9
- package/lib/ensureApiEnabled.js +1 -1
- package/lib/functions/projectConfig.js +13 -8
- package/lib/functionsShellCommandAction.js +1 -1
- package/lib/gcp/cloudfunctions.js +8 -1
- package/lib/gcp/cloudfunctionsv2.js +8 -1
- package/lib/serve/functions.js +16 -19
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -40,7 +40,13 @@ exports.default = 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 = planner.createDeploymentPlan({
|
|
44
|
+
wantBackend: backend.empty(),
|
|
45
|
+
haveBackend: existingBackend,
|
|
46
|
+
codebase: "",
|
|
47
|
+
filters: context.filters,
|
|
48
|
+
deleteAll: true,
|
|
49
|
+
});
|
|
44
50
|
const allEpToDelete = Object.values(plan)
|
|
45
51
|
.map((changes) => changes.endpointsToDelete)
|
|
46
52
|
.reduce(functional_1.reduceFlat, [])
|
|
@@ -69,8 +75,9 @@ exports.default = new command_1.Command("functions:delete [filters...]")
|
|
|
69
75
|
try {
|
|
70
76
|
const fab = new fabricator.Fabricator({
|
|
71
77
|
functionExecutor,
|
|
72
|
-
executor: new executor.QueueExecutor({}),
|
|
73
78
|
appEngineLocation,
|
|
79
|
+
executor: new executor.QueueExecutor({}),
|
|
80
|
+
sources: {},
|
|
74
81
|
});
|
|
75
82
|
const summary = await fab.applyPlan(plan);
|
|
76
83
|
await reporter.logAndTrackDeployStats(summary);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.findEndpoint = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.of = exports.empty = exports.isBlockingTriggered = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.AllFunctionsPlatforms = exports.secretVersionName = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_MEMORY_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.memoryOptionDisplayName = exports.AllMemoryOptions = exports.AllIngressSettings = exports.AllVpcEgressSettings = exports.endpointTriggerType = void 0;
|
|
3
|
+
exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.findEndpoint = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.merge = exports.of = exports.empty = exports.isBlockingTriggered = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.AllFunctionsPlatforms = exports.secretVersionName = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_MEMORY_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.memoryOptionDisplayName = exports.AllMemoryOptions = exports.AllIngressSettings = exports.AllVpcEgressSettings = exports.endpointTriggerType = void 0;
|
|
4
4
|
const gcf = require("../../gcp/cloudfunctions");
|
|
5
5
|
const gcfV2 = require("../../gcp/cloudfunctionsv2");
|
|
6
6
|
const utils = require("../../utils");
|
|
7
7
|
const error_1 = require("../../error");
|
|
8
8
|
const previews_1 = require("../../previews");
|
|
9
|
+
const functional_1 = require("../../functional");
|
|
9
10
|
function endpointTriggerType(endpoint) {
|
|
10
11
|
if (isScheduleTriggered(endpoint)) {
|
|
11
12
|
return "scheduled";
|
|
@@ -102,6 +103,25 @@ function of(...endpoints) {
|
|
|
102
103
|
return bkend;
|
|
103
104
|
}
|
|
104
105
|
exports.of = of;
|
|
106
|
+
function merge(...backends) {
|
|
107
|
+
const merged = of(...(0, functional_1.flattenArray)(backends.map((b) => allEndpoints(b))));
|
|
108
|
+
const apiToReasons = {};
|
|
109
|
+
for (const b of backends) {
|
|
110
|
+
for (const { api, reason } of b.requiredAPIs) {
|
|
111
|
+
const reasons = apiToReasons[api] || new Set();
|
|
112
|
+
if (reason) {
|
|
113
|
+
reasons.add(reason);
|
|
114
|
+
}
|
|
115
|
+
apiToReasons[api] = reasons;
|
|
116
|
+
}
|
|
117
|
+
merged.environmentVariables = Object.assign(Object.assign({}, merged.environmentVariables), b.environmentVariables);
|
|
118
|
+
}
|
|
119
|
+
for (const [api, reasons] of Object.entries(apiToReasons)) {
|
|
120
|
+
merged.requiredAPIs.push({ api, reason: Array.from(reasons).join(" ") });
|
|
121
|
+
}
|
|
122
|
+
return merged;
|
|
123
|
+
}
|
|
124
|
+
exports.merge = merge;
|
|
105
125
|
function isEmptyBackend(backend) {
|
|
106
126
|
return (Object.keys(backend.requiredAPIs).length === 0 && Object.keys(backend.endpoints).length === 0);
|
|
107
127
|
}
|
|
@@ -5,6 +5,7 @@ const cli_color_1 = require("cli-color");
|
|
|
5
5
|
const logger_1 = require("../../logger");
|
|
6
6
|
const functionsDeployHelper_1 = require("./functionsDeployHelper");
|
|
7
7
|
const error_1 = require("../../error");
|
|
8
|
+
const functional_1 = require("../../functional");
|
|
8
9
|
const iam = require("../../gcp/iam");
|
|
9
10
|
const backend = require("./backend");
|
|
10
11
|
const track_1 = require("../../track");
|
|
@@ -35,10 +36,12 @@ async function checkServiceAccountIam(projectId) {
|
|
|
35
36
|
}
|
|
36
37
|
exports.checkServiceAccountIam = checkServiceAccountIam;
|
|
37
38
|
async function checkHttpIam(context, options, payload) {
|
|
39
|
+
if (!payload.functions) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
38
42
|
const filters = context.filters || (0, functionsDeployHelper_1.getEndpointFilters)(options);
|
|
39
|
-
const
|
|
40
|
-
const httpEndpoints = backend
|
|
41
|
-
.allEndpoints(wantBackend)
|
|
43
|
+
const wantBackends = Object.values(payload.functions).map(({ wantBackend }) => wantBackend);
|
|
44
|
+
const httpEndpoints = [...(0, functional_1.flattenArray)(wantBackends.map((b) => backend.allEndpoints(b)))]
|
|
42
45
|
.filter(backend.isHttpsTriggered)
|
|
43
46
|
.filter((f) => (0, functionsDeployHelper_1.endpointMatchesAnyFilter)(f, filters));
|
|
44
47
|
const existing = await backend.existingBackend(context);
|
|
@@ -6,54 +6,62 @@ const clc = require("cli-color");
|
|
|
6
6
|
const fs = require("fs");
|
|
7
7
|
const checkIam_1 = require("./checkIam");
|
|
8
8
|
const utils_1 = require("../../utils");
|
|
9
|
+
const projectConfig_1 = require("../../functions/projectConfig");
|
|
9
10
|
const gcs = require("../../gcp/storage");
|
|
10
11
|
const gcf = require("../../gcp/cloudfunctions");
|
|
11
12
|
const gcfv2 = require("../../gcp/cloudfunctionsv2");
|
|
12
13
|
const backend = require("./backend");
|
|
13
14
|
(0, tmp_1.setGracefulCleanup)();
|
|
14
|
-
async function uploadSourceV1(
|
|
15
|
-
const
|
|
16
|
-
|
|
15
|
+
async function uploadSourceV1(projectId, source, wantBackend) {
|
|
16
|
+
const v1Endpoints = backend.allEndpoints(wantBackend).filter((e) => e.platform === "gcfv1");
|
|
17
|
+
if (v1Endpoints.length === 0) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const region = v1Endpoints[0].region;
|
|
21
|
+
const uploadUrl = await gcf.generateUploadUrl(projectId, region);
|
|
17
22
|
const uploadOpts = {
|
|
18
|
-
file:
|
|
19
|
-
stream: fs.createReadStream(
|
|
23
|
+
file: source.functionsSourceV1,
|
|
24
|
+
stream: fs.createReadStream(source.functionsSourceV1),
|
|
20
25
|
};
|
|
21
26
|
await gcs.upload(uploadOpts, uploadUrl, {
|
|
22
27
|
"x-goog-content-length-range": "0,104857600",
|
|
23
28
|
});
|
|
29
|
+
return uploadUrl;
|
|
24
30
|
}
|
|
25
|
-
async function uploadSourceV2(
|
|
26
|
-
const
|
|
31
|
+
async function uploadSourceV2(projectId, source, wantBackend) {
|
|
32
|
+
const v2Endpoints = backend.allEndpoints(wantBackend).filter((e) => e.platform === "gcfv2");
|
|
33
|
+
if (v2Endpoints.length === 0) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const region = v2Endpoints[0].region;
|
|
37
|
+
const res = await gcfv2.generateUploadUrl(projectId, region);
|
|
27
38
|
const uploadOpts = {
|
|
28
|
-
file:
|
|
29
|
-
stream: fs.createReadStream(
|
|
39
|
+
file: source.functionsSourceV2,
|
|
40
|
+
stream: fs.createReadStream(source.functionsSourceV2),
|
|
30
41
|
};
|
|
31
42
|
await gcs.upload(uploadOpts, res.uploadUrl);
|
|
32
|
-
|
|
43
|
+
return res.storageSource;
|
|
33
44
|
}
|
|
34
|
-
async function
|
|
35
|
-
var _a
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
if (!((_a = context.source) === null || _a === void 0 ? void 0 : _a.functionsSourceV1) && !((_b = context.source) === null || _b === void 0 ? void 0 : _b.functionsSourceV2)) {
|
|
45
|
+
async function uploadCodebase(context, codebase, wantBackend) {
|
|
46
|
+
var _a;
|
|
47
|
+
const source = (_a = context.sources) === null || _a === void 0 ? void 0 : _a[codebase];
|
|
48
|
+
if (!source || (!source.functionsSourceV1 && !source.functionsSourceV2)) {
|
|
40
49
|
return;
|
|
41
50
|
}
|
|
42
|
-
|
|
51
|
+
const uploads = [];
|
|
43
52
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
if (
|
|
48
|
-
|
|
53
|
+
uploads.push(uploadSourceV1(context.projectId, source, wantBackend));
|
|
54
|
+
uploads.push(uploadSourceV2(context.projectId, source, wantBackend));
|
|
55
|
+
const [sourceUrl, storage] = await Promise.all(uploads);
|
|
56
|
+
if (sourceUrl) {
|
|
57
|
+
source.sourceUrl = sourceUrl;
|
|
49
58
|
}
|
|
50
|
-
if (
|
|
51
|
-
|
|
59
|
+
if (storage) {
|
|
60
|
+
source.storage = storage;
|
|
52
61
|
}
|
|
53
|
-
|
|
54
|
-
const source = context.config.source;
|
|
62
|
+
const sourceDir = (0, projectConfig_1.configForCodebase)(context.config, codebase).source;
|
|
55
63
|
if (uploads.length) {
|
|
56
|
-
(0, utils_1.logSuccess)(`${clc.green.bold("functions:")} ${clc.bold(
|
|
64
|
+
(0, utils_1.logSuccess)(`${clc.green.bold("functions:")} ${clc.bold(sourceDir)} folder uploaded successfully`);
|
|
57
65
|
}
|
|
58
66
|
}
|
|
59
67
|
catch (err) {
|
|
@@ -61,4 +69,18 @@ async function deploy(context, options, payload) {
|
|
|
61
69
|
throw err;
|
|
62
70
|
}
|
|
63
71
|
}
|
|
72
|
+
async function deploy(context, options, payload) {
|
|
73
|
+
if (!context.config) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!payload.functions) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
await (0, checkIam_1.checkHttpIam)(context, options, payload);
|
|
80
|
+
const uploads = [];
|
|
81
|
+
for (const [codebase, { wantBackend }] of Object.entries(payload.functions)) {
|
|
82
|
+
uploads.push(uploadCodebase(context, codebase, wantBackend));
|
|
83
|
+
}
|
|
84
|
+
await Promise.all(uploads);
|
|
85
|
+
}
|
|
64
86
|
exports.deploy = deploy;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getFunctionLabel = exports.getEndpointFilters = exports.parseFunctionSelector = exports.endpointMatchesFilter = exports.endpointMatchesAnyFilter = void 0;
|
|
4
|
-
const
|
|
3
|
+
exports.groupEndpointsByCodebase = exports.targetCodebases = exports.getFunctionLabel = exports.getEndpointFilters = exports.parseFunctionSelector = exports.endpointMatchesFilter = exports.endpointMatchesAnyFilter = void 0;
|
|
4
|
+
const backend = require("./backend");
|
|
5
|
+
const projectConfig_1 = require("../../functions/projectConfig");
|
|
5
6
|
function endpointMatchesAnyFilter(endpoint, filters) {
|
|
6
7
|
if (!filters) {
|
|
7
8
|
return true;
|
|
@@ -35,7 +36,7 @@ function parseFunctionSelector(selector) {
|
|
|
35
36
|
if (fragments.length < 2) {
|
|
36
37
|
return [
|
|
37
38
|
{ codebase: fragments[0] },
|
|
38
|
-
{ codebase:
|
|
39
|
+
{ codebase: projectConfig_1.DEFAULT_CODEBASE, idChunks: fragments[0].split(/[-.]/) },
|
|
39
40
|
];
|
|
40
41
|
}
|
|
41
42
|
return [
|
|
@@ -67,6 +68,49 @@ function getEndpointFilters(options) {
|
|
|
67
68
|
}
|
|
68
69
|
exports.getEndpointFilters = getEndpointFilters;
|
|
69
70
|
function getFunctionLabel(fn) {
|
|
70
|
-
|
|
71
|
+
let id = `${fn.id}(${fn.region})`;
|
|
72
|
+
if (fn.codebase && fn.codebase !== projectConfig_1.DEFAULT_CODEBASE) {
|
|
73
|
+
id = `[${fn.codebase}]${id}`;
|
|
74
|
+
}
|
|
75
|
+
return id;
|
|
71
76
|
}
|
|
72
77
|
exports.getFunctionLabel = getFunctionLabel;
|
|
78
|
+
function targetCodebases(config, filters) {
|
|
79
|
+
const codebasesFromConfig = [...new Set(Object.values(config).map((c) => c.codebase))];
|
|
80
|
+
if (!filters) {
|
|
81
|
+
return [...codebasesFromConfig];
|
|
82
|
+
}
|
|
83
|
+
const codebasesFromFilters = [
|
|
84
|
+
...new Set(filters.map((f) => f.codebase).filter((c) => c !== undefined)),
|
|
85
|
+
];
|
|
86
|
+
if (codebasesFromFilters.length === 0) {
|
|
87
|
+
return [...codebasesFromConfig];
|
|
88
|
+
}
|
|
89
|
+
const intersections = [];
|
|
90
|
+
for (const codebase of codebasesFromConfig) {
|
|
91
|
+
if (codebasesFromFilters.includes(codebase)) {
|
|
92
|
+
intersections.push(codebase);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return intersections;
|
|
96
|
+
}
|
|
97
|
+
exports.targetCodebases = targetCodebases;
|
|
98
|
+
function groupEndpointsByCodebase(wantBackends, haveEndpoints) {
|
|
99
|
+
const grouped = {};
|
|
100
|
+
let endpointsToAssign = haveEndpoints;
|
|
101
|
+
for (const codebase of Object.keys(wantBackends)) {
|
|
102
|
+
const names = backend.allEndpoints(wantBackends[codebase]).map((e) => backend.functionName(e));
|
|
103
|
+
grouped[codebase] = backend.of(...endpointsToAssign.filter((e) => names.includes(backend.functionName(e))));
|
|
104
|
+
endpointsToAssign = endpointsToAssign.filter((e) => !names.includes(backend.functionName(e)));
|
|
105
|
+
}
|
|
106
|
+
for (const codebase of Object.keys(wantBackends)) {
|
|
107
|
+
const matchedEndpoints = endpointsToAssign.filter((e) => e.codebase === codebase);
|
|
108
|
+
grouped[codebase] = backend.merge(grouped[codebase], backend.of(...matchedEndpoints));
|
|
109
|
+
const matchedNames = matchedEndpoints.map((e) => backend.functionName(e));
|
|
110
|
+
endpointsToAssign = endpointsToAssign.filter((e) => {
|
|
111
|
+
return !matchedNames.includes(backend.functionName(e));
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return grouped;
|
|
115
|
+
}
|
|
116
|
+
exports.groupEndpointsByCodebase = groupEndpointsByCodebase;
|
|
@@ -25,16 +25,13 @@ const v1_1 = require("../../functions/events/v1");
|
|
|
25
25
|
function hasUserConfig(config) {
|
|
26
26
|
return Object.keys(config).length > 1;
|
|
27
27
|
}
|
|
28
|
-
function hasDotenv(opts) {
|
|
29
|
-
return functionsEnv.hasUserEnvs(opts);
|
|
30
|
-
}
|
|
31
28
|
async function prepare(context, options, payload) {
|
|
32
29
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
33
30
|
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
34
|
-
context.config = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions)
|
|
31
|
+
context.config = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
|
|
35
32
|
context.filters = (0, functionsDeployHelper_1.getEndpointFilters)(options);
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
const codebases = (0, functionsDeployHelper_1.targetCodebases)(context.config, context.filters);
|
|
34
|
+
if (codebases.length === 0) {
|
|
38
35
|
throw new error_1.FirebaseError("No function matches given --only filters. Aborting deployment.");
|
|
39
36
|
}
|
|
40
37
|
const checkAPIsEnabled = await Promise.all([
|
|
@@ -50,79 +47,97 @@ async function prepare(context, options, payload) {
|
|
|
50
47
|
if (checkAPIsEnabled[1]) {
|
|
51
48
|
runtimeConfig = Object.assign(Object.assign({}, runtimeConfig), (await (0, prepareFunctionsUpload_1.getFunctionsConfig)(projectId)));
|
|
52
49
|
}
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
context.sources = {};
|
|
51
|
+
const codebaseUsesEnvs = [];
|
|
52
|
+
const wantBackends = {};
|
|
53
|
+
for (const codebase of codebases) {
|
|
54
|
+
(0, utils_1.logLabeledBullet)("functions", `preparing codebase ${clc.bold(codebase)} for deployment`);
|
|
55
|
+
const config = (0, projectConfig_1.configForCodebase)(context.config, codebase);
|
|
56
|
+
const sourceDirName = config.source;
|
|
57
|
+
if (!sourceDirName) {
|
|
58
|
+
throw new error_1.FirebaseError(`No functions code detected at default location (./functions), and no functions source defined in firebase.json`);
|
|
59
|
+
}
|
|
60
|
+
const sourceDir = options.config.path(sourceDirName);
|
|
61
|
+
const delegateContext = {
|
|
62
|
+
projectId,
|
|
63
|
+
sourceDir,
|
|
64
|
+
projectDir: options.config.projectDir,
|
|
65
|
+
runtime: config.runtime || "",
|
|
66
|
+
};
|
|
67
|
+
const runtimeDelegate = await runtimes.getRuntimeDelegate(delegateContext);
|
|
68
|
+
logger_1.logger.debug(`Validating ${runtimeDelegate.name} source`);
|
|
69
|
+
await runtimeDelegate.validate();
|
|
70
|
+
logger_1.logger.debug(`Building ${runtimeDelegate.name} source`);
|
|
71
|
+
await runtimeDelegate.build();
|
|
72
|
+
const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId);
|
|
73
|
+
const userEnvOpt = {
|
|
74
|
+
functionsSource: sourceDir,
|
|
75
|
+
projectId: projectId,
|
|
76
|
+
projectAlias: options.projectAlias,
|
|
77
|
+
};
|
|
78
|
+
const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
|
|
79
|
+
logger_1.logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
|
|
80
|
+
const wantBackend = await runtimeDelegate.discoverSpec(runtimeConfig, firebaseEnvs);
|
|
81
|
+
wantBackend.environmentVariables = Object.assign(Object.assign({}, userEnvs), firebaseEnvs);
|
|
82
|
+
for (const endpoint of backend.allEndpoints(wantBackend)) {
|
|
83
|
+
endpoint.environmentVariables = wantBackend.environmentVariables;
|
|
84
|
+
endpoint.codebase = codebase;
|
|
85
|
+
}
|
|
86
|
+
wantBackends[codebase] = wantBackend;
|
|
87
|
+
if (functionsEnv.hasUserEnvs(userEnvOpt)) {
|
|
88
|
+
codebaseUsesEnvs.push(codebase);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
validate.endpointsAreUnique(wantBackends);
|
|
92
|
+
for (const [codebase, wantBackend] of Object.entries(wantBackends)) {
|
|
93
|
+
const config = (0, projectConfig_1.configForCodebase)(context.config, codebase);
|
|
94
|
+
const sourceDirName = config.source;
|
|
95
|
+
const sourceDir = options.config.path(sourceDirName);
|
|
96
|
+
const source = {};
|
|
97
|
+
if (backend.someEndpoint(wantBackend, () => true)) {
|
|
98
|
+
(0, utils_1.logLabeledBullet)("functions", `preparing ${clc.bold(sourceDirName)} directory for uploading...`);
|
|
99
|
+
}
|
|
100
|
+
if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) {
|
|
101
|
+
if (!previews_1.previews.functionsv2) {
|
|
102
|
+
throw new error_1.FirebaseError("This version of firebase-tools does not support Google Cloud " +
|
|
103
|
+
"Functions gen 2\n" +
|
|
104
|
+
"If Cloud Functions for Firebase gen 2 is still in alpha, sign up " +
|
|
105
|
+
"for the alpha program at " +
|
|
106
|
+
"https://services.google.com/fb/forms/firebasealphaprogram/\n" +
|
|
107
|
+
"If Cloud Functions for Firebase gen 2 is in beta, get the latest " +
|
|
108
|
+
"version of Firebse Tools with `npm i -g firebase-tools@latest`");
|
|
109
|
+
}
|
|
110
|
+
source.functionsSourceV2 = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, config);
|
|
111
|
+
}
|
|
112
|
+
if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv1")) {
|
|
113
|
+
source.functionsSourceV1 = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, config, runtimeConfig);
|
|
114
|
+
}
|
|
115
|
+
context.sources[codebase] = source;
|
|
116
|
+
}
|
|
117
|
+
payload.functions = {};
|
|
118
|
+
const haveBackends = (0, functionsDeployHelper_1.groupEndpointsByCodebase)(wantBackends, backend.allEndpoints(await backend.existingBackend(context)));
|
|
119
|
+
for (const [codebase, wantBackend] of Object.entries(wantBackends)) {
|
|
120
|
+
const haveBackend = haveBackends[codebase] || Object.assign({}, backend.empty());
|
|
121
|
+
payload.functions[codebase] = { wantBackend, haveBackend };
|
|
122
|
+
}
|
|
123
|
+
for (const [codebase, { wantBackend, haveBackend }] of Object.entries(payload.functions)) {
|
|
124
|
+
inferDetailsFromExisting(wantBackend, haveBackend, codebaseUsesEnvs.includes(codebase));
|
|
125
|
+
await (0, triggerRegionHelper_1.ensureTriggerRegions)(wantBackend);
|
|
126
|
+
validate.endpointsAreValid(wantBackend);
|
|
127
|
+
inferBlockingDetails(wantBackend);
|
|
57
128
|
}
|
|
58
|
-
const sourceDir = options.config.path(sourceDirName);
|
|
59
|
-
const delegateContext = {
|
|
60
|
-
projectId,
|
|
61
|
-
sourceDir,
|
|
62
|
-
projectDir: options.config.projectDir,
|
|
63
|
-
runtime: context.config.runtime || "",
|
|
64
|
-
};
|
|
65
|
-
const runtimeDelegate = await runtimes.getRuntimeDelegate(delegateContext);
|
|
66
|
-
logger_1.logger.debug(`Validating ${runtimeDelegate.name} source`);
|
|
67
|
-
await runtimeDelegate.validate();
|
|
68
|
-
logger_1.logger.debug(`Building ${runtimeDelegate.name} source`);
|
|
69
|
-
await runtimeDelegate.build();
|
|
70
|
-
const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId);
|
|
71
|
-
const userEnvOpt = {
|
|
72
|
-
functionsSource: sourceDir,
|
|
73
|
-
projectId: projectId,
|
|
74
|
-
projectAlias: options.projectAlias,
|
|
75
|
-
};
|
|
76
|
-
const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
|
|
77
|
-
const usedDotenv = hasDotenv(userEnvOpt);
|
|
78
129
|
const tag = hasUserConfig(runtimeConfig)
|
|
79
|
-
?
|
|
130
|
+
? codebaseUsesEnvs.length > 0
|
|
80
131
|
? "mixed"
|
|
81
132
|
: "runtime_config"
|
|
82
|
-
:
|
|
133
|
+
: codebaseUsesEnvs.length > 0
|
|
83
134
|
? "dotenv"
|
|
84
135
|
: "none";
|
|
85
136
|
void (0, track_1.track)("functions_codebase_deploy_env_method", tag);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
wantBackend
|
|
89
|
-
|
|
90
|
-
endpoint.environmentVariables = wantBackend.environmentVariables;
|
|
91
|
-
endpoint.codebase = context.config.codebase;
|
|
92
|
-
}
|
|
93
|
-
const source = {};
|
|
94
|
-
if (backend.someEndpoint(wantBackend, () => true)) {
|
|
95
|
-
(0, utils_1.logLabeledBullet)("functions", `preparing ${clc.bold(sourceDirName)} directory for uploading...`);
|
|
96
|
-
}
|
|
97
|
-
if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) {
|
|
98
|
-
if (!previews_1.previews.functionsv2) {
|
|
99
|
-
throw new error_1.FirebaseError("This version of firebase-tools does not support Google Cloud " +
|
|
100
|
-
"Functions gen 2\n" +
|
|
101
|
-
"If Cloud Functions for Firebase gen 2 is still in alpha, sign up " +
|
|
102
|
-
"for the alpha program at " +
|
|
103
|
-
"https://services.google.com/fb/forms/firebasealphaprogram/\n" +
|
|
104
|
-
"If Cloud Functions for Firebase gen 2 is in beta, get the latest " +
|
|
105
|
-
"version of Firebse Tools with `npm i -g firebase-tools@latest`");
|
|
106
|
-
}
|
|
107
|
-
source.functionsSourceV2 = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, context.config);
|
|
108
|
-
}
|
|
109
|
-
if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv1")) {
|
|
110
|
-
source.functionsSourceV1 = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, context.config, runtimeConfig);
|
|
111
|
-
}
|
|
112
|
-
context.source = source;
|
|
113
|
-
const wantEndpointNames = backend.allEndpoints(wantBackend).map((e) => backend.functionName(e));
|
|
114
|
-
const haveBackend = backend.matchingBackend(await backend.existingBackend(context), (endpoint) => {
|
|
115
|
-
var _a;
|
|
116
|
-
if (endpoint.codebase === ((_a = context.config) === null || _a === void 0 ? void 0 : _a.codebase)) {
|
|
117
|
-
return true;
|
|
118
|
-
}
|
|
119
|
-
return wantEndpointNames.includes(backend.functionName(endpoint));
|
|
120
|
-
});
|
|
121
|
-
inferDetailsFromExisting(wantBackend, haveBackend, usedDotenv);
|
|
122
|
-
await (0, triggerRegionHelper_1.ensureTriggerRegions)(wantBackend);
|
|
123
|
-
validate.endpointsAreValid(wantBackend);
|
|
124
|
-
inferBlockingDetails(wantBackend);
|
|
125
|
-
payload.functions = { wantBackend: wantBackend, haveBackend: haveBackend };
|
|
137
|
+
const codebaseCnt = Object.keys(payload.functions).length;
|
|
138
|
+
void (0, track_1.track)("functions_codebase_deploy_count", codebaseCnt >= 5 ? "5+" : codebaseCnt.toString());
|
|
139
|
+
const wantBackend = backend.merge(...Object.values(wantBackends));
|
|
140
|
+
const haveBackend = backend.merge(...Object.values(haveBackends));
|
|
126
141
|
await Promise.all(Object.values(wantBackend.requiredAPIs).map(({ api }) => {
|
|
127
142
|
return ensureApiEnabled.ensure(projectId, api, "functions", false);
|
|
128
143
|
}));
|
|
@@ -44,8 +44,7 @@ class Fabricator {
|
|
|
44
44
|
constructor(args) {
|
|
45
45
|
this.executor = args.executor;
|
|
46
46
|
this.functionExecutor = args.functionExecutor;
|
|
47
|
-
this.
|
|
48
|
-
this.storage = args.storage;
|
|
47
|
+
this.sources = args.sources;
|
|
49
48
|
this.appEngineLocation = args.appEngineLocation;
|
|
50
49
|
}
|
|
51
50
|
async applyPlan(plan) {
|
|
@@ -154,12 +153,13 @@ class Fabricator {
|
|
|
154
153
|
}
|
|
155
154
|
}
|
|
156
155
|
async createV1Function(endpoint, scraper) {
|
|
157
|
-
var _a;
|
|
158
|
-
|
|
156
|
+
var _a, _b;
|
|
157
|
+
const sourceUrl = (_a = this.sources[endpoint.codebase]) === null || _a === void 0 ? void 0 : _a.sourceUrl;
|
|
158
|
+
if (!sourceUrl) {
|
|
159
159
|
logger_1.logger.debug("Precondition failed. Cannot create a GCF function without sourceUrl");
|
|
160
160
|
throw new Error("Precondition failed");
|
|
161
161
|
}
|
|
162
|
-
const apiFunction = gcf.functionFromEndpoint(endpoint,
|
|
162
|
+
const apiFunction = gcf.functionFromEndpoint(endpoint, sourceUrl);
|
|
163
163
|
if (apiFunction.httpsTrigger) {
|
|
164
164
|
apiFunction.httpsTrigger.securityLevel = "SECURE_ALWAYS";
|
|
165
165
|
}
|
|
@@ -167,10 +167,10 @@ class Fabricator {
|
|
|
167
167
|
const resultFunction = await this.functionExecutor
|
|
168
168
|
.run(async () => {
|
|
169
169
|
const op = await gcf.createFunction(apiFunction);
|
|
170
|
-
return poller.pollOperation(Object.assign(Object.assign({}, gcfV1PollerOptions), { pollerName: `create-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, onPoll: scraper.poller }));
|
|
170
|
+
return poller.pollOperation(Object.assign(Object.assign({}, gcfV1PollerOptions), { pollerName: `create-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, onPoll: scraper.poller }));
|
|
171
171
|
})
|
|
172
172
|
.catch(rethrowAs(endpoint, "create"));
|
|
173
|
-
endpoint.uri = (
|
|
173
|
+
endpoint.uri = (_b = resultFunction === null || resultFunction === void 0 ? void 0 : resultFunction.httpsTrigger) === null || _b === void 0 ? void 0 : _b.url;
|
|
174
174
|
if (backend.isHttpsTriggered(endpoint)) {
|
|
175
175
|
const invoker = endpoint.httpsTrigger.invoker || ["public"];
|
|
176
176
|
if (!invoker.includes("private")) {
|
|
@@ -208,13 +208,14 @@ class Fabricator {
|
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
210
|
async createV2Function(endpoint) {
|
|
211
|
-
var _a;
|
|
212
|
-
|
|
211
|
+
var _a, _b;
|
|
212
|
+
const storage = (_a = this.sources[endpoint.codebase]) === null || _a === void 0 ? void 0 : _a.storage;
|
|
213
|
+
if (!storage) {
|
|
213
214
|
logger_1.logger.debug("Precondition failed. Cannot create a GCFv2 function without storage");
|
|
214
215
|
throw new Error("Precondition failed");
|
|
215
216
|
}
|
|
216
|
-
const apiFunction = gcfV2.functionFromEndpoint(endpoint,
|
|
217
|
-
const topic = (
|
|
217
|
+
const apiFunction = gcfV2.functionFromEndpoint(endpoint, storage);
|
|
218
|
+
const topic = (_b = apiFunction.eventTrigger) === null || _b === void 0 ? void 0 : _b.pubsubTopic;
|
|
218
219
|
if (topic) {
|
|
219
220
|
await this.executor
|
|
220
221
|
.run(async () => {
|
|
@@ -235,7 +236,7 @@ class Fabricator {
|
|
|
235
236
|
const resultFunction = await this.functionExecutor
|
|
236
237
|
.run(async () => {
|
|
237
238
|
const op = await gcfV2.createFunction(apiFunction);
|
|
238
|
-
return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `create-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
|
|
239
|
+
return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `create-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
|
|
239
240
|
})
|
|
240
241
|
.catch(rethrowAs(endpoint, "create"));
|
|
241
242
|
endpoint.uri = resultFunction.serviceConfig.uri;
|
|
@@ -275,20 +276,21 @@ class Fabricator {
|
|
|
275
276
|
}
|
|
276
277
|
}
|
|
277
278
|
async updateV1Function(endpoint, scraper) {
|
|
278
|
-
var _a;
|
|
279
|
-
|
|
279
|
+
var _a, _b;
|
|
280
|
+
const sourceUrl = (_a = this.sources[endpoint.codebase]) === null || _a === void 0 ? void 0 : _a.sourceUrl;
|
|
281
|
+
if (!sourceUrl) {
|
|
280
282
|
logger_1.logger.debug("Precondition failed. Cannot update a GCF function without sourceUrl");
|
|
281
283
|
throw new Error("Precondition failed");
|
|
282
284
|
}
|
|
283
|
-
const apiFunction = gcf.functionFromEndpoint(endpoint,
|
|
285
|
+
const apiFunction = gcf.functionFromEndpoint(endpoint, sourceUrl);
|
|
284
286
|
apiFunction.sourceToken = await scraper.tokenPromise();
|
|
285
287
|
const resultFunction = await this.functionExecutor
|
|
286
288
|
.run(async () => {
|
|
287
289
|
const op = await gcf.updateFunction(apiFunction);
|
|
288
|
-
return await poller.pollOperation(Object.assign(Object.assign({}, gcfV1PollerOptions), { pollerName: `update-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, onPoll: scraper.poller }));
|
|
290
|
+
return await poller.pollOperation(Object.assign(Object.assign({}, gcfV1PollerOptions), { pollerName: `update-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, onPoll: scraper.poller }));
|
|
289
291
|
})
|
|
290
292
|
.catch(rethrowAs(endpoint, "update"));
|
|
291
|
-
endpoint.uri = (
|
|
293
|
+
endpoint.uri = (_b = resultFunction === null || resultFunction === void 0 ? void 0 : resultFunction.httpsTrigger) === null || _b === void 0 ? void 0 : _b.url;
|
|
292
294
|
let invoker;
|
|
293
295
|
if (backend.isHttpsTriggered(endpoint)) {
|
|
294
296
|
invoker = endpoint.httpsTrigger.invoker;
|
|
@@ -307,19 +309,20 @@ class Fabricator {
|
|
|
307
309
|
}
|
|
308
310
|
}
|
|
309
311
|
async updateV2Function(endpoint) {
|
|
310
|
-
var _a;
|
|
311
|
-
|
|
312
|
+
var _a, _b;
|
|
313
|
+
const storage = (_a = this.sources[endpoint.codebase]) === null || _a === void 0 ? void 0 : _a.storage;
|
|
314
|
+
if (!storage) {
|
|
312
315
|
logger_1.logger.debug("Precondition failed. Cannot update a GCFv2 function without storage");
|
|
313
316
|
throw new Error("Precondition failed");
|
|
314
317
|
}
|
|
315
|
-
const apiFunction = gcfV2.functionFromEndpoint(endpoint,
|
|
316
|
-
if ((
|
|
318
|
+
const apiFunction = gcfV2.functionFromEndpoint(endpoint, storage);
|
|
319
|
+
if ((_b = apiFunction.eventTrigger) === null || _b === void 0 ? void 0 : _b.pubsubTopic) {
|
|
317
320
|
delete apiFunction.eventTrigger.pubsubTopic;
|
|
318
321
|
}
|
|
319
322
|
const resultFunction = await this.functionExecutor
|
|
320
323
|
.run(async () => {
|
|
321
324
|
const op = await gcfV2.updateFunction(apiFunction);
|
|
322
|
-
return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `update-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
|
|
325
|
+
return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `update-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
|
|
323
326
|
})
|
|
324
327
|
.catch(rethrowAs(endpoint, "update"));
|
|
325
328
|
endpoint.uri = resultFunction.serviceConfig.uri;
|
|
@@ -349,7 +352,7 @@ class Fabricator {
|
|
|
349
352
|
await this.functionExecutor
|
|
350
353
|
.run(async () => {
|
|
351
354
|
const op = await gcf.deleteFunction(fnName);
|
|
352
|
-
const pollerOptions = Object.assign(Object.assign({}, gcfV1PollerOptions), { pollerName: `delete-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name });
|
|
355
|
+
const pollerOptions = Object.assign(Object.assign({}, gcfV1PollerOptions), { pollerName: `delete-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name });
|
|
353
356
|
await poller.pollOperation(pollerOptions);
|
|
354
357
|
})
|
|
355
358
|
.catch(rethrowAs(endpoint, "delete"));
|
|
@@ -359,7 +362,7 @@ class Fabricator {
|
|
|
359
362
|
await this.functionExecutor
|
|
360
363
|
.run(async () => {
|
|
361
364
|
const op = await gcfV2.deleteFunction(fnName);
|
|
362
|
-
const pollerOptions = Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `delete-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name });
|
|
365
|
+
const pollerOptions = Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `delete-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name });
|
|
363
366
|
await poller.pollOperation(pollerOptions);
|
|
364
367
|
})
|
|
365
368
|
.catch(rethrowAs(endpoint, "delete"));
|
|
@@ -471,7 +474,7 @@ class Fabricator {
|
|
|
471
474
|
logOpStart(op, endpoint) {
|
|
472
475
|
const runtime = (0, runtimes_1.getHumanFriendlyRuntimeName)(endpoint.runtime);
|
|
473
476
|
const label = helper.getFunctionLabel(endpoint);
|
|
474
|
-
utils.
|
|
477
|
+
utils.logLabeledBullet("functions", `${op} ${runtime} function ${clc.bold(label)}...`);
|
|
475
478
|
}
|
|
476
479
|
logOpSuccess(op, endpoint) {
|
|
477
480
|
const label = helper.getFunctionLabel(endpoint);
|
|
@@ -24,8 +24,18 @@ async function release(context, options, payload) {
|
|
|
24
24
|
if (!payload.functions) {
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
if (!context.sources) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
let plan = {};
|
|
31
|
+
for (const [codebase, { wantBackend, haveBackend }] of Object.entries(payload.functions)) {
|
|
32
|
+
plan = Object.assign(Object.assign({}, plan), planner.createDeploymentPlan({
|
|
33
|
+
codebase,
|
|
34
|
+
wantBackend,
|
|
35
|
+
haveBackend,
|
|
36
|
+
filters: context.filters,
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
29
39
|
const fnsToDelete = Object.values(plan)
|
|
30
40
|
.map((regionalChanges) => regionalChanges.endpointsToDelete)
|
|
31
41
|
.reduce(functional_1.reduceFlat, []);
|
|
@@ -44,15 +54,15 @@ async function release(context, options, payload) {
|
|
|
44
54
|
const fab = new fabricator.Fabricator({
|
|
45
55
|
functionExecutor,
|
|
46
56
|
executor: new executor.QueueExecutor({}),
|
|
47
|
-
|
|
48
|
-
storage: context.source.storage,
|
|
57
|
+
sources: context.sources,
|
|
49
58
|
appEngineLocation: (0, functionsConfig_1.getAppEngineLocation)(context.firebaseConfig),
|
|
50
59
|
});
|
|
51
60
|
const summary = await fab.applyPlan(plan);
|
|
52
61
|
await reporter.logAndTrackDeployStats(summary);
|
|
53
62
|
reporter.printErrors(summary);
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
const wantBackend = backend.merge(...Object.values(payload.functions).map((p) => p.wantBackend));
|
|
64
|
+
printTriggerUrls(wantBackend);
|
|
65
|
+
const haveEndpoints = backend.allEndpoints(wantBackend);
|
|
56
66
|
const deletedEndpoints = Object.values(plan)
|
|
57
67
|
.map((r) => r.endpointsToDelete)
|
|
58
68
|
.reduce(functional_1.reduceFlat, []);
|
|
@@ -48,20 +48,25 @@ function calculateUpdate(want, have) {
|
|
|
48
48
|
return update;
|
|
49
49
|
}
|
|
50
50
|
exports.calculateUpdate = calculateUpdate;
|
|
51
|
-
function createDeploymentPlan(
|
|
51
|
+
function createDeploymentPlan(args) {
|
|
52
|
+
let { wantBackend, haveBackend, codebase, filters, deleteAll } = args;
|
|
52
53
|
let deployment = {};
|
|
53
|
-
|
|
54
|
+
wantBackend = backend.matchingBackend(wantBackend, (endpoint) => {
|
|
54
55
|
return (0, functionsDeployHelper_1.endpointMatchesAnyFilter)(endpoint, filters);
|
|
55
56
|
});
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
const wantedEndpoint = backend.hasEndpoint(wantBackend);
|
|
58
|
+
haveBackend = backend.matchingBackend(haveBackend, (endpoint) => {
|
|
59
|
+
return wantedEndpoint(endpoint) || (0, functionsDeployHelper_1.endpointMatchesAnyFilter)(endpoint, filters);
|
|
58
60
|
});
|
|
59
|
-
const regions = new Set([
|
|
61
|
+
const regions = new Set([
|
|
62
|
+
...Object.keys(wantBackend.endpoints),
|
|
63
|
+
...Object.keys(haveBackend.endpoints),
|
|
64
|
+
]);
|
|
60
65
|
for (const region of regions) {
|
|
61
|
-
const changesets = calculateChangesets(
|
|
66
|
+
const changesets = calculateChangesets(wantBackend.endpoints[region] || {}, haveBackend.endpoints[region] || {}, (e) => `${codebase}-${e.region}-${e.availableMemoryMb || "default"}`, deleteAll);
|
|
62
67
|
deployment = Object.assign(Object.assign({}, deployment), changesets);
|
|
63
68
|
}
|
|
64
|
-
if (upgradedToGCFv2WithoutSettingConcurrency(
|
|
69
|
+
if (upgradedToGCFv2WithoutSettingConcurrency(wantBackend, haveBackend)) {
|
|
65
70
|
utils.logLabeledBullet("functions", "You are updating one or more functions to Google Cloud Functions v2, " +
|
|
66
71
|
"which introduces support for concurrent execution. New functions " +
|
|
67
72
|
"default to 80 concurrent executions, but existing functions keep the " +
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.secretsAreValid = exports.functionIdsAreValid = exports.functionsDirectoryExists = exports.endpointsAreValid = void 0;
|
|
3
|
+
exports.secretsAreValid = exports.functionIdsAreValid = exports.functionsDirectoryExists = exports.endpointsAreUnique = exports.endpointsAreValid = void 0;
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const clc = require("cli-color");
|
|
6
6
|
const error_1 = require("../../error");
|
|
@@ -39,6 +39,29 @@ function endpointsAreValid(wantBackend) {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
exports.endpointsAreValid = endpointsAreValid;
|
|
42
|
+
function endpointsAreUnique(backends) {
|
|
43
|
+
const endpointToCodebases = {};
|
|
44
|
+
for (const [codebase, b] of Object.entries(backends)) {
|
|
45
|
+
for (const endpoint of backend.allEndpoints(b)) {
|
|
46
|
+
const key = backend.functionName(endpoint);
|
|
47
|
+
const cs = endpointToCodebases[key] || new Set();
|
|
48
|
+
cs.add(codebase);
|
|
49
|
+
endpointToCodebases[key] = cs;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const conflicts = {};
|
|
53
|
+
for (const [fn, codebases] of Object.entries(endpointToCodebases)) {
|
|
54
|
+
if (codebases.size > 1) {
|
|
55
|
+
conflicts[fn] = Array.from(codebases);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (Object.keys(conflicts).length === 0) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const msgs = Object.entries(conflicts).map(([fn, codebases]) => `${fn}: ${codebases.join(",")}`);
|
|
62
|
+
throw new error_1.FirebaseError("More than one codebase claims following functions:\n\t" + `${msgs.join("\n\t")}`);
|
|
63
|
+
}
|
|
64
|
+
exports.endpointsAreUnique = endpointsAreUnique;
|
|
42
65
|
function functionsDirectoryExists(sourceDir, projectDir) {
|
|
43
66
|
if (!fsutils.dirExistsSync(sourceDir)) {
|
|
44
67
|
const sourceDirName = path.relative(projectDir, sourceDir);
|
|
@@ -40,10 +40,12 @@ async function convertConfig(context, payload, config, finalize) {
|
|
|
40
40
|
return out;
|
|
41
41
|
}
|
|
42
42
|
const endpointBeingDeployed = (serviceId, region = "us-central1") => {
|
|
43
|
-
var _a
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
var _a;
|
|
44
|
+
for (const { wantBackend } of Object.values(payload.functions || {})) {
|
|
45
|
+
const endpoint = (_a = wantBackend === null || wantBackend === void 0 ? void 0 : wantBackend.endpoints[region]) === null || _a === void 0 ? void 0 : _a[serviceId];
|
|
46
|
+
if (endpoint && (0, backend_1.isHttpsTriggered)(endpoint) && endpoint.platform === "gcfv2")
|
|
47
|
+
return endpoint;
|
|
48
|
+
}
|
|
47
49
|
return undefined;
|
|
48
50
|
};
|
|
49
51
|
const matchingEndpoint = async (serviceId, region = "us-central1") => {
|
|
@@ -268,16 +268,19 @@ async function startAll(options, showUI = true) {
|
|
|
268
268
|
const emulatableBackends = [];
|
|
269
269
|
const projectDir = (options.extDevDir || options.config.projectDir);
|
|
270
270
|
if (shouldStart(options, types_1.Emulators.FUNCTIONS)) {
|
|
271
|
-
const functionsCfg = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions)
|
|
271
|
+
const functionsCfg = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
|
|
272
272
|
utils.assertIsStringOrUndefined(options.extDevDir);
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
273
|
+
for (const cfg of functionsCfg) {
|
|
274
|
+
const functionsDir = path.join(projectDir, cfg.source);
|
|
275
|
+
emulatableBackends.push({
|
|
276
|
+
functionsDir,
|
|
277
|
+
codebase: cfg.codebase,
|
|
278
|
+
env: Object.assign({}, options.extDevEnv),
|
|
279
|
+
secretEnv: [],
|
|
280
|
+
predefinedTriggers: options.extDevTriggers,
|
|
281
|
+
nodeMajorVersion: (0, functionsEmulatorUtils_1.parseRuntimeVersion)(options.extDevNodeVersion || cfg.runtime),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
281
284
|
}
|
|
282
285
|
if (shouldStart(options, types_1.Emulators.EXTENSIONS) && previews_1.previews.extensionsemulator) {
|
|
283
286
|
const projectNumber = constants_1.Constants.isDemoProject(projectId)
|
|
@@ -177,7 +177,9 @@ class FunctionsEmulator {
|
|
|
177
177
|
});
|
|
178
178
|
return hub;
|
|
179
179
|
}
|
|
180
|
-
async invokeTrigger(
|
|
180
|
+
async invokeTrigger(trigger, proto, runtimeOpts) {
|
|
181
|
+
const record = this.getTriggerRecordByKey(this.getTriggerKey(trigger));
|
|
182
|
+
const backend = record.backend;
|
|
181
183
|
const bundleTemplate = this.getBaseBundle();
|
|
182
184
|
const runtimeBundle = Object.assign(Object.assign({}, bundleTemplate), { proto });
|
|
183
185
|
if (this.args.debugPort) {
|
|
@@ -284,6 +286,9 @@ class FunctionsEmulator {
|
|
|
284
286
|
const discoveredBackend = await runtimeDelegate.discoverSpec(runtimeConfig, Object.assign(Object.assign(Object.assign(Object.assign({}, this.getSystemEnvs()), this.getEmulatorEnvs()), { FIREBASE_CONFIG: this.getFirebaseConfig() }), emulatableBackend.env));
|
|
285
287
|
const endpoints = backend.allEndpoints(discoveredBackend);
|
|
286
288
|
(0, functionsEmulatorShared_1.prepareEndpoints)(endpoints);
|
|
289
|
+
for (const e of endpoints) {
|
|
290
|
+
e.codebase = emulatableBackend.codebase;
|
|
291
|
+
}
|
|
287
292
|
triggerDefinitions = (0, functionsEmulatorShared_1.emulatedFunctionsFromEndpoints)(endpoints);
|
|
288
293
|
}
|
|
289
294
|
const toSetup = triggerDefinitions.filter((definition) => {
|
|
@@ -579,6 +584,7 @@ class FunctionsEmulator {
|
|
|
579
584
|
};
|
|
580
585
|
}
|
|
581
586
|
setTriggersForTesting(triggers, backend) {
|
|
587
|
+
this.triggers = {};
|
|
582
588
|
triggers.forEach((def) => this.addTriggerRecord(def, { backend, ignored: false }));
|
|
583
589
|
}
|
|
584
590
|
getBaseBundle() {
|
|
@@ -862,7 +868,7 @@ class FunctionsEmulator {
|
|
|
862
868
|
}
|
|
863
869
|
const trigger = record.def;
|
|
864
870
|
const service = (0, functionsEmulatorShared_1.getFunctionService)(trigger);
|
|
865
|
-
const worker = await this.invokeTrigger(
|
|
871
|
+
const worker = await this.invokeTrigger(trigger, proto);
|
|
866
872
|
return new Promise((resolve, reject) => {
|
|
867
873
|
if (projectId !== this.args.projectId) {
|
|
868
874
|
if (service !== constants_1.Constants.SERVICE_REALTIME_DATABASE) {
|
|
@@ -949,7 +955,7 @@ class FunctionsEmulator {
|
|
|
949
955
|
req.headers[functionsEmulatorShared_1.HttpConstants.CALLABLE_AUTH_HEADER] = encodeURIComponent(JSON.stringify(contextAuth));
|
|
950
956
|
}
|
|
951
957
|
}
|
|
952
|
-
const worker = await this.invokeTrigger(
|
|
958
|
+
const worker = await this.invokeTrigger(trigger);
|
|
953
959
|
worker.onLogs((el) => {
|
|
954
960
|
if (el.level === "FATAL") {
|
|
955
961
|
res.status(500).send(el.text);
|
|
@@ -66,6 +66,7 @@ function emulatedFunctionsFromEndpoints(endpoints) {
|
|
|
66
66
|
region: endpoint.region,
|
|
67
67
|
name: endpoint.id,
|
|
68
68
|
id: `${endpoint.region}-${endpoint.id}`,
|
|
69
|
+
codebase: endpoint.codebase,
|
|
69
70
|
};
|
|
70
71
|
(0, proto_1.copyIfPresent)(def, endpoint, "availableMemoryMb", "labels", "timeoutSeconds", "platform", "secretEnvironmentVariables");
|
|
71
72
|
if (backend.isHttpsTriggered(endpoint)) {
|
|
@@ -279,7 +280,7 @@ function toBackendInfo(e, cf3Triggers) {
|
|
|
279
280
|
extension: e.extension,
|
|
280
281
|
extensionVersion: extensionVersion,
|
|
281
282
|
extensionSpec: extensionSpec,
|
|
282
|
-
functionTriggers: (_b = e.predefinedTriggers) !== null && _b !== void 0 ? _b : cf3Triggers,
|
|
283
|
+
functionTriggers: (_b = e.predefinedTriggers) !== null && _b !== void 0 ? _b : cf3Triggers.filter((t) => t.codebase === e.codebase),
|
|
283
284
|
}));
|
|
284
285
|
}
|
|
285
286
|
exports.toBackendInfo = toBackendInfo;
|
|
@@ -7,9 +7,8 @@ const utils = require("../utils");
|
|
|
7
7
|
const logger_1 = require("../logger");
|
|
8
8
|
const error_1 = require("../error");
|
|
9
9
|
class FunctionsEmulatorShell {
|
|
10
|
-
constructor(emu
|
|
10
|
+
constructor(emu) {
|
|
11
11
|
this.emu = emu;
|
|
12
|
-
this.backend = backend;
|
|
13
12
|
this.urls = {};
|
|
14
13
|
this.triggers = emu.getTriggerDefinitions();
|
|
15
14
|
this.emulatedFunctions = this.triggers.map((t) => t.id);
|
|
@@ -42,7 +41,7 @@ class FunctionsEmulatorShell {
|
|
|
42
41
|
auth: opts.auth,
|
|
43
42
|
data,
|
|
44
43
|
};
|
|
45
|
-
this.emu.invokeTrigger(
|
|
44
|
+
this.emu.invokeTrigger(trigger, proto);
|
|
46
45
|
}
|
|
47
46
|
getTrigger(name) {
|
|
48
47
|
const result = this.triggers.find((trigger) => {
|
|
@@ -44,14 +44,7 @@ class PubsubEmulator {
|
|
|
44
44
|
getName() {
|
|
45
45
|
return types_1.Emulators.PUBSUB;
|
|
46
46
|
}
|
|
47
|
-
async
|
|
48
|
-
this.logger.logLabeled("DEBUG", "pubsub", `addTrigger(${topicName}, ${triggerKey}, ${signatureType})`);
|
|
49
|
-
const triggers = this.triggersForTopic.get(topicName) || [];
|
|
50
|
-
if (triggers.some((t) => t.triggerKey === triggerKey) &&
|
|
51
|
-
this.subscriptionForTopic.has(topicName)) {
|
|
52
|
-
this.logger.logLabeled("DEBUG", "pubsub", "Trigger already exists");
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
47
|
+
async maybeCreateTopicAndSub(topicName) {
|
|
55
48
|
const topic = this.pubsub.topic(topicName);
|
|
56
49
|
try {
|
|
57
50
|
this.logger.logLabeled("DEBUG", "pubsub", `Creating topic: ${topicName}`);
|
|
@@ -74,7 +67,7 @@ class PubsubEmulator {
|
|
|
74
67
|
catch (e) {
|
|
75
68
|
if (e && e.code === 6) {
|
|
76
69
|
this.logger.logLabeled("DEBUG", "pubsub", `Sub for ${topicName} exists`);
|
|
77
|
-
sub = topic.subscription(
|
|
70
|
+
sub = topic.subscription(subName);
|
|
78
71
|
}
|
|
79
72
|
else {
|
|
80
73
|
throw new error_1.FirebaseError(`Could not create sub ${subName}`, { original: e });
|
|
@@ -83,6 +76,17 @@ class PubsubEmulator {
|
|
|
83
76
|
sub.on("message", (message) => {
|
|
84
77
|
this.onMessage(topicName, message);
|
|
85
78
|
});
|
|
79
|
+
return sub;
|
|
80
|
+
}
|
|
81
|
+
async addTrigger(topicName, triggerKey, signatureType) {
|
|
82
|
+
this.logger.logLabeled("DEBUG", "pubsub", `addTrigger(${topicName}, ${triggerKey}, ${signatureType})`);
|
|
83
|
+
const sub = await this.maybeCreateTopicAndSub(topicName);
|
|
84
|
+
const triggers = this.triggersForTopic.get(topicName) || [];
|
|
85
|
+
if (triggers.some((t) => t.triggerKey === triggerKey) &&
|
|
86
|
+
this.subscriptionForTopic.has(topicName)) {
|
|
87
|
+
this.logger.logLabeled("DEBUG", "pubsub", "Trigger already exists");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
86
90
|
triggers.push({ triggerKey, signatureType });
|
|
87
91
|
this.triggersForTopic.set(topicName, triggers);
|
|
88
92
|
this.subscriptionForTopic.set(topicName, sub);
|
package/lib/ensureApiEnabled.js
CHANGED
|
@@ -28,7 +28,7 @@ async function check(projectId, apiName, prefix, silent = false) {
|
|
|
28
28
|
exports.check = check;
|
|
29
29
|
async function enable(projectId, apiName) {
|
|
30
30
|
try {
|
|
31
|
-
await apiClient.post(`/projects/${projectId}/services/${apiName}:enable`, {
|
|
31
|
+
await apiClient.post(`/projects/${projectId}/services/${apiName}:enable`, undefined, {
|
|
32
32
|
skipLog: { resBody: true },
|
|
33
33
|
});
|
|
34
34
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.normalizeAndValidate = exports.validate = exports.normalize = exports.DEFAULT_CODEBASE = void 0;
|
|
3
|
+
exports.configForCodebase = exports.normalizeAndValidate = exports.validate = exports.normalize = exports.DEFAULT_CODEBASE = void 0;
|
|
4
4
|
const error_1 = require("../error");
|
|
5
5
|
exports.DEFAULT_CODEBASE = "default";
|
|
6
6
|
function normalize(config) {
|
|
@@ -40,16 +40,21 @@ function assertUnique(config, property) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
function validate(config) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
assertUnique([validated], "source");
|
|
48
|
-
assertUnique([validated], "codebase");
|
|
49
|
-
return [validated];
|
|
43
|
+
const validated = config.map((cfg) => validateSingle(cfg));
|
|
44
|
+
assertUnique(validated, "source");
|
|
45
|
+
assertUnique(validated, "codebase");
|
|
46
|
+
return validated;
|
|
50
47
|
}
|
|
51
48
|
exports.validate = validate;
|
|
52
49
|
function normalizeAndValidate(config) {
|
|
53
50
|
return validate(normalize(config));
|
|
54
51
|
}
|
|
55
52
|
exports.normalizeAndValidate = normalizeAndValidate;
|
|
53
|
+
function configForCodebase(config, codebase) {
|
|
54
|
+
const codebaseCfg = config.find((c) => c.codebase === codebase);
|
|
55
|
+
if (!codebaseCfg) {
|
|
56
|
+
throw new error_1.FirebaseError(`No functions config found for codebase ${codebase}`);
|
|
57
|
+
}
|
|
58
|
+
return codebaseCfg;
|
|
59
|
+
}
|
|
60
|
+
exports.configForCodebase = configForCodebase;
|
|
@@ -55,7 +55,7 @@ const actionFunction = async (options) => {
|
|
|
55
55
|
})
|
|
56
56
|
.then(() => {
|
|
57
57
|
const instance = serveFunctions.get();
|
|
58
|
-
const emulator = new shell.FunctionsEmulatorShell(instance
|
|
58
|
+
const emulator = new shell.FunctionsEmulatorShell(instance);
|
|
59
59
|
if (emulator.emulatedFunctions && emulator.emulatedFunctions.length === 0) {
|
|
60
60
|
logger_1.logger.info("No functions emulated.");
|
|
61
61
|
process.exit();
|
|
@@ -280,6 +280,7 @@ function endpointFromFunction(gcfFunction) {
|
|
|
280
280
|
}
|
|
281
281
|
exports.endpointFromFunction = endpointFromFunction;
|
|
282
282
|
function functionFromEndpoint(endpoint, sourceUploadUrl) {
|
|
283
|
+
var _a;
|
|
283
284
|
if (endpoint.platform !== "gcfv1") {
|
|
284
285
|
throw new error_1.FirebaseError("Trying to create a v1 CloudFunction with v2 API. This should never happen");
|
|
285
286
|
}
|
|
@@ -334,7 +335,13 @@ function functionFromEndpoint(endpoint, sourceUploadUrl) {
|
|
|
334
335
|
proto.renameIfPresent(gcfFunction, endpoint.vpc, "vpcConnector", "connector");
|
|
335
336
|
proto.renameIfPresent(gcfFunction, endpoint.vpc, "vpcConnectorEgressSettings", "egressSettings");
|
|
336
337
|
}
|
|
337
|
-
|
|
338
|
+
const codebase = endpoint.codebase || projectConfig.DEFAULT_CODEBASE;
|
|
339
|
+
if (codebase !== projectConfig.DEFAULT_CODEBASE) {
|
|
340
|
+
gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [exports.CODEBASE_LABEL]: codebase });
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
(_a = gcfFunction.labels) === null || _a === void 0 ? true : delete _a[exports.CODEBASE_LABEL];
|
|
344
|
+
}
|
|
338
345
|
return gcfFunction;
|
|
339
346
|
}
|
|
340
347
|
exports.functionFromEndpoint = functionFromEndpoint;
|
|
@@ -157,6 +157,7 @@ async function deleteFunction(cloudFunction) {
|
|
|
157
157
|
}
|
|
158
158
|
exports.deleteFunction = deleteFunction;
|
|
159
159
|
function functionFromEndpoint(endpoint, source) {
|
|
160
|
+
var _a;
|
|
160
161
|
if (endpoint.platform !== "gcfv2") {
|
|
161
162
|
throw new error_1.FirebaseError("Trying to create a v2 CloudFunction with v1 API. This should never happen");
|
|
162
163
|
}
|
|
@@ -223,7 +224,13 @@ function functionFromEndpoint(endpoint, source) {
|
|
|
223
224
|
else if (backend.isBlockingTriggered(endpoint)) {
|
|
224
225
|
gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [exports.BLOCKING_LABEL]: BLOCKING_EVENT_TO_LABEL_KEY[endpoint.blockingTrigger.eventType] });
|
|
225
226
|
}
|
|
226
|
-
|
|
227
|
+
const codebase = endpoint.codebase || projectConfig.DEFAULT_CODEBASE;
|
|
228
|
+
if (codebase !== projectConfig.DEFAULT_CODEBASE) {
|
|
229
|
+
gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [exports.CODEBASE_LABEL]: codebase });
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
(_a = gcfFunction.labels) === null || _a === void 0 ? true : delete _a[exports.CODEBASE_LABEL];
|
|
233
|
+
}
|
|
227
234
|
return gcfFunction;
|
|
228
235
|
}
|
|
229
236
|
exports.functionFromEndpoint = functionFromEndpoint;
|
package/lib/serve/functions.js
CHANGED
|
@@ -10,28 +10,29 @@ const auth_1 = require("../auth");
|
|
|
10
10
|
const projectConfig = require("../functions/projectConfig");
|
|
11
11
|
const utils = require("../utils");
|
|
12
12
|
class FunctionsServer {
|
|
13
|
-
constructor() {
|
|
14
|
-
this.emulatorServer = undefined;
|
|
15
|
-
this.backend = undefined;
|
|
16
|
-
}
|
|
17
13
|
assertServer() {
|
|
18
|
-
if (!this.emulatorServer || !this.
|
|
14
|
+
if (!this.emulatorServer || !this.backends) {
|
|
19
15
|
throw new Error("Must call start() before calling any other operation!");
|
|
20
16
|
}
|
|
21
17
|
}
|
|
22
18
|
async start(options, partialArgs) {
|
|
23
19
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
24
|
-
const config = projectConfig.normalizeAndValidate(options.config.src.functions)
|
|
25
|
-
const
|
|
20
|
+
const config = projectConfig.normalizeAndValidate(options.config.src.functions);
|
|
21
|
+
const backends = [];
|
|
22
|
+
for (const cfg of config) {
|
|
23
|
+
const functionsDir = path.join(options.config.projectDir, cfg.source);
|
|
24
|
+
const nodeMajorVersion = (0, functionsEmulatorUtils_1.parseRuntimeVersion)(cfg.runtime);
|
|
25
|
+
backends.push({
|
|
26
|
+
functionsDir,
|
|
27
|
+
codebase: cfg.codebase,
|
|
28
|
+
nodeMajorVersion,
|
|
29
|
+
env: {},
|
|
30
|
+
secretEnv: [],
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
this.backends = backends;
|
|
26
34
|
const account = (0, auth_1.getProjectDefaultAccount)(options.config.projectDir);
|
|
27
|
-
const
|
|
28
|
-
this.backend = {
|
|
29
|
-
functionsDir,
|
|
30
|
-
nodeMajorVersion,
|
|
31
|
-
env: {},
|
|
32
|
-
secretEnv: [],
|
|
33
|
-
};
|
|
34
|
-
const args = Object.assign({ projectId, projectDir: options.config.projectDir, emulatableBackends: [this.backend], projectAlias: options.projectAlias, account }, partialArgs);
|
|
35
|
+
const args = Object.assign({ projectId, projectDir: options.config.projectDir, emulatableBackends: this.backends, projectAlias: options.projectAlias, account }, partialArgs);
|
|
35
36
|
if (options.host) {
|
|
36
37
|
utils.assertIsStringOrUndefined(options.host);
|
|
37
38
|
args.host = options.host;
|
|
@@ -59,10 +60,6 @@ class FunctionsServer {
|
|
|
59
60
|
this.assertServer();
|
|
60
61
|
await this.emulatorServer.stop();
|
|
61
62
|
}
|
|
62
|
-
getBackend() {
|
|
63
|
-
this.assertServer();
|
|
64
|
-
return this.backend;
|
|
65
|
-
}
|
|
66
63
|
get() {
|
|
67
64
|
this.assertServer();
|
|
68
65
|
return this.emulatorServer.get();
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firebase-tools",
|
|
3
|
-
"version": "10.7.
|
|
3
|
+
"version": "10.7.1",
|
|
4
4
|
"lockfileVersion": 2,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "firebase-tools",
|
|
9
|
-
"version": "10.7.
|
|
9
|
+
"version": "10.7.1",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@google-cloud/pubsub": "^2.18.4",
|