firebase-tools 9.20.0 → 9.23.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/CHANGELOG.md +5 -1
- package/lib/api.js +2 -0
- package/lib/apiv2.js +7 -4
- package/lib/commands/crashlytics-symbols-upload.js +2 -2
- package/lib/commands/deploy.js +9 -1
- package/lib/commands/ext-configure.js +1 -1
- package/lib/commands/ext-dev-deprecate.js +63 -0
- package/lib/commands/ext-dev-undeprecate.js +56 -0
- package/lib/commands/ext-dev-unpublish.js +10 -3
- package/lib/commands/ext-export.js +44 -0
- package/lib/commands/ext-install.js +1 -1
- package/lib/commands/ext-update.js +1 -1
- package/lib/commands/functions-delete.js +55 -42
- package/lib/commands/functions-list.js +11 -11
- package/lib/commands/index.js +6 -5
- package/lib/commands/init.js +3 -0
- package/lib/config.js +3 -2
- package/lib/deploy/extensions/args.js +2 -0
- package/lib/deploy/extensions/deploy.js +49 -0
- package/lib/deploy/extensions/deploymentSummary.js +52 -0
- package/lib/deploy/extensions/errors.js +31 -0
- package/lib/deploy/extensions/index.js +8 -0
- package/lib/deploy/extensions/params.js +39 -0
- package/lib/deploy/extensions/planner.js +94 -0
- package/lib/deploy/extensions/prepare.js +111 -0
- package/lib/deploy/extensions/release.js +43 -0
- package/lib/deploy/extensions/secrets.js +150 -0
- package/lib/deploy/extensions/tasks.js +98 -0
- package/lib/deploy/extensions/validate.js +17 -0
- package/lib/deploy/functions/backend.js +84 -115
- package/lib/deploy/functions/checkIam.js +73 -12
- package/lib/deploy/functions/containerCleaner.js +97 -50
- package/lib/deploy/functions/deploy.js +4 -10
- package/lib/deploy/functions/eventTypes.js +10 -0
- package/lib/deploy/functions/functionsDeployHelper.js +3 -68
- package/lib/deploy/functions/prepare.js +72 -29
- package/lib/deploy/functions/pricing.js +17 -17
- package/lib/deploy/functions/prompts.js +22 -21
- package/lib/deploy/functions/release/executor.js +39 -0
- package/lib/deploy/functions/release/fabricator.js +425 -0
- package/lib/deploy/functions/release/index.js +73 -0
- package/lib/deploy/functions/release/planner.js +162 -0
- package/lib/deploy/functions/release/reporter.js +165 -0
- package/lib/deploy/functions/release/sourceTokenScraper.js +28 -0
- package/lib/deploy/functions/release/timer.js +14 -0
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +129 -126
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +32 -54
- package/lib/deploy/functions/services/index.js +38 -0
- package/lib/deploy/functions/services/storage.js +43 -0
- package/lib/deploy/functions/triggerRegionHelper.js +9 -25
- package/lib/deploy/functions/validate.js +1 -24
- package/lib/deploy/index.js +10 -1
- package/lib/emulator/auth/apiSpec.js +37 -6
- package/lib/emulator/auth/operations.js +45 -17
- package/lib/emulator/auth/server.js +16 -2
- package/lib/emulator/auth/state.js +34 -15
- package/lib/emulator/auth/widget_ui.js +14 -0
- package/lib/emulator/downloadableEmulators.js +7 -7
- package/lib/emulator/functionsEmulator.js +18 -4
- package/lib/emulator/storage/cloudFunctions.js +37 -7
- package/lib/ensureApiEnabled.js +10 -12
- package/lib/extensions/askUserForParam.js +14 -6
- package/lib/extensions/checkProjectBilling.js +7 -7
- package/lib/extensions/export.js +107 -0
- package/lib/extensions/extensionsApi.js +103 -21
- package/lib/extensions/extensionsHelper.js +5 -2
- package/lib/extensions/listExtensions.js +16 -11
- package/lib/extensions/paramHelper.js +6 -4
- package/lib/extensions/provisioningHelper.js +16 -3
- package/lib/extensions/refs.js +9 -1
- package/lib/extensions/secretsUtils.js +10 -9
- package/lib/extensions/updateHelper.js +12 -2
- package/lib/extensions/versionHelper.js +14 -0
- package/lib/extensions/warnings.js +33 -1
- package/lib/gcp/artifactregistry.js +16 -0
- package/lib/gcp/cloudfunctions.js +25 -72
- package/lib/gcp/cloudfunctionsv2.js +46 -98
- package/lib/gcp/cloudscheduler.js +22 -16
- package/lib/gcp/cloudtasks.js +143 -0
- package/lib/gcp/docker.js +36 -2
- package/lib/gcp/location.js +44 -0
- package/lib/gcp/proto.js +2 -2
- package/lib/gcp/pubsub.js +1 -9
- package/lib/gcp/secretManager.js +27 -6
- package/lib/gcp/storage.js +48 -32
- package/lib/init/features/project.js +2 -1
- package/lib/previews.js +1 -1
- package/lib/projectUtils.js +10 -1
- package/lib/utils.js +30 -1
- package/package.json +5 -4
- package/schema/firebase-config.json +9 -0
- package/lib/deploy/functions/deploymentPlanner.js +0 -113
- package/lib/deploy/functions/deploymentTimer.js +0 -23
- package/lib/deploy/functions/errorHandler.js +0 -75
- package/lib/deploy/functions/release.js +0 -116
- package/lib/deploy/functions/tasks.js +0 -324
- package/lib/functions/listFunctions.js +0 -10
- package/lib/functionsDelete.js +0 -60
|
@@ -123,10 +123,22 @@ class FunctionsEmulator {
|
|
|
123
123
|
});
|
|
124
124
|
};
|
|
125
125
|
const multicastHandler = (req, res) => {
|
|
126
|
-
|
|
127
|
-
const proto = JSON.parse(reqBody.toString());
|
|
128
|
-
const triggers = this.multicastTriggers[`${this.args.projectId}:${proto.eventType}`] || [];
|
|
126
|
+
var _a;
|
|
129
127
|
const projectId = req.params.project_id;
|
|
128
|
+
const reqBody = req.rawBody;
|
|
129
|
+
let proto = JSON.parse(reqBody.toString());
|
|
130
|
+
let triggerKey;
|
|
131
|
+
if ((_a = req.headers["content-type"]) === null || _a === void 0 ? void 0 : _a.includes("cloudevent")) {
|
|
132
|
+
triggerKey = `${this.args.projectId}:${proto.type}`;
|
|
133
|
+
if (types_2.EventUtils.isBinaryCloudEvent(req)) {
|
|
134
|
+
proto = types_2.EventUtils.extractBinaryCloudEventContext(req);
|
|
135
|
+
proto.data = req.body;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
triggerKey = `${this.args.projectId}:${proto.eventType}`;
|
|
140
|
+
}
|
|
141
|
+
const triggers = this.multicastTriggers[triggerKey] || [];
|
|
130
142
|
triggers.forEach((triggerId) => {
|
|
131
143
|
this.workQueue.submit(() => {
|
|
132
144
|
this.logger.log("DEBUG", `Accepted multicast request ${req.method} ${req.url} --> ${triggerId}`);
|
|
@@ -514,6 +526,8 @@ class FunctionsEmulator {
|
|
|
514
526
|
const envs = {};
|
|
515
527
|
envs.FUNCTIONS_EMULATOR = "true";
|
|
516
528
|
envs.TZ = "UTC";
|
|
529
|
+
envs.FIREBASE_DEBUG_MODE = "true";
|
|
530
|
+
envs.FIREBASE_DEBUG_FEATURES = JSON.stringify({ skipTokenVerification: true });
|
|
517
531
|
const firestoreEmulator = this.getEmulatorInfo(types_1.Emulators.FIRESTORE);
|
|
518
532
|
if (firestoreEmulator != null) {
|
|
519
533
|
envs[constants_1.Constants.FIRESTORE_EMULATOR_HOST] = functionsEmulatorShared_1.formatHost(firestoreEmulator);
|
|
@@ -718,7 +732,7 @@ class FunctionsEmulator {
|
|
|
718
732
|
const reqBody = req.rawBody;
|
|
719
733
|
const isCallable = trigger.labels && trigger.labels["deployment-callable"] === "true";
|
|
720
734
|
const authHeader = req.header("Authorization");
|
|
721
|
-
if (authHeader && isCallable) {
|
|
735
|
+
if (authHeader && isCallable && trigger.platform !== "gcfv2") {
|
|
722
736
|
const token = this.tokenFromAuthHeader(authHeader);
|
|
723
737
|
if (token) {
|
|
724
738
|
const contextAuth = {
|
|
@@ -6,6 +6,12 @@ const types_1 = require("../types");
|
|
|
6
6
|
const emulatorLogger_1 = require("../emulatorLogger");
|
|
7
7
|
const metadata_1 = require("./metadata");
|
|
8
8
|
const apiv2_1 = require("../../apiv2");
|
|
9
|
+
const STORAGE_V2_ACTION_MAP = {
|
|
10
|
+
finalize: "finalized",
|
|
11
|
+
metadataUpdate: "metadataUpdated",
|
|
12
|
+
delete: "deleted",
|
|
13
|
+
archive: "archived",
|
|
14
|
+
};
|
|
9
15
|
class StorageCloudFunctions {
|
|
10
16
|
constructor(projectId) {
|
|
11
17
|
this.projectId = projectId;
|
|
@@ -19,26 +25,37 @@ class StorageCloudFunctions {
|
|
|
19
25
|
this.functionsEmulatorInfo = functionsEmulator.getInfo();
|
|
20
26
|
this.multicastOrigin = `http://${registry_1.EmulatorRegistry.getInfoHostString(this.functionsEmulatorInfo)}`;
|
|
21
27
|
this.multicastPath = `/functions/projects/${projectId}/trigger_multicast`;
|
|
28
|
+
this.client = new apiv2_1.Client({ urlPrefix: this.multicastOrigin, auth: false });
|
|
22
29
|
}
|
|
23
30
|
}
|
|
24
31
|
async dispatch(action, object) {
|
|
25
|
-
if (!this.enabled)
|
|
32
|
+
if (!this.enabled) {
|
|
26
33
|
return;
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
let res;
|
|
34
|
+
}
|
|
35
|
+
const errStatus = [];
|
|
30
36
|
let err;
|
|
31
37
|
try {
|
|
32
|
-
|
|
38
|
+
const eventBody = this.createLegacyEventRequestBody(action, object);
|
|
39
|
+
const eventRes = await this.client.post(this.multicastPath, eventBody);
|
|
40
|
+
if (eventRes.status !== 200) {
|
|
41
|
+
errStatus.push(eventRes.status);
|
|
42
|
+
}
|
|
43
|
+
const cloudEventBody = this.createCloudEventRequestBody(action, object);
|
|
44
|
+
const cloudEventRes = await this.client.post(this.multicastPath, cloudEventBody, {
|
|
45
|
+
headers: { "Content-Type": "application/cloudevents+json; charset=UTF-8" },
|
|
46
|
+
});
|
|
47
|
+
if (cloudEventRes.status !== 200) {
|
|
48
|
+
errStatus.push(cloudEventRes.status);
|
|
49
|
+
}
|
|
33
50
|
}
|
|
34
51
|
catch (e) {
|
|
35
52
|
err = e;
|
|
36
53
|
}
|
|
37
|
-
if (err ||
|
|
54
|
+
if (err || errStatus.length > 0) {
|
|
38
55
|
this.logger.logLabeled("WARN", "functions", `Firebase Storage function was not triggered due to emulation error. Please file a bug.`);
|
|
39
56
|
}
|
|
40
57
|
}
|
|
41
|
-
|
|
58
|
+
createLegacyEventRequestBody(action, objectMetadataPayload) {
|
|
42
59
|
const timestamp = new Date();
|
|
43
60
|
return JSON.stringify({
|
|
44
61
|
eventId: `${timestamp.getTime()}`,
|
|
@@ -52,5 +69,18 @@ class StorageCloudFunctions {
|
|
|
52
69
|
data: objectMetadataPayload,
|
|
53
70
|
});
|
|
54
71
|
}
|
|
72
|
+
createCloudEventRequestBody(action, objectMetadataPayload) {
|
|
73
|
+
const ceAction = STORAGE_V2_ACTION_MAP[action];
|
|
74
|
+
if (!ceAction) {
|
|
75
|
+
throw new Error("Action is not definied as a CloudEvents action");
|
|
76
|
+
}
|
|
77
|
+
const data = objectMetadataPayload;
|
|
78
|
+
return JSON.stringify({
|
|
79
|
+
specVersion: 1,
|
|
80
|
+
type: `google.cloud.storage.object.v1.${ceAction}`,
|
|
81
|
+
source: `//storage.googleapis.com/projects/_/buckets/${objectMetadataPayload.bucket}/objects/${objectMetadataPayload.name}`,
|
|
82
|
+
data,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
55
85
|
}
|
|
56
86
|
exports.StorageCloudFunctions = StorageCloudFunctions;
|
package/lib/ensureApiEnabled.js
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ensure = exports.enable = exports.check = exports.POLL_SETTINGS = void 0;
|
|
4
|
-
const _ = require("lodash");
|
|
5
4
|
const cli_color_1 = require("cli-color");
|
|
6
5
|
const track = require("./track");
|
|
7
|
-
const
|
|
6
|
+
const api_1 = require("./api");
|
|
7
|
+
const apiv2_1 = require("./apiv2");
|
|
8
8
|
const utils = require("./utils");
|
|
9
9
|
const error_1 = require("./error");
|
|
10
10
|
exports.POLL_SETTINGS = {
|
|
11
11
|
pollInterval: 10000,
|
|
12
12
|
pollsBeforeRetry: 12,
|
|
13
13
|
};
|
|
14
|
+
const apiClient = new apiv2_1.Client({
|
|
15
|
+
urlPrefix: api_1.serviceUsageOrigin,
|
|
16
|
+
apiVersion: "v1",
|
|
17
|
+
});
|
|
14
18
|
async function check(projectId, apiName, prefix, silent = false) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
origin: api.serviceUsageOrigin,
|
|
18
|
-
});
|
|
19
|
-
const isEnabled = _.get(response.body, "state") === "ENABLED";
|
|
19
|
+
const res = await apiClient.get(`/projects/${projectId}/services/${apiName}`);
|
|
20
|
+
const isEnabled = res.body.state === "ENABLED";
|
|
20
21
|
if (isEnabled && !silent) {
|
|
21
22
|
utils.logLabeledSuccess(prefix, `required API ${cli_color_1.bold(apiName)} is enabled`);
|
|
22
23
|
}
|
|
@@ -25,10 +26,7 @@ async function check(projectId, apiName, prefix, silent = false) {
|
|
|
25
26
|
exports.check = check;
|
|
26
27
|
async function enable(projectId, apiName) {
|
|
27
28
|
try {
|
|
28
|
-
await
|
|
29
|
-
auth: true,
|
|
30
|
-
origin: api.serviceUsageOrigin,
|
|
31
|
-
});
|
|
29
|
+
await apiClient.post(`/projects/${projectId}/services/${apiName}:enable`);
|
|
32
30
|
}
|
|
33
31
|
catch (err) {
|
|
34
32
|
if (error_1.isBillingError(err)) {
|
|
@@ -49,7 +47,7 @@ async function pollCheckEnabled(projectId, apiName, prefix, silent, enablementRe
|
|
|
49
47
|
});
|
|
50
48
|
const isEnabled = await check(projectId, apiName, prefix, silent);
|
|
51
49
|
if (isEnabled) {
|
|
52
|
-
track("api_enabled", apiName);
|
|
50
|
+
void track("api_enabled", apiName);
|
|
53
51
|
return;
|
|
54
52
|
}
|
|
55
53
|
if (!silent) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ask = exports.getInquirerDefault = exports.askForParam = exports.checkResponse = void 0;
|
|
3
|
+
exports.ask = exports.getInquirerDefault = exports.promptCreateSecret = exports.askForParam = exports.checkResponse = void 0;
|
|
4
4
|
const _ = require("lodash");
|
|
5
5
|
const clc = require("cli-color");
|
|
6
6
|
const marked = require("marked");
|
|
@@ -148,17 +148,25 @@ async function promptReconfigureSecret(projectId, instanceId, paramSpec) {
|
|
|
148
148
|
return paramSpec.default || "";
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
-
async function promptCreateSecret(projectId, instanceId, paramSpec) {
|
|
152
|
-
const
|
|
151
|
+
async function promptCreateSecret(projectId, instanceId, paramSpec, secretName) {
|
|
152
|
+
const name = secretName !== null && secretName !== void 0 ? secretName : (await generateSecretName(projectId, instanceId, paramSpec.param));
|
|
153
153
|
const secretValue = await prompt_1.promptOnce({
|
|
154
154
|
name: paramSpec.param,
|
|
155
155
|
type: "password",
|
|
156
156
|
default: paramSpec.default,
|
|
157
|
-
message: `This secret will be stored in Cloud Secret Manager (https://cloud.google.com/secret-manager/pricing) as ${
|
|
157
|
+
message: `This secret will be stored in Cloud Secret Manager (https://cloud.google.com/secret-manager/pricing) as ${name} and managed by Firebase Extensions (Firebase Extensions Service Agent will be granted Secret Admin role on this secret).\nEnter a value for ${paramSpec.label.trim()}:`,
|
|
158
158
|
});
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
if (secretValue === "" && paramSpec.required) {
|
|
160
|
+
logger_1.logger.info(`Secret value cannot be empty for required param ${paramSpec.param}`);
|
|
161
|
+
return await promptCreateSecret(projectId, instanceId, paramSpec, name);
|
|
162
|
+
}
|
|
163
|
+
else if (secretValue !== "") {
|
|
164
|
+
const secret = await secretManagerApi.createSecret(projectId, name, secretsUtils.getSecretLabels(instanceId));
|
|
165
|
+
return addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue);
|
|
166
|
+
}
|
|
167
|
+
return secretValue;
|
|
161
168
|
}
|
|
169
|
+
exports.promptCreateSecret = promptCreateSecret;
|
|
162
170
|
async function generateSecretName(projectId, instanceId, paramName) {
|
|
163
171
|
let secretName = `ext-${instanceId}-${paramName}`;
|
|
164
172
|
while (await secretManagerApi.secretExists(projectId, secretName)) {
|
|
@@ -33,13 +33,13 @@ async function openBillingAccount(projectId, url, open) {
|
|
|
33
33
|
});
|
|
34
34
|
return cloudbilling.checkBillingEnabled(projectId);
|
|
35
35
|
}
|
|
36
|
-
async function chooseBillingAccount(projectId,
|
|
36
|
+
async function chooseBillingAccount(projectId, accounts) {
|
|
37
37
|
const choices = accounts.map((m) => m.displayName);
|
|
38
38
|
choices.push(ADD_BILLING_ACCOUNT);
|
|
39
39
|
const answer = await prompt.promptOnce({
|
|
40
40
|
name: "billing",
|
|
41
41
|
type: "list",
|
|
42
|
-
message: `
|
|
42
|
+
message: `Extensions require your project to be upgraded to the Blaze plan. You have access to the following billing accounts.
|
|
43
43
|
Please select the one that you would like to associate with this project:`,
|
|
44
44
|
choices: choices,
|
|
45
45
|
});
|
|
@@ -54,10 +54,10 @@ Please select the one that you would like to associate with this project:`,
|
|
|
54
54
|
}
|
|
55
55
|
return logBillingStatus(billingEnabled, projectId);
|
|
56
56
|
}
|
|
57
|
-
async function setUpBillingAccount(projectId
|
|
57
|
+
async function setUpBillingAccount(projectId) {
|
|
58
58
|
const billingURL = `https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
|
|
59
59
|
logger_1.logger.info();
|
|
60
|
-
logger_1.logger.info(`
|
|
60
|
+
logger_1.logger.info(`Extension require your project to be upgraded to the Blaze plan. Please visit the following link to add a billing account:`);
|
|
61
61
|
logger_1.logger.info();
|
|
62
62
|
logger_1.logger.info(clc.bold.underline(billingURL));
|
|
63
63
|
logger_1.logger.info();
|
|
@@ -70,13 +70,13 @@ async function setUpBillingAccount(projectId, extensionName) {
|
|
|
70
70
|
const billingEnabled = await openBillingAccount(projectId, billingURL, open);
|
|
71
71
|
return logBillingStatus(billingEnabled, projectId);
|
|
72
72
|
}
|
|
73
|
-
async function enableBilling(projectId
|
|
73
|
+
async function enableBilling(projectId) {
|
|
74
74
|
const billingAccounts = await cloudbilling.listBillingAccounts();
|
|
75
75
|
if (billingAccounts) {
|
|
76
76
|
const accounts = billingAccounts.filter((account) => account.open);
|
|
77
77
|
return accounts.length > 0
|
|
78
|
-
? chooseBillingAccount(projectId,
|
|
79
|
-
: setUpBillingAccount(projectId
|
|
78
|
+
? chooseBillingAccount(projectId, accounts)
|
|
79
|
+
: setUpBillingAccount(projectId);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
exports.enableBilling = enableBilling;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeFiles = exports.displayExportInfo = exports.setSecretParamsToLatest = exports.parameterizeProject = void 0;
|
|
4
|
+
const clc = require("cli-color");
|
|
5
|
+
const refs = require("./refs");
|
|
6
|
+
const config_1 = require("../config");
|
|
7
|
+
const planner_1 = require("../deploy/extensions/planner");
|
|
8
|
+
const deploymentSummary_1 = require("../deploy/extensions/deploymentSummary");
|
|
9
|
+
const logger_1 = require("../logger");
|
|
10
|
+
const error_1 = require("../error");
|
|
11
|
+
const prompt_1 = require("../prompt");
|
|
12
|
+
const secretManager_1 = require("../gcp/secretManager");
|
|
13
|
+
const secretsUtils_1 = require("./secretsUtils");
|
|
14
|
+
function parameterizeProject(projectId, projectNumber, spec) {
|
|
15
|
+
const newParams = {};
|
|
16
|
+
for (const [key, val] of Object.entries(spec.params)) {
|
|
17
|
+
const p1 = val.replace(projectId, "${param:PROJECT_ID}");
|
|
18
|
+
const p2 = p1.replace(projectNumber, "${param:PROJECT_NUMBER}");
|
|
19
|
+
newParams[key] = p2;
|
|
20
|
+
}
|
|
21
|
+
const newSpec = Object.assign({}, spec);
|
|
22
|
+
newSpec.params = newParams;
|
|
23
|
+
return newSpec;
|
|
24
|
+
}
|
|
25
|
+
exports.parameterizeProject = parameterizeProject;
|
|
26
|
+
async function setSecretParamsToLatest(spec) {
|
|
27
|
+
const newParams = Object.assign({}, spec.params);
|
|
28
|
+
const extensionVersion = await planner_1.getExtensionVersion(spec);
|
|
29
|
+
const activeSecrets = secretsUtils_1.getActiveSecrets(extensionVersion.spec, newParams);
|
|
30
|
+
for (const [key, val] of Object.entries(newParams)) {
|
|
31
|
+
if (activeSecrets.includes(val)) {
|
|
32
|
+
const parsed = secretManager_1.parseSecretVersionResourceName(val);
|
|
33
|
+
parsed.versionId = "latest";
|
|
34
|
+
newParams[key] = secretManager_1.toSecretVersionResourceName(parsed);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return Object.assign(Object.assign({}, spec), { params: newParams });
|
|
38
|
+
}
|
|
39
|
+
exports.setSecretParamsToLatest = setSecretParamsToLatest;
|
|
40
|
+
function displayExportInfo(withRef, withoutRef) {
|
|
41
|
+
logger_1.logger.info("The following Extension instances will be saved locally:");
|
|
42
|
+
logger_1.logger.info("");
|
|
43
|
+
displaySpecs(withRef);
|
|
44
|
+
if (withoutRef.length) {
|
|
45
|
+
logger_1.logger.info(`Your project also has the following instances installed from local sources. These will not be saved to firebase.json:`);
|
|
46
|
+
for (const spec of withoutRef) {
|
|
47
|
+
logger_1.logger.info(spec.instanceId);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.displayExportInfo = displayExportInfo;
|
|
52
|
+
function displaySpecs(specs) {
|
|
53
|
+
for (let i = 0; i < specs.length; i++) {
|
|
54
|
+
const spec = specs[i];
|
|
55
|
+
logger_1.logger.info(`${i + 1}. ${deploymentSummary_1.humanReadable(spec)}`);
|
|
56
|
+
logger_1.logger.info(`Configuration will be written to 'extensions/${spec.instanceId}.env'`);
|
|
57
|
+
for (const p of Object.entries(spec.params)) {
|
|
58
|
+
logger_1.logger.info(`\t${p[0]}=${p[1]}`);
|
|
59
|
+
}
|
|
60
|
+
logger_1.logger.info("");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function writeExtensionsToFirebaseJson(have, existingConfig) {
|
|
64
|
+
const extensions = existingConfig.get("extensions", {});
|
|
65
|
+
for (const s of have) {
|
|
66
|
+
extensions[s.instanceId] = refs.toExtensionVersionRef(s.ref);
|
|
67
|
+
}
|
|
68
|
+
existingConfig.set("extensions", extensions);
|
|
69
|
+
logger_1.logger.info("Adding Extensions to " + clc.bold("firebase.json") + "...");
|
|
70
|
+
existingConfig.writeProjectFile("firebase.json", existingConfig.src);
|
|
71
|
+
}
|
|
72
|
+
async function writeEnvFile(spec, existingConfig, force) {
|
|
73
|
+
const content = Object.entries(spec.params)
|
|
74
|
+
.map((r) => `${r[0]}=${r[1]}`)
|
|
75
|
+
.join("\n");
|
|
76
|
+
await existingConfig.askWriteProjectFile(`extensions/${spec.instanceId}.env`, content, force);
|
|
77
|
+
}
|
|
78
|
+
async function writeFiles(have, options) {
|
|
79
|
+
const existingConfig = config_1.Config.load(options, true);
|
|
80
|
+
if (!existingConfig) {
|
|
81
|
+
throw new error_1.FirebaseError("Not currently in a Firebase directory. Please run `firebase init` to create a Firebase directory.");
|
|
82
|
+
}
|
|
83
|
+
if (existingConfig.has("extensions") &&
|
|
84
|
+
Object.keys(existingConfig.get("extensions")).length &&
|
|
85
|
+
!options.nonInteractive &&
|
|
86
|
+
!options.force) {
|
|
87
|
+
const currentExtensions = Object.entries(existingConfig.get("extensions"))
|
|
88
|
+
.map((i) => `${i[0]}: ${i[1]}`)
|
|
89
|
+
.join("\n\t");
|
|
90
|
+
const overwrite = await prompt_1.promptOnce({
|
|
91
|
+
type: "list",
|
|
92
|
+
message: `firebase.json already contains extensions:\n${currentExtensions}\nWould you like to overwrite or merge?`,
|
|
93
|
+
choices: [
|
|
94
|
+
{ name: "Overwrite", value: true },
|
|
95
|
+
{ name: "Merge", value: false },
|
|
96
|
+
],
|
|
97
|
+
});
|
|
98
|
+
if (overwrite) {
|
|
99
|
+
existingConfig.set("extensions", {});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
writeExtensionsToFirebaseJson(have, existingConfig);
|
|
103
|
+
for (const spec of have) {
|
|
104
|
+
await writeEnvFile(spec, existingConfig, options.force);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
exports.writeFiles = writeFiles;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getExtension = exports.deleteExtension = exports.unpublishExtension = exports.publishExtensionVersion = exports.registerPublisherProfile = exports.listExtensionVersions = exports.listExtensions = exports.getExtensionVersion = exports.getSource = exports.createSource = exports.updateInstanceFromRegistry = exports.updateInstance = exports.configureInstance = exports.listInstances = exports.getInstance = exports.deleteInstance = exports.createInstance = exports.ParamType = exports.Visibility = exports.RegistryLaunchStage = void 0;
|
|
3
|
+
exports.getExtension = exports.deleteExtension = exports.unpublishExtension = exports.publishExtensionVersion = exports.undeprecateExtensionVersion = exports.deprecateExtensionVersion = exports.registerPublisherProfile = exports.listExtensionVersions = exports.listExtensions = exports.getExtensionVersion = exports.getSource = exports.createSource = exports.updateInstanceFromRegistry = exports.updateInstance = exports.configureInstance = exports.listInstances = exports.getInstance = exports.deleteInstance = exports.createInstance = exports.ParamType = exports.Visibility = exports.RegistryLaunchStage = void 0;
|
|
4
4
|
const yaml = require("js-yaml");
|
|
5
5
|
const _ = require("lodash");
|
|
6
6
|
const clc = require("cli-color");
|
|
@@ -32,7 +32,7 @@ var ParamType;
|
|
|
32
32
|
ParamType["MULTISELECT"] = "MULTISELECT";
|
|
33
33
|
ParamType["SECRET"] = "SECRET";
|
|
34
34
|
})(ParamType = exports.ParamType || (exports.ParamType = {}));
|
|
35
|
-
async function createInstanceHelper(projectId, instanceId, config) {
|
|
35
|
+
async function createInstanceHelper(projectId, instanceId, config, validateOnly = false) {
|
|
36
36
|
const createRes = await api.request("POST", `/${VERSION}/projects/${projectId}/instances/`, {
|
|
37
37
|
auth: true,
|
|
38
38
|
origin: api.extensionsOrigin,
|
|
@@ -40,7 +40,13 @@ async function createInstanceHelper(projectId, instanceId, config) {
|
|
|
40
40
|
name: `projects/${projectId}/instances/${instanceId}`,
|
|
41
41
|
config: config,
|
|
42
42
|
},
|
|
43
|
+
query: {
|
|
44
|
+
validateOnly,
|
|
45
|
+
},
|
|
43
46
|
});
|
|
47
|
+
if (validateOnly) {
|
|
48
|
+
return createRes;
|
|
49
|
+
}
|
|
44
50
|
const pollRes = await operationPoller.pollOperation({
|
|
45
51
|
apiOrigin: api.extensionsOrigin,
|
|
46
52
|
apiVersion: VERSION,
|
|
@@ -68,7 +74,7 @@ async function createInstance(args) {
|
|
|
68
74
|
else {
|
|
69
75
|
throw new error_1.FirebaseError("No ExtensionVersion or ExtensionSource provided but one is required.");
|
|
70
76
|
}
|
|
71
|
-
return createInstanceHelper(args.projectId, args.instanceId, config);
|
|
77
|
+
return createInstanceHelper(args.projectId, args.instanceId, config, args.validateOnly);
|
|
72
78
|
}
|
|
73
79
|
exports.createInstance = createInstance;
|
|
74
80
|
async function deleteInstance(projectId, instanceId) {
|
|
@@ -115,31 +121,46 @@ async function listInstances(projectId) {
|
|
|
115
121
|
return instances;
|
|
116
122
|
}
|
|
117
123
|
exports.listInstances = listInstances;
|
|
118
|
-
async function configureInstance(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
124
|
+
async function configureInstance(args) {
|
|
125
|
+
var _a;
|
|
126
|
+
const res = await patchInstance({
|
|
127
|
+
projectId: args.projectId,
|
|
128
|
+
instanceId: args.instanceId,
|
|
129
|
+
updateMask: "config.params",
|
|
130
|
+
validateOnly: (_a = args.validateOnly) !== null && _a !== void 0 ? _a : false,
|
|
131
|
+
data: {
|
|
132
|
+
config: {
|
|
133
|
+
params: args.params,
|
|
134
|
+
},
|
|
122
135
|
},
|
|
123
136
|
});
|
|
124
137
|
return res;
|
|
125
138
|
}
|
|
126
139
|
exports.configureInstance = configureInstance;
|
|
127
|
-
async function updateInstance(
|
|
140
|
+
async function updateInstance(args) {
|
|
141
|
+
var _a;
|
|
128
142
|
const body = {
|
|
129
143
|
config: {
|
|
130
|
-
source: { name: extensionSource.name },
|
|
144
|
+
source: { name: args.extensionSource.name },
|
|
131
145
|
},
|
|
132
146
|
};
|
|
133
147
|
let updateMask = "config.source.name";
|
|
134
|
-
if (params) {
|
|
135
|
-
body.config.params = params;
|
|
148
|
+
if (args.params) {
|
|
149
|
+
body.config.params = args.params;
|
|
136
150
|
updateMask += ",config.params";
|
|
137
151
|
}
|
|
138
|
-
return await patchInstance(
|
|
152
|
+
return await patchInstance({
|
|
153
|
+
projectId: args.projectId,
|
|
154
|
+
instanceId: args.instanceId,
|
|
155
|
+
updateMask,
|
|
156
|
+
validateOnly: (_a = args.validateOnly) !== null && _a !== void 0 ? _a : false,
|
|
157
|
+
data: body,
|
|
158
|
+
});
|
|
139
159
|
}
|
|
140
160
|
exports.updateInstance = updateInstance;
|
|
141
|
-
async function updateInstanceFromRegistry(
|
|
142
|
-
|
|
161
|
+
async function updateInstanceFromRegistry(args) {
|
|
162
|
+
var _a;
|
|
163
|
+
const ref = refs.parse(args.extRef);
|
|
143
164
|
const body = {
|
|
144
165
|
config: {
|
|
145
166
|
extensionRef: refs.toExtensionRef(ref),
|
|
@@ -147,22 +168,32 @@ async function updateInstanceFromRegistry(projectId, instanceId, extRef, params)
|
|
|
147
168
|
},
|
|
148
169
|
};
|
|
149
170
|
let updateMask = "config.extension_ref,config.extension_version";
|
|
150
|
-
if (params) {
|
|
151
|
-
body.config.params = params;
|
|
171
|
+
if (args.params) {
|
|
172
|
+
body.config.params = args.params;
|
|
152
173
|
updateMask += ",config.params";
|
|
153
174
|
}
|
|
154
|
-
return await patchInstance(
|
|
175
|
+
return await patchInstance({
|
|
176
|
+
projectId: args.projectId,
|
|
177
|
+
instanceId: args.instanceId,
|
|
178
|
+
updateMask,
|
|
179
|
+
validateOnly: (_a = args.validateOnly) !== null && _a !== void 0 ? _a : false,
|
|
180
|
+
data: body,
|
|
181
|
+
});
|
|
155
182
|
}
|
|
156
183
|
exports.updateInstanceFromRegistry = updateInstanceFromRegistry;
|
|
157
|
-
async function patchInstance(
|
|
158
|
-
const updateRes = await api.request("PATCH", `/${VERSION}/projects/${projectId}/instances/${instanceId}`, {
|
|
184
|
+
async function patchInstance(args) {
|
|
185
|
+
const updateRes = await api.request("PATCH", `/${VERSION}/projects/${args.projectId}/instances/${args.instanceId}`, {
|
|
159
186
|
auth: true,
|
|
160
187
|
origin: api.extensionsOrigin,
|
|
161
188
|
query: {
|
|
162
|
-
updateMask,
|
|
189
|
+
updateMask: args.updateMask,
|
|
190
|
+
validateOnly: args.validateOnly,
|
|
163
191
|
},
|
|
164
|
-
data,
|
|
192
|
+
data: args.data,
|
|
165
193
|
});
|
|
194
|
+
if (args.validateOnly) {
|
|
195
|
+
return updateRes;
|
|
196
|
+
}
|
|
166
197
|
const pollRes = await operationPoller.pollOperation({
|
|
167
198
|
apiOrigin: api.extensionsOrigin,
|
|
168
199
|
apiVersion: VERSION,
|
|
@@ -302,6 +333,57 @@ async function registerPublisherProfile(projectId, publisherId) {
|
|
|
302
333
|
return res.body;
|
|
303
334
|
}
|
|
304
335
|
exports.registerPublisherProfile = registerPublisherProfile;
|
|
336
|
+
async function deprecateExtensionVersion(extensionRef, deprecationMessage) {
|
|
337
|
+
const ref = refs.parse(extensionRef);
|
|
338
|
+
try {
|
|
339
|
+
const res = await api.request("POST", `/${VERSION}/${refs.toExtensionVersionName(ref)}:deprecate`, {
|
|
340
|
+
auth: true,
|
|
341
|
+
origin: api.extensionsOrigin,
|
|
342
|
+
data: { deprecationMessage },
|
|
343
|
+
});
|
|
344
|
+
return res.body;
|
|
345
|
+
}
|
|
346
|
+
catch (err) {
|
|
347
|
+
if (err.status === 403) {
|
|
348
|
+
throw new error_1.FirebaseError(`You are not the owner of extension '${clc.bold(extensionRef)}' and don’t have the correct permissions to deprecate this extension version.` + err, { status: err.status });
|
|
349
|
+
}
|
|
350
|
+
else if (err.status === 404) {
|
|
351
|
+
throw new error_1.FirebaseError(`Extension version ${clc.bold(extensionRef)} was not found.`);
|
|
352
|
+
}
|
|
353
|
+
else if (err instanceof error_1.FirebaseError) {
|
|
354
|
+
throw err;
|
|
355
|
+
}
|
|
356
|
+
throw new error_1.FirebaseError(`Error occurred deprecating extension version '${extensionRef}': ${err}`, {
|
|
357
|
+
status: err.status,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
exports.deprecateExtensionVersion = deprecateExtensionVersion;
|
|
362
|
+
async function undeprecateExtensionVersion(extensionRef) {
|
|
363
|
+
const ref = refs.parse(extensionRef);
|
|
364
|
+
try {
|
|
365
|
+
const res = await api.request("POST", `/${VERSION}/${refs.toExtensionVersionName(ref)}:undeprecate`, {
|
|
366
|
+
auth: true,
|
|
367
|
+
origin: api.extensionsOrigin,
|
|
368
|
+
});
|
|
369
|
+
return res.body;
|
|
370
|
+
}
|
|
371
|
+
catch (err) {
|
|
372
|
+
if (err.status === 403) {
|
|
373
|
+
throw new error_1.FirebaseError(`You are not the owner of extension '${clc.bold(extensionRef)}' and don’t have the correct permissions to undeprecate this extension version.`, { status: err.status });
|
|
374
|
+
}
|
|
375
|
+
else if (err.status === 404) {
|
|
376
|
+
throw new error_1.FirebaseError(`Extension version ${clc.bold(extensionRef)} was not found.`);
|
|
377
|
+
}
|
|
378
|
+
else if (err instanceof error_1.FirebaseError) {
|
|
379
|
+
throw err;
|
|
380
|
+
}
|
|
381
|
+
throw new error_1.FirebaseError(`Error occurred undeprecating extension version '${extensionRef}': ${err}`, {
|
|
382
|
+
status: err.status,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
exports.undeprecateExtensionVersion = undeprecateExtensionVersion;
|
|
305
387
|
async function publishExtensionVersion(extensionVersionRef, packageUri, extensionRoot) {
|
|
306
388
|
const ref = refs.parse(extensionVersionRef);
|
|
307
389
|
if (!ref.version) {
|
|
@@ -27,6 +27,7 @@ const prompt_1 = require("../prompt");
|
|
|
27
27
|
const logger_1 = require("../logger");
|
|
28
28
|
const utils_2 = require("../utils");
|
|
29
29
|
const changelog_1 = require("./changelog");
|
|
30
|
+
const getProjectNumber_1 = require("../getProjectNumber");
|
|
30
31
|
var SpecParamType;
|
|
31
32
|
(function (SpecParamType) {
|
|
32
33
|
SpecParamType["SELECT"] = "select";
|
|
@@ -76,6 +77,7 @@ function getDBInstanceFromURL(databaseUrl = "") {
|
|
|
76
77
|
exports.getDBInstanceFromURL = getDBInstanceFromURL;
|
|
77
78
|
async function getFirebaseProjectParams(projectId) {
|
|
78
79
|
const body = await functionsConfig_1.getFirebaseConfig({ project: projectId });
|
|
80
|
+
const projectNumber = await getProjectNumber_1.getProjectNumber({ projectId });
|
|
79
81
|
const FIREBASE_CONFIG = JSON.stringify({
|
|
80
82
|
projectId: body.projectId,
|
|
81
83
|
databaseURL: body.databaseURL,
|
|
@@ -83,6 +85,7 @@ async function getFirebaseProjectParams(projectId) {
|
|
|
83
85
|
});
|
|
84
86
|
return {
|
|
85
87
|
PROJECT_ID: body.projectId,
|
|
88
|
+
PROJECT_NUMBER: projectNumber,
|
|
86
89
|
DATABASE_URL: body.databaseURL,
|
|
87
90
|
STORAGE_BUCKET: body.storageBucket,
|
|
88
91
|
FIREBASE_CONFIG,
|
|
@@ -108,7 +111,7 @@ function populateDefaultParams(paramVars, paramSpecs) {
|
|
|
108
111
|
const newParams = paramVars;
|
|
109
112
|
for (const param of paramSpecs) {
|
|
110
113
|
if (!paramVars[param.param]) {
|
|
111
|
-
if (param.default != undefined) {
|
|
114
|
+
if (param.default != undefined && param.required) {
|
|
112
115
|
newParams[param.param] = param.default;
|
|
113
116
|
}
|
|
114
117
|
else if (param.required) {
|
|
@@ -307,7 +310,7 @@ async function publishExtensionVersionFromLocalSource(args) {
|
|
|
307
310
|
else if (extension &&
|
|
308
311
|
extension.latestVersion &&
|
|
309
312
|
semver.eq(extensionSpec.version, extension.latestVersion)) {
|
|
310
|
-
throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(extensionSpec.version)}) already exists for the extension '${clc.bold(`${args.publisherId}/${args.extensionId}`)}'. Please increment the version inside of extension.yaml.\n
|
|
313
|
+
throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(extensionSpec.version)}) already exists for the extension '${clc.bold(`${args.publisherId}/${args.extensionId}`)}'. Please increment the version inside of extension.yaml.\n`, { exit: 103 });
|
|
311
314
|
}
|
|
312
315
|
const ref = `${args.publisherId}/${args.extensionId}@${extensionSpec.version}`;
|
|
313
316
|
let packageUri;
|
|
@@ -8,19 +8,20 @@ const extensionsApi_1 = require("./extensionsApi");
|
|
|
8
8
|
const extensionsHelper_1 = require("./extensionsHelper");
|
|
9
9
|
const utils = require("../utils");
|
|
10
10
|
const extensionsUtils = require("./utils");
|
|
11
|
-
const logger_1 = require("../logger");
|
|
12
11
|
async function listExtensions(projectId) {
|
|
13
12
|
const instances = await extensionsApi_1.listInstances(projectId);
|
|
14
13
|
if (instances.length < 1) {
|
|
15
14
|
utils.logLabeledBullet(extensionsHelper_1.logPrefix, `there are no extensions installed on project ${clc.bold(projectId)}.`);
|
|
16
|
-
return
|
|
15
|
+
return [];
|
|
17
16
|
}
|
|
18
17
|
const table = new Table({
|
|
19
18
|
head: ["Extension", "Publisher", "Instance ID", "State", "Version", "Your last update"],
|
|
20
19
|
style: { head: ["yellow"] },
|
|
21
20
|
});
|
|
22
21
|
const sorted = _.sortBy(instances, "createTime", "asc").reverse();
|
|
22
|
+
const formatted = [];
|
|
23
23
|
sorted.forEach((instance) => {
|
|
24
|
+
var _a, _b, _c, _d;
|
|
24
25
|
let extension = _.get(instance, "config.extensionRef", "");
|
|
25
26
|
let publisher;
|
|
26
27
|
if (extension === "") {
|
|
@@ -30,18 +31,22 @@ async function listExtensions(projectId) {
|
|
|
30
31
|
else {
|
|
31
32
|
publisher = extension.split("/")[0];
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
+
const instanceId = (_a = _.last(instance.name.split("/"))) !== null && _a !== void 0 ? _a : "";
|
|
35
|
+
const state = instance.state +
|
|
36
|
+
(_.get(instance, "config.source.state", "ACTIVE") === "DELETED" ? " (UNPUBLISHED)" : "");
|
|
37
|
+
const version = (_d = (_c = (_b = instance === null || instance === void 0 ? void 0 : instance.config) === null || _b === void 0 ? void 0 : _b.source) === null || _c === void 0 ? void 0 : _c.spec) === null || _d === void 0 ? void 0 : _d.version;
|
|
38
|
+
const updateTime = extensionsUtils.formatTimestamp(instance.updateTime);
|
|
39
|
+
table.push([extension, publisher, instanceId, state, version, updateTime]);
|
|
40
|
+
formatted.push({
|
|
34
41
|
extension,
|
|
35
42
|
publisher,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
]);
|
|
43
|
+
instanceId,
|
|
44
|
+
state,
|
|
45
|
+
version,
|
|
46
|
+
updateTime,
|
|
47
|
+
});
|
|
42
48
|
});
|
|
43
49
|
utils.logLabeledBullet(extensionsHelper_1.logPrefix, `list of extensions installed in ${clc.bold(projectId)}:`);
|
|
44
|
-
|
|
45
|
-
return { instances: sorted };
|
|
50
|
+
return formatted;
|
|
46
51
|
}
|
|
47
52
|
exports.listExtensions = listExtensions;
|