firebase-tools 9.19.0 → 9.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -3
- package/lib/api.js +3 -0
- package/lib/apiv2.js +7 -4
- package/lib/commands/crashlytics-symbols-upload.js +146 -0
- package/lib/commands/deploy.js +9 -1
- package/lib/commands/ext-configure.js +3 -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 +24 -3
- package/lib/commands/ext-uninstall.js +6 -0
- package/lib/commands/ext-update.js +10 -3
- package/lib/commands/functions-config-export.js +115 -0
- package/lib/commands/functions-delete.js +47 -25
- package/lib/commands/functions-list.js +12 -12
- package/lib/commands/index.js +9 -0
- 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 +93 -115
- package/lib/deploy/functions/checkIam.js +8 -8
- package/lib/deploy/functions/containerCleaner.js +71 -14
- package/lib/deploy/functions/deploy.js +4 -10
- package/lib/deploy/functions/functionsDeployHelper.js +3 -68
- package/lib/deploy/functions/prepare.js +63 -27
- 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 +422 -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 +41 -45
- package/lib/deploy/functions/triggerRegionHelper.js +40 -0
- package/lib/deploy/functions/validate.js +1 -24
- package/lib/deploy/index.js +10 -1
- package/lib/downloadUtils.js +37 -0
- package/lib/emulator/auth/apiSpec.js +549 -6
- package/lib/emulator/auth/handlers.js +4 -3
- package/lib/emulator/auth/operations.js +154 -14
- package/lib/emulator/auth/server.js +26 -15
- package/lib/emulator/auth/state.js +151 -13
- package/lib/emulator/download.js +2 -31
- package/lib/emulator/downloadableEmulators.js +7 -7
- package/lib/emulator/functionsEmulator.js +18 -4
- package/lib/emulator/functionsEmulatorRuntime.js +29 -7
- package/lib/emulator/storage/cloudFunctions.js +37 -7
- package/lib/extensions/askUserForConsent.js +14 -1
- package/lib/extensions/askUserForParam.js +81 -4
- package/lib/extensions/checkProjectBilling.js +7 -7
- package/lib/extensions/export.js +107 -0
- package/lib/extensions/extensionsApi.js +104 -21
- package/lib/extensions/extensionsHelper.js +6 -2
- package/lib/extensions/listExtensions.js +16 -11
- package/lib/extensions/paramHelper.js +9 -6
- package/lib/extensions/provisioningHelper.js +16 -3
- package/lib/extensions/refs.js +9 -1
- package/lib/extensions/secretsUtils.js +59 -0
- package/lib/extensions/updateHelper.js +12 -2
- package/lib/extensions/versionHelper.js +14 -0
- package/lib/extensions/warnings.js +33 -1
- package/lib/functional.js +8 -1
- package/lib/functions/env.js +10 -4
- package/lib/functions/runtimeConfigExport.js +137 -0
- package/lib/gcp/artifactregistry.js +16 -0
- package/lib/gcp/cloudfunctions.js +20 -74
- package/lib/gcp/cloudfunctionsv2.js +12 -90
- package/lib/gcp/cloudscheduler.js +22 -16
- package/lib/gcp/cloudtasks.js +143 -0
- package/lib/gcp/docker.js +7 -1
- package/lib/gcp/proto.js +2 -2
- package/lib/gcp/pubsub.js +1 -9
- package/lib/gcp/secretManager.js +132 -0
- package/lib/gcp/storage.js +16 -0
- package/lib/projectUtils.js +10 -1
- package/lib/requireInteractive.js +12 -0
- 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
|
@@ -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");
|
|
@@ -30,8 +30,9 @@ var ParamType;
|
|
|
30
30
|
ParamType["STRING"] = "STRING";
|
|
31
31
|
ParamType["SELECT"] = "SELECT";
|
|
32
32
|
ParamType["MULTISELECT"] = "MULTISELECT";
|
|
33
|
+
ParamType["SECRET"] = "SECRET";
|
|
33
34
|
})(ParamType = exports.ParamType || (exports.ParamType = {}));
|
|
34
|
-
async function createInstanceHelper(projectId, instanceId, config) {
|
|
35
|
+
async function createInstanceHelper(projectId, instanceId, config, validateOnly = false) {
|
|
35
36
|
const createRes = await api.request("POST", `/${VERSION}/projects/${projectId}/instances/`, {
|
|
36
37
|
auth: true,
|
|
37
38
|
origin: api.extensionsOrigin,
|
|
@@ -39,7 +40,13 @@ async function createInstanceHelper(projectId, instanceId, config) {
|
|
|
39
40
|
name: `projects/${projectId}/instances/${instanceId}`,
|
|
40
41
|
config: config,
|
|
41
42
|
},
|
|
43
|
+
query: {
|
|
44
|
+
validateOnly,
|
|
45
|
+
},
|
|
42
46
|
});
|
|
47
|
+
if (validateOnly) {
|
|
48
|
+
return createRes;
|
|
49
|
+
}
|
|
43
50
|
const pollRes = await operationPoller.pollOperation({
|
|
44
51
|
apiOrigin: api.extensionsOrigin,
|
|
45
52
|
apiVersion: VERSION,
|
|
@@ -67,7 +74,7 @@ async function createInstance(args) {
|
|
|
67
74
|
else {
|
|
68
75
|
throw new error_1.FirebaseError("No ExtensionVersion or ExtensionSource provided but one is required.");
|
|
69
76
|
}
|
|
70
|
-
return createInstanceHelper(args.projectId, args.instanceId, config);
|
|
77
|
+
return createInstanceHelper(args.projectId, args.instanceId, config, args.validateOnly);
|
|
71
78
|
}
|
|
72
79
|
exports.createInstance = createInstance;
|
|
73
80
|
async function deleteInstance(projectId, instanceId) {
|
|
@@ -114,31 +121,46 @@ async function listInstances(projectId) {
|
|
|
114
121
|
return instances;
|
|
115
122
|
}
|
|
116
123
|
exports.listInstances = listInstances;
|
|
117
|
-
async function configureInstance(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
+
},
|
|
121
135
|
},
|
|
122
136
|
});
|
|
123
137
|
return res;
|
|
124
138
|
}
|
|
125
139
|
exports.configureInstance = configureInstance;
|
|
126
|
-
async function updateInstance(
|
|
140
|
+
async function updateInstance(args) {
|
|
141
|
+
var _a;
|
|
127
142
|
const body = {
|
|
128
143
|
config: {
|
|
129
|
-
source: { name: extensionSource.name },
|
|
144
|
+
source: { name: args.extensionSource.name },
|
|
130
145
|
},
|
|
131
146
|
};
|
|
132
147
|
let updateMask = "config.source.name";
|
|
133
|
-
if (params) {
|
|
134
|
-
body.config.params = params;
|
|
148
|
+
if (args.params) {
|
|
149
|
+
body.config.params = args.params;
|
|
135
150
|
updateMask += ",config.params";
|
|
136
151
|
}
|
|
137
|
-
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
|
+
});
|
|
138
159
|
}
|
|
139
160
|
exports.updateInstance = updateInstance;
|
|
140
|
-
async function updateInstanceFromRegistry(
|
|
141
|
-
|
|
161
|
+
async function updateInstanceFromRegistry(args) {
|
|
162
|
+
var _a;
|
|
163
|
+
const ref = refs.parse(args.extRef);
|
|
142
164
|
const body = {
|
|
143
165
|
config: {
|
|
144
166
|
extensionRef: refs.toExtensionRef(ref),
|
|
@@ -146,22 +168,32 @@ async function updateInstanceFromRegistry(projectId, instanceId, extRef, params)
|
|
|
146
168
|
},
|
|
147
169
|
};
|
|
148
170
|
let updateMask = "config.extension_ref,config.extension_version";
|
|
149
|
-
if (params) {
|
|
150
|
-
body.config.params = params;
|
|
171
|
+
if (args.params) {
|
|
172
|
+
body.config.params = args.params;
|
|
151
173
|
updateMask += ",config.params";
|
|
152
174
|
}
|
|
153
|
-
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
|
+
});
|
|
154
182
|
}
|
|
155
183
|
exports.updateInstanceFromRegistry = updateInstanceFromRegistry;
|
|
156
|
-
async function patchInstance(
|
|
157
|
-
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}`, {
|
|
158
186
|
auth: true,
|
|
159
187
|
origin: api.extensionsOrigin,
|
|
160
188
|
query: {
|
|
161
|
-
updateMask,
|
|
189
|
+
updateMask: args.updateMask,
|
|
190
|
+
validateOnly: args.validateOnly,
|
|
162
191
|
},
|
|
163
|
-
data,
|
|
192
|
+
data: args.data,
|
|
164
193
|
});
|
|
194
|
+
if (args.validateOnly) {
|
|
195
|
+
return updateRes;
|
|
196
|
+
}
|
|
165
197
|
const pollRes = await operationPoller.pollOperation({
|
|
166
198
|
apiOrigin: api.extensionsOrigin,
|
|
167
199
|
apiVersion: VERSION,
|
|
@@ -301,6 +333,57 @@ async function registerPublisherProfile(projectId, publisherId) {
|
|
|
301
333
|
return res.body;
|
|
302
334
|
}
|
|
303
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;
|
|
304
387
|
async function publishExtensionVersion(extensionVersionRef, packageUri, extensionRoot) {
|
|
305
388
|
const ref = refs.parse(extensionVersionRef);
|
|
306
389
|
if (!ref.version) {
|
|
@@ -27,12 +27,14 @@ 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";
|
|
33
34
|
SpecParamType["MULTISELECT"] = "multiSelect";
|
|
34
35
|
SpecParamType["STRING"] = "string";
|
|
35
36
|
SpecParamType["SELECTRESOURCE"] = "selectResource";
|
|
37
|
+
SpecParamType["SECRET"] = "secret";
|
|
36
38
|
})(SpecParamType = exports.SpecParamType || (exports.SpecParamType = {}));
|
|
37
39
|
var SourceOrigin;
|
|
38
40
|
(function (SourceOrigin) {
|
|
@@ -75,6 +77,7 @@ function getDBInstanceFromURL(databaseUrl = "") {
|
|
|
75
77
|
exports.getDBInstanceFromURL = getDBInstanceFromURL;
|
|
76
78
|
async function getFirebaseProjectParams(projectId) {
|
|
77
79
|
const body = await functionsConfig_1.getFirebaseConfig({ project: projectId });
|
|
80
|
+
const projectNumber = await getProjectNumber_1.getProjectNumber({ projectId });
|
|
78
81
|
const FIREBASE_CONFIG = JSON.stringify({
|
|
79
82
|
projectId: body.projectId,
|
|
80
83
|
databaseURL: body.databaseURL,
|
|
@@ -82,6 +85,7 @@ async function getFirebaseProjectParams(projectId) {
|
|
|
82
85
|
});
|
|
83
86
|
return {
|
|
84
87
|
PROJECT_ID: body.projectId,
|
|
88
|
+
PROJECT_NUMBER: projectNumber,
|
|
85
89
|
DATABASE_URL: body.databaseURL,
|
|
86
90
|
STORAGE_BUCKET: body.storageBucket,
|
|
87
91
|
FIREBASE_CONFIG,
|
|
@@ -107,7 +111,7 @@ function populateDefaultParams(paramVars, paramSpecs) {
|
|
|
107
111
|
const newParams = paramVars;
|
|
108
112
|
for (const param of paramSpecs) {
|
|
109
113
|
if (!paramVars[param.param]) {
|
|
110
|
-
if (param.default != undefined) {
|
|
114
|
+
if (param.default != undefined && param.required) {
|
|
111
115
|
newParams[param.param] = param.default;
|
|
112
116
|
}
|
|
113
117
|
else if (param.required) {
|
|
@@ -306,7 +310,7 @@ async function publishExtensionVersionFromLocalSource(args) {
|
|
|
306
310
|
else if (extension &&
|
|
307
311
|
extension.latestVersion &&
|
|
308
312
|
semver.eq(extensionSpec.version, extension.latestVersion)) {
|
|
309
|
-
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 });
|
|
310
314
|
}
|
|
311
315
|
const ref = `${args.publisherId}/${args.extensionId}@${extensionSpec.version}`;
|
|
312
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;
|
|
@@ -4,13 +4,13 @@ exports.readEnvFile = exports.getParamsFromFile = exports.promptForNewParams = e
|
|
|
4
4
|
const _ = require("lodash");
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const clc = require("cli-color");
|
|
7
|
-
const dotenv = require("dotenv");
|
|
8
7
|
const fs = require("fs-extra");
|
|
9
8
|
const error_1 = require("../error");
|
|
10
9
|
const logger_1 = require("../logger");
|
|
11
10
|
const extensionsHelper_1 = require("./extensionsHelper");
|
|
12
11
|
const askUserForParam = require("./askUserForParam");
|
|
13
12
|
const track = require("../track");
|
|
13
|
+
const env = require("../functions/env");
|
|
14
14
|
function setNewDefaults(params, newDefaults) {
|
|
15
15
|
params.forEach((param) => {
|
|
16
16
|
if (newDefaults[param.param.toUpperCase()]) {
|
|
@@ -42,13 +42,12 @@ async function getParams(args) {
|
|
|
42
42
|
params = getParamsFromFile({
|
|
43
43
|
projectId: args.projectId,
|
|
44
44
|
paramSpecs: args.paramSpecs,
|
|
45
|
-
noninteractive: args.nonInteractive,
|
|
46
45
|
paramsEnvPath: args.paramsEnvPath,
|
|
47
46
|
});
|
|
48
47
|
}
|
|
49
48
|
else {
|
|
50
49
|
const firebaseProjectParams = await extensionsHelper_1.getFirebaseProjectParams(args.projectId);
|
|
51
|
-
params = await askUserForParam.ask(args.paramSpecs, firebaseProjectParams);
|
|
50
|
+
params = await askUserForParam.ask(args.projectId, args.instanceId, args.paramSpecs, firebaseProjectParams, !!args.reconfiguring);
|
|
52
51
|
}
|
|
53
52
|
track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params));
|
|
54
53
|
return params;
|
|
@@ -71,7 +70,6 @@ async function getParamsForUpdate(args) {
|
|
|
71
70
|
params = getParamsFromFile({
|
|
72
71
|
projectId: args.projectId,
|
|
73
72
|
paramSpecs: args.newSpec.params,
|
|
74
|
-
noninteractive: args.nonInteractive,
|
|
75
73
|
paramsEnvPath: args.paramsEnvPath,
|
|
76
74
|
});
|
|
77
75
|
}
|
|
@@ -81,6 +79,7 @@ async function getParamsForUpdate(args) {
|
|
|
81
79
|
newSpec: args.newSpec,
|
|
82
80
|
currentParams: args.currentParams,
|
|
83
81
|
projectId: args.projectId,
|
|
82
|
+
instanceId: args.instanceId,
|
|
84
83
|
});
|
|
85
84
|
}
|
|
86
85
|
track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params));
|
|
@@ -106,7 +105,7 @@ async function promptForNewParams(args) {
|
|
|
106
105
|
if (paramsDiffAdditions.length) {
|
|
107
106
|
logger_1.logger.info("To update this instance, configure the following new parameters:");
|
|
108
107
|
for (const param of paramsDiffAdditions) {
|
|
109
|
-
const chosenValue = await askUserForParam.askForParam(param);
|
|
108
|
+
const chosenValue = await askUserForParam.askForParam(args.projectId, args.instanceId, param, false);
|
|
110
109
|
args.currentParams[param.param] = chosenValue;
|
|
111
110
|
}
|
|
112
111
|
}
|
|
@@ -131,6 +130,10 @@ function getParamsFromFile(args) {
|
|
|
131
130
|
exports.getParamsFromFile = getParamsFromFile;
|
|
132
131
|
function readEnvFile(envPath) {
|
|
133
132
|
const buf = fs.readFileSync(path.resolve(envPath), "utf8");
|
|
134
|
-
|
|
133
|
+
const result = env.parse(buf.toString().trim());
|
|
134
|
+
if (result.errors.length) {
|
|
135
|
+
throw new error_1.FirebaseError(`Error while parsing ${envPath} - unable to parse following lines:\n${result.errors.join("\n")}`);
|
|
136
|
+
}
|
|
137
|
+
return result.envs;
|
|
135
138
|
}
|
|
136
139
|
exports.readEnvFile = readEnvFile;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getUsedProducts = exports.checkProductsProvisioned = exports.DeferredProduct = void 0;
|
|
3
|
+
exports.getUsedProducts = exports.bulkCheckProductsProvisioned = exports.checkProductsProvisioned = exports.DeferredProduct = void 0;
|
|
4
4
|
const marked = require("marked");
|
|
5
5
|
const api = require("../api");
|
|
6
|
+
const functional_1 = require("../functional");
|
|
6
7
|
const error_1 = require("../error");
|
|
8
|
+
const planner_1 = require("../deploy/extensions/planner");
|
|
7
9
|
var DeferredProduct;
|
|
8
10
|
(function (DeferredProduct) {
|
|
9
11
|
DeferredProduct[DeferredProduct["STORAGE"] = 0] = "STORAGE";
|
|
@@ -11,6 +13,18 @@ var DeferredProduct;
|
|
|
11
13
|
})(DeferredProduct = exports.DeferredProduct || (exports.DeferredProduct = {}));
|
|
12
14
|
async function checkProductsProvisioned(projectId, spec) {
|
|
13
15
|
const usedProducts = getUsedProducts(spec);
|
|
16
|
+
await checkProducts(projectId, usedProducts);
|
|
17
|
+
}
|
|
18
|
+
exports.checkProductsProvisioned = checkProductsProvisioned;
|
|
19
|
+
async function bulkCheckProductsProvisioned(projectId, instanceSpecs) {
|
|
20
|
+
const usedProducts = await Promise.all(instanceSpecs.map(async (i) => {
|
|
21
|
+
const extensionVersion = await planner_1.getExtensionVersion(i);
|
|
22
|
+
return getUsedProducts(extensionVersion.spec);
|
|
23
|
+
}));
|
|
24
|
+
await checkProducts(projectId, [...functional_1.flattenArray(usedProducts)]);
|
|
25
|
+
}
|
|
26
|
+
exports.bulkCheckProductsProvisioned = bulkCheckProductsProvisioned;
|
|
27
|
+
async function checkProducts(projectId, usedProducts) {
|
|
14
28
|
const needProvisioning = [];
|
|
15
29
|
let isStorageProvisionedPromise;
|
|
16
30
|
let isAuthProvisionedPromise;
|
|
@@ -29,7 +43,7 @@ async function checkProductsProvisioned(projectId, spec) {
|
|
|
29
43
|
if (needProvisioning.length > 0) {
|
|
30
44
|
let errorMessage = "Some services used by this extension have not been set up on your " +
|
|
31
45
|
"Firebase project. To ensure this extension works as intended, you must enable these " +
|
|
32
|
-
"services by following the provided links, then retry
|
|
46
|
+
"services by following the provided links, then retry this command\n\n";
|
|
33
47
|
if (needProvisioning.includes(DeferredProduct.STORAGE)) {
|
|
34
48
|
errorMessage +=
|
|
35
49
|
" - Firebase Storage: store and retrieve user-generated files like images, audio, and " +
|
|
@@ -46,7 +60,6 @@ async function checkProductsProvisioned(projectId, spec) {
|
|
|
46
60
|
throw new error_1.FirebaseError(marked(errorMessage), { exit: 2 });
|
|
47
61
|
}
|
|
48
62
|
}
|
|
49
|
-
exports.checkProductsProvisioned = checkProductsProvisioned;
|
|
50
63
|
function getUsedProducts(spec) {
|
|
51
64
|
var _a, _b;
|
|
52
65
|
const usedProducts = [];
|
package/lib/extensions/refs.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toExtensionVersionName = exports.toExtensionName = exports.toExtensionVersionRef = exports.toExtensionRef = exports.parse = void 0;
|
|
3
|
+
exports.equal = exports.toExtensionVersionName = exports.toExtensionName = exports.toExtensionVersionRef = exports.toExtensionRef = exports.parse = void 0;
|
|
4
4
|
const semver = require("semver");
|
|
5
5
|
const error_1 = require("../error");
|
|
6
6
|
const refRegex = new RegExp(/^([^/@\n]+)\/{1}([^/@\n]+)(@{1}([^\n]+)|)$/);
|
|
@@ -57,3 +57,11 @@ function toExtensionVersionName(ref) {
|
|
|
57
57
|
return `publishers/${ref.publisherId}/extensions/${ref.extensionId}/versions/${ref.version}`;
|
|
58
58
|
}
|
|
59
59
|
exports.toExtensionVersionName = toExtensionVersionName;
|
|
60
|
+
function equal(a, b) {
|
|
61
|
+
return (!!a &&
|
|
62
|
+
!!b &&
|
|
63
|
+
a.publisherId === b.publisherId &&
|
|
64
|
+
a.extensionId === b.extensionId &&
|
|
65
|
+
a.version === b.version);
|
|
66
|
+
}
|
|
67
|
+
exports.equal = equal;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.prettySecretName = exports.getSecretLabels = exports.getActiveSecrets = exports.getManagedSecrets = exports.grantFirexServiceAgentSecretAdminRole = exports.usesSecrets = exports.ensureSecretManagerApiEnabled = exports.SECRET_LABEL = void 0;
|
|
4
|
+
const getProjectNumber_1 = require("../getProjectNumber");
|
|
5
|
+
const utils = require("../utils");
|
|
6
|
+
const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
7
|
+
const projectUtils_1 = require("../projectUtils");
|
|
8
|
+
const extensionsApi = require("./extensionsApi");
|
|
9
|
+
const secretManagerApi = require("../gcp/secretManager");
|
|
10
|
+
const logger_1 = require("../logger");
|
|
11
|
+
exports.SECRET_LABEL = "firebase-extensions-managed";
|
|
12
|
+
async function ensureSecretManagerApiEnabled(options) {
|
|
13
|
+
const projectId = projectUtils_1.needProjectId(options);
|
|
14
|
+
return await ensureApiEnabled_1.ensure(projectId, "secretmanager.googleapis.com", "extensions", options.markdown);
|
|
15
|
+
}
|
|
16
|
+
exports.ensureSecretManagerApiEnabled = ensureSecretManagerApiEnabled;
|
|
17
|
+
function usesSecrets(spec) {
|
|
18
|
+
return spec.params && !!spec.params.find((p) => p.type == extensionsApi.ParamType.SECRET);
|
|
19
|
+
}
|
|
20
|
+
exports.usesSecrets = usesSecrets;
|
|
21
|
+
async function grantFirexServiceAgentSecretAdminRole(secret) {
|
|
22
|
+
const projectNumber = await getProjectNumber_1.getProjectNumber({ projectId: secret.projectId });
|
|
23
|
+
const firexSaProjectId = utils.envOverride("FIREBASE_EXTENSIONS_SA_PROJECT_ID", "gcp-sa-firebasemods");
|
|
24
|
+
const saEmail = `service-${projectNumber}@${firexSaProjectId}.iam.gserviceaccount.com`;
|
|
25
|
+
return secretManagerApi.grantServiceAgentRole(secret, saEmail, "roles/secretmanager.admin");
|
|
26
|
+
}
|
|
27
|
+
exports.grantFirexServiceAgentSecretAdminRole = grantFirexServiceAgentSecretAdminRole;
|
|
28
|
+
async function getManagedSecrets(instance) {
|
|
29
|
+
return (await Promise.all(getActiveSecrets(instance.config.source.spec, instance.config.params).map(async (secretResourceName) => {
|
|
30
|
+
const secret = secretManagerApi.parseSecretResourceName(secretResourceName);
|
|
31
|
+
const labels = (await secretManagerApi.getSecret(secret.projectId, secret.name)).labels;
|
|
32
|
+
if (labels && labels[exports.SECRET_LABEL]) {
|
|
33
|
+
return secretResourceName;
|
|
34
|
+
}
|
|
35
|
+
return Promise.resolve("");
|
|
36
|
+
}))).filter((secretId) => !!secretId);
|
|
37
|
+
}
|
|
38
|
+
exports.getManagedSecrets = getManagedSecrets;
|
|
39
|
+
function getActiveSecrets(spec, params) {
|
|
40
|
+
return spec.params
|
|
41
|
+
.map((p) => (p.type == extensionsApi.ParamType.SECRET ? params[p.param] : ""))
|
|
42
|
+
.filter((pv) => !!pv);
|
|
43
|
+
}
|
|
44
|
+
exports.getActiveSecrets = getActiveSecrets;
|
|
45
|
+
function getSecretLabels(instanceId) {
|
|
46
|
+
const labels = {};
|
|
47
|
+
labels[exports.SECRET_LABEL] = instanceId;
|
|
48
|
+
return labels;
|
|
49
|
+
}
|
|
50
|
+
exports.getSecretLabels = getSecretLabels;
|
|
51
|
+
function prettySecretName(secretResourceName) {
|
|
52
|
+
const nameTokens = secretResourceName.split("/");
|
|
53
|
+
if (nameTokens.length != 4 && nameTokens.length != 6) {
|
|
54
|
+
logger_1.logger.debug(`unable to parse secret secretResourceName: ${secretResourceName}`);
|
|
55
|
+
return secretResourceName;
|
|
56
|
+
}
|
|
57
|
+
return nameTokens.slice(0, 4).join("/");
|
|
58
|
+
}
|
|
59
|
+
exports.prettySecretName = prettySecretName;
|
|
@@ -62,10 +62,20 @@ exports.displayChanges = displayChanges;
|
|
|
62
62
|
async function update(updateOptions) {
|
|
63
63
|
const { projectId, instanceId, source, extRef, params } = updateOptions;
|
|
64
64
|
if (extRef) {
|
|
65
|
-
return await extensionsApi.updateInstanceFromRegistry(
|
|
65
|
+
return await extensionsApi.updateInstanceFromRegistry({
|
|
66
|
+
projectId,
|
|
67
|
+
instanceId,
|
|
68
|
+
extRef,
|
|
69
|
+
params,
|
|
70
|
+
});
|
|
66
71
|
}
|
|
67
72
|
else if (source) {
|
|
68
|
-
return await extensionsApi.updateInstance(
|
|
73
|
+
return await extensionsApi.updateInstance({
|
|
74
|
+
projectId,
|
|
75
|
+
instanceId,
|
|
76
|
+
extensionSource: source,
|
|
77
|
+
params,
|
|
78
|
+
});
|
|
69
79
|
}
|
|
70
80
|
throw new error_1.FirebaseError(`Neither a source nor a version of the extension was supplied for ${instanceId}. Please make sure this is a valid extension and try again.`);
|
|
71
81
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseVersionPredicate = void 0;
|
|
4
|
+
const error_1 = require("../error");
|
|
5
|
+
function parseVersionPredicate(versionPredicate) {
|
|
6
|
+
const versionPredicateRegex = "^(?<comparator>>=|<=|>|<)?(?<targetSemVer>.*)";
|
|
7
|
+
const matches = versionPredicate.match(versionPredicateRegex);
|
|
8
|
+
if (!matches || !matches.groups.targetSemVer) {
|
|
9
|
+
throw new error_1.FirebaseError("Invalid version predicate.");
|
|
10
|
+
}
|
|
11
|
+
const comparator = matches.groups.comparator || "=";
|
|
12
|
+
return { comparator, targetSemVer: matches.groups.targetSemVer };
|
|
13
|
+
}
|
|
14
|
+
exports.parseVersionPredicate = parseVersionPredicate;
|