firebase-tools 13.17.0 → 13.19.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/README.md +10 -9
- package/lib/commands/dataconnect-services-list.js +4 -3
- package/lib/commands/dataconnect-sql-grant.js +37 -0
- package/lib/commands/dataconnect-sql-migrate.js +2 -1
- package/lib/commands/deploy.js +2 -0
- package/lib/commands/ext-info.js +3 -1
- package/lib/commands/ext-sdk-install.js +88 -0
- package/lib/commands/ext.js +1 -0
- package/lib/commands/index.js +3 -0
- package/lib/dataconnect/client.js +1 -1
- package/lib/dataconnect/dataplaneClient.js +1 -1
- package/lib/dataconnect/ensureApis.js +6 -1
- package/lib/dataconnect/load.js +3 -1
- package/lib/dataconnect/provisionCloudSql.js +34 -21
- package/lib/dataconnect/schemaMigration.js +126 -73
- package/lib/deploy/dataconnect/deploy.js +16 -13
- package/lib/deploy/dataconnect/prepare.js +36 -0
- package/lib/deploy/dataconnect/release.js +9 -2
- package/lib/deploy/extensions/deploymentSummary.js +3 -2
- package/lib/deploy/extensions/planner.js +32 -3
- package/lib/deploy/extensions/prepare.js +36 -64
- package/lib/deploy/extensions/release.js +11 -10
- package/lib/deploy/extensions/tasks.js +32 -21
- package/lib/deploy/firestore/prepare.js +10 -0
- package/lib/deploy/firestore/release.js +3 -6
- package/lib/deploy/functions/checkIam.js +7 -2
- package/lib/deploy/functions/ensure.js +10 -2
- package/lib/deploy/functions/prepare.js +10 -2
- package/lib/deploy/functions/runtimes/node/index.js +1 -1
- package/lib/deploy/index.js +9 -5
- package/lib/emulator/downloadableEmulators.js +9 -9
- package/lib/extensions/extensionsApi.js +3 -2
- package/lib/extensions/extensionsHelper.js +3 -3
- package/lib/extensions/localHelper.js +31 -0
- package/lib/extensions/runtimes/common.js +186 -38
- package/lib/extensions/runtimes/node.js +399 -0
- package/lib/extensions/types.js +10 -14
- package/lib/extensions/warnings.js +5 -2
- package/lib/firestore/checkDatabaseType.js +10 -3
- package/lib/frameworks/angular/index.js +15 -3
- package/lib/frameworks/next/utils.js +1 -1
- package/lib/gcp/cloudsql/permissions.js +6 -1
- package/lib/gcp/secretManager.js +12 -5
- package/lib/init/features/dataconnect/index.js +66 -53
- package/lib/init/features/firestore/index.js +20 -1
- package/lib/init/features/firestore/indexes.js +4 -4
- package/package.json +1 -1
- package/schema/connector-yaml.json +43 -17
- package/templates/init/dataconnect/connector.yaml +0 -1
- package/templates/init/dataconnect/dataconnect-fdccompatiblemode.yaml +12 -0
- package/templates/init/dataconnect/dataconnect.yaml +1 -1
|
@@ -1,45 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.extractExtensionsFromBuilds = exports.
|
|
3
|
+
exports.longestCommonPrefix = exports.snakeToCamelCase = exports.lowercaseFirstLetter = exports.capitalizeFirstLetter = exports.toTitleCase = exports.getInstallPathPrefix = exports.getCodebaseDir = exports.writeSDK = exports.getCodebaseRuntime = exports.copyDirectory = exports.writeFile = exports.isTypescriptCodebase = exports.extensionMatchesAnyFilter = exports.extractExtensionsFromBuilds = exports.getErrorMessage = exports.fixDarkBlueText = void 0;
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const prompt_1 = require("../../prompt");
|
|
7
|
+
const fsutils = require("../../fsutils");
|
|
8
|
+
const utils_1 = require("../../utils");
|
|
9
|
+
const error_1 = require("../../error");
|
|
4
10
|
const projectConfig_1 = require("../../functions/projectConfig");
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
let functionsBuilds = {};
|
|
21
|
-
const codebases = (0, functionsDeployHelper_1.targetCodebases)(functionsConfig);
|
|
22
|
-
silenceLogging();
|
|
23
|
-
for (const codebase of codebases) {
|
|
24
|
-
try {
|
|
25
|
-
const filters = [{ codebase: `${codebase}` }];
|
|
26
|
-
const builds = await (0, prepare_1.loadCodebases)(functionsConfig, options, firebaseConfig, runtimeConfig, filters);
|
|
27
|
-
functionsBuilds = Object.assign(Object.assign({}, functionsBuilds), builds);
|
|
28
|
-
}
|
|
29
|
-
catch (err) {
|
|
30
|
-
}
|
|
11
|
+
const types_1 = require("../types");
|
|
12
|
+
const functionRuntimes = require("../../deploy/functions/runtimes");
|
|
13
|
+
const nodeRuntime = require("./node");
|
|
14
|
+
function fixDarkBlueText(txt) {
|
|
15
|
+
const DARK_BLUE = "\u001b[34m";
|
|
16
|
+
const BRIGHT_CYAN = "\u001b[36;1m";
|
|
17
|
+
return txt.replaceAll(DARK_BLUE, BRIGHT_CYAN);
|
|
18
|
+
}
|
|
19
|
+
exports.fixDarkBlueText = fixDarkBlueText;
|
|
20
|
+
function getErrorMessage(err, defaultMsg) {
|
|
21
|
+
if ((0, types_1.isObject)(err) && err.message && typeof err.message === "string") {
|
|
22
|
+
return err.message;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
return defaultMsg;
|
|
31
26
|
}
|
|
32
|
-
resumeLogging();
|
|
33
|
-
return extractExtensionsFromBuilds(functionsBuilds);
|
|
34
27
|
}
|
|
35
|
-
exports.
|
|
28
|
+
exports.getErrorMessage = getErrorMessage;
|
|
36
29
|
function extractExtensionsFromBuilds(builds, filters) {
|
|
37
30
|
const extRecords = {};
|
|
38
31
|
for (const [codebase, build] of Object.entries(builds)) {
|
|
39
32
|
if (build.extensions) {
|
|
40
33
|
for (const [id, ext] of Object.entries(build.extensions)) {
|
|
41
34
|
if (extensionMatchesAnyFilter(codebase, id, filters)) {
|
|
42
|
-
extRecords[id]
|
|
35
|
+
if (extRecords[id]) {
|
|
36
|
+
throw new error_1.FirebaseError(`Duplicate extension id found: ${id}`);
|
|
37
|
+
}
|
|
38
|
+
extRecords[id] = Object.assign(Object.assign({}, ext), { labels: { createdBy: "SDK", codebase } });
|
|
43
39
|
}
|
|
44
40
|
}
|
|
45
41
|
}
|
|
@@ -53,6 +49,7 @@ function extensionMatchesAnyFilter(codebase, extensionId, filters) {
|
|
|
53
49
|
}
|
|
54
50
|
return filters.some((f) => extensionMatchesFilter(codebase, extensionId, f));
|
|
55
51
|
}
|
|
52
|
+
exports.extensionMatchesAnyFilter = extensionMatchesAnyFilter;
|
|
56
53
|
function extensionMatchesFilter(codebase, extensionId, filter) {
|
|
57
54
|
if (codebase && filter.codebase) {
|
|
58
55
|
if (codebase !== filter.codebase) {
|
|
@@ -62,14 +59,165 @@ function extensionMatchesFilter(codebase, extensionId, filter) {
|
|
|
62
59
|
if (!filter.idChunks) {
|
|
63
60
|
return true;
|
|
64
61
|
}
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
const filterId = filter.idChunks.join("-");
|
|
63
|
+
return extensionId === filterId;
|
|
64
|
+
}
|
|
65
|
+
function isTypescriptCodebase(codebaseDir) {
|
|
66
|
+
return fsutils.fileExistsSync(path.join(codebaseDir, "tsconfig.json"));
|
|
67
|
+
}
|
|
68
|
+
exports.isTypescriptCodebase = isTypescriptCodebase;
|
|
69
|
+
async function writeFile(filePath, data, options) {
|
|
70
|
+
const shortFilePath = filePath.replace(process.cwd(), ".");
|
|
71
|
+
if (fsutils.fileExistsSync(filePath)) {
|
|
72
|
+
if (await (0, prompt_1.confirm)({
|
|
73
|
+
message: `${shortFilePath} already exists. Overwite it?`,
|
|
74
|
+
nonInteractive: options.nonInteractive,
|
|
75
|
+
force: options.force,
|
|
76
|
+
default: false,
|
|
77
|
+
})) {
|
|
78
|
+
try {
|
|
79
|
+
await fs.promises.writeFile(filePath, data, { flag: "w" });
|
|
80
|
+
(0, utils_1.logLabeledBullet)("extensions", `successfully wrote ${shortFilePath}`);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
throw new error_1.FirebaseError(`Failed to write ${shortFilePath}:\n ${err}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
68
89
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
90
|
+
else {
|
|
91
|
+
try {
|
|
92
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
93
|
+
try {
|
|
94
|
+
await fs.promises.writeFile(`${filePath}`, data, { flag: "w" });
|
|
95
|
+
(0, utils_1.logLabeledBullet)("extensions", `successfully created ${shortFilePath}`);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
throw new error_1.FirebaseError(`Failed to create ${shortFilePath}:\n ${err}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
throw new error_1.FirebaseError(`Error during SDK file creation:\n ${err}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.writeFile = writeFile;
|
|
107
|
+
async function copyDirectory(src, dest, options) {
|
|
108
|
+
const shortDestPath = dest.replace(process.cwd(), ",");
|
|
109
|
+
if (fsutils.dirExistsSync(dest)) {
|
|
110
|
+
if (await (0, prompt_1.confirm)({
|
|
111
|
+
message: `${shortDestPath} already exists. Copy anyway?`,
|
|
112
|
+
nonInteractive: options.nonInteractive,
|
|
113
|
+
force: options.force,
|
|
114
|
+
default: false,
|
|
115
|
+
})) {
|
|
116
|
+
const entries = await fs.promises.readdir(src, { withFileTypes: true });
|
|
117
|
+
for (const entry of entries) {
|
|
118
|
+
const srcPath = path.join(src, entry.name);
|
|
119
|
+
const destPath = path.join(dest, entry.name);
|
|
120
|
+
if (entry.isDirectory()) {
|
|
121
|
+
if (srcPath.includes("node_modules")) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
await copyDirectory(srcPath, destPath, { force: true });
|
|
125
|
+
}
|
|
126
|
+
else if (entry.isFile())
|
|
127
|
+
try {
|
|
128
|
+
await fs.promises.copyFile(srcPath, destPath);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
throw new error_1.FirebaseError(`Failed to copy ${destPath.replace(process.cwd(), ".")}:\n ${err}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
await fs.promises.mkdir(dest, { recursive: true }).then(async () => {
|
|
141
|
+
await copyDirectory(src, dest, { force: true });
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
exports.copyDirectory = copyDirectory;
|
|
146
|
+
async function getCodebaseRuntime(options) {
|
|
147
|
+
const config = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
|
|
148
|
+
const codebaseConfig = (0, projectConfig_1.configForCodebase)(config, options.codebase || projectConfig_1.DEFAULT_CODEBASE);
|
|
149
|
+
const sourceDirName = codebaseConfig.source;
|
|
150
|
+
const sourceDir = options.config.path(sourceDirName);
|
|
151
|
+
const delegateContext = {
|
|
152
|
+
projectId: "",
|
|
153
|
+
sourceDir,
|
|
154
|
+
projectDir: options.config.projectDir,
|
|
155
|
+
runtime: codebaseConfig.runtime,
|
|
156
|
+
};
|
|
157
|
+
let delegate;
|
|
158
|
+
try {
|
|
159
|
+
delegate = await functionRuntimes.getRuntimeDelegate(delegateContext);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
throw new error_1.FirebaseError(`Could not detect target language for SDK at ${sourceDir}`);
|
|
163
|
+
}
|
|
164
|
+
return delegate.runtime;
|
|
165
|
+
}
|
|
166
|
+
exports.getCodebaseRuntime = getCodebaseRuntime;
|
|
167
|
+
async function writeSDK(extensionRef, localPath, spec, options) {
|
|
168
|
+
const runtime = await getCodebaseRuntime(options);
|
|
169
|
+
if (runtime.startsWith("nodejs")) {
|
|
170
|
+
let sampleImport = await nodeRuntime.writeSDK(extensionRef, localPath, spec, options);
|
|
171
|
+
sampleImport = fixDarkBlueText(sampleImport);
|
|
172
|
+
return sampleImport;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
throw new error_1.FirebaseError(`Extension SDK generation is currently only supported for NodeJs. We detected the target source to be: ${runtime}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
exports.writeSDK = writeSDK;
|
|
179
|
+
function getCodebaseDir(options) {
|
|
180
|
+
const config = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
|
|
181
|
+
const codebaseConfig = (0, projectConfig_1.configForCodebase)(config, options.codebase || projectConfig_1.DEFAULT_CODEBASE);
|
|
182
|
+
return `${options.projectRoot}/${codebaseConfig.source}/`;
|
|
183
|
+
}
|
|
184
|
+
exports.getCodebaseDir = getCodebaseDir;
|
|
185
|
+
function getInstallPathPrefix(options) {
|
|
186
|
+
return `${getCodebaseDir(options)}generated/extensions/`;
|
|
187
|
+
}
|
|
188
|
+
exports.getInstallPathPrefix = getInstallPathPrefix;
|
|
189
|
+
function toTitleCase(txt) {
|
|
190
|
+
return txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase();
|
|
191
|
+
}
|
|
192
|
+
exports.toTitleCase = toTitleCase;
|
|
193
|
+
function capitalizeFirstLetter(txt) {
|
|
194
|
+
return txt.charAt(0).toUpperCase() + txt.substring(1);
|
|
195
|
+
}
|
|
196
|
+
exports.capitalizeFirstLetter = capitalizeFirstLetter;
|
|
197
|
+
function lowercaseFirstLetter(txt) {
|
|
198
|
+
return txt.charAt(0).toLowerCase() + txt.substring(1);
|
|
199
|
+
}
|
|
200
|
+
exports.lowercaseFirstLetter = lowercaseFirstLetter;
|
|
201
|
+
function snakeToCamelCase(txt) {
|
|
202
|
+
let ret = txt.toLowerCase();
|
|
203
|
+
ret = ret.replace(/_/g, " ");
|
|
204
|
+
ret = ret.replace(/\w\S*/g, toTitleCase);
|
|
205
|
+
ret = ret.charAt(0).toLowerCase() + ret.substring(1);
|
|
206
|
+
return ret;
|
|
207
|
+
}
|
|
208
|
+
exports.snakeToCamelCase = snakeToCamelCase;
|
|
209
|
+
function longestCommonPrefix(arr) {
|
|
210
|
+
if (arr.length === 0) {
|
|
211
|
+
return "";
|
|
212
|
+
}
|
|
213
|
+
let prefix = "";
|
|
214
|
+
for (let pos = 0; pos < arr[0].length; pos++) {
|
|
215
|
+
if (arr.every((s) => s.charAt(pos) === arr[0][pos])) {
|
|
216
|
+
prefix += arr[0][pos];
|
|
72
217
|
}
|
|
218
|
+
else
|
|
219
|
+
break;
|
|
73
220
|
}
|
|
74
|
-
return
|
|
221
|
+
return prefix;
|
|
75
222
|
}
|
|
223
|
+
exports.longestCommonPrefix = longestCommonPrefix;
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeSDK = exports.TYPESCRIPT_VERSION = exports.FIREBASE_FUNCTIONS_VERSION = exports.SDK_GENERATION_VERSION = void 0;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
const prompt_1 = require("../../prompt");
|
|
6
|
+
const secretsUtils = require("../secretsUtils");
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const utils_1 = require("../../utils");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const common_1 = require("./common");
|
|
11
|
+
const askUserForEventsConfig_1 = require("../askUserForEventsConfig");
|
|
12
|
+
const extensionsHelper_1 = require("../extensionsHelper");
|
|
13
|
+
const error_1 = require("../../error");
|
|
14
|
+
const marked_terminal_1 = require("marked-terminal");
|
|
15
|
+
const marked_1 = require("marked");
|
|
16
|
+
marked_1.marked.use((0, marked_terminal_1.markedTerminal)());
|
|
17
|
+
exports.SDK_GENERATION_VERSION = "1.0.0";
|
|
18
|
+
exports.FIREBASE_FUNCTIONS_VERSION = ">=5.1.0";
|
|
19
|
+
exports.TYPESCRIPT_VERSION = "^4.9.0";
|
|
20
|
+
function makePackageName(extensionRef, name) {
|
|
21
|
+
if (!extensionRef) {
|
|
22
|
+
return `@firebase-extensions/local-${name}-sdk`;
|
|
23
|
+
}
|
|
24
|
+
const pub = extensionRef.split("/")[0];
|
|
25
|
+
return `@firebase-extensions/${pub}-${name}-sdk`;
|
|
26
|
+
}
|
|
27
|
+
function makeTypeName(name) {
|
|
28
|
+
let typeName = name.replace(/_/g, " ");
|
|
29
|
+
typeName = typeName.replace(/\w\S*/g, common_1.toTitleCase);
|
|
30
|
+
return typeName.replace(/ /g, "") + "Param";
|
|
31
|
+
}
|
|
32
|
+
const systemPrefixes = {
|
|
33
|
+
"firebaseextensions.v1beta.function": "_FUNCTION",
|
|
34
|
+
"firebaseextensions.v1beta.v2function": "_V2FUNCTION",
|
|
35
|
+
FUNCTION: "firebaseextensions.v1beta.function",
|
|
36
|
+
V2FUNCTION: "firebaseextensions.v1beta.v2function",
|
|
37
|
+
};
|
|
38
|
+
function convertSystemPrefix(prefix) {
|
|
39
|
+
return systemPrefixes[prefix];
|
|
40
|
+
}
|
|
41
|
+
function makeSystemTypeName(name) {
|
|
42
|
+
if (name.includes("/")) {
|
|
43
|
+
const prefix = name.split("/")[0];
|
|
44
|
+
let typeName = name.split("/")[1];
|
|
45
|
+
typeName = typeName.replace(/([A-Z])/g, " $1").trim();
|
|
46
|
+
typeName = `${convertSystemPrefix(prefix)}_${typeName}`;
|
|
47
|
+
return `System${makeTypeName(typeName)}`;
|
|
48
|
+
}
|
|
49
|
+
return makeTypeName(name);
|
|
50
|
+
}
|
|
51
|
+
function makeSystemParamName(name) {
|
|
52
|
+
if (name.includes("/")) {
|
|
53
|
+
const prefix = name.split("/")[0];
|
|
54
|
+
let paramName = name.split("/")[1];
|
|
55
|
+
paramName = paramName.replace(/([A-Z])/g, " $1").trim();
|
|
56
|
+
paramName = paramName.toUpperCase();
|
|
57
|
+
paramName = paramName.replace(/ /g, "_");
|
|
58
|
+
return `${convertSystemPrefix(prefix)}_${paramName}`;
|
|
59
|
+
}
|
|
60
|
+
return name;
|
|
61
|
+
}
|
|
62
|
+
function makeClassName(name) {
|
|
63
|
+
let className = name.replace(/[_-]/g, " ");
|
|
64
|
+
className = className.replace(/\w\S*/g, common_1.toTitleCase);
|
|
65
|
+
return className.replace(/ /g, "");
|
|
66
|
+
}
|
|
67
|
+
function makeEventName(name, prefix) {
|
|
68
|
+
let eventName;
|
|
69
|
+
const versionedEvent = /^(?:[^.]+[.])+(?:[vV]\d+[.])(?<event>.*)$/;
|
|
70
|
+
const match = name.match(versionedEvent);
|
|
71
|
+
if (match) {
|
|
72
|
+
eventName = match[1];
|
|
73
|
+
}
|
|
74
|
+
else if (prefix.length < name.length) {
|
|
75
|
+
eventName = name.substring(prefix.length);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const parts = name.split(".");
|
|
79
|
+
eventName = parts[parts.length - 1];
|
|
80
|
+
}
|
|
81
|
+
const allCaps = /^[A-Z._-]+$/;
|
|
82
|
+
eventName = eventName.match(allCaps) ? eventName : eventName.replace(/([A-Z])/g, " $1").trim();
|
|
83
|
+
eventName = eventName.replace(/[._-]/g, " ");
|
|
84
|
+
eventName = eventName.toLowerCase().startsWith("on") ? eventName : "on " + eventName;
|
|
85
|
+
eventName = eventName.replace(/\w\S*/g, common_1.toTitleCase);
|
|
86
|
+
eventName = eventName.replace(/ /g, "");
|
|
87
|
+
eventName = eventName.charAt(0).toLowerCase() + eventName.substring(1);
|
|
88
|
+
return eventName;
|
|
89
|
+
}
|
|
90
|
+
async function writeSDK(extensionRef, localPath, spec, options) {
|
|
91
|
+
var _a, _b, _c;
|
|
92
|
+
const sdkLines = [];
|
|
93
|
+
const className = makeClassName(spec.name);
|
|
94
|
+
let dirPath;
|
|
95
|
+
if (extensionRef) {
|
|
96
|
+
dirPath = path.join((0, common_1.getInstallPathPrefix)(options), extensionRef.replace("@", "/"));
|
|
97
|
+
}
|
|
98
|
+
else if (localPath) {
|
|
99
|
+
dirPath = path.join((0, common_1.getInstallPathPrefix)(options), "local", spec.name, spec.version);
|
|
100
|
+
if (await (0, prompt_1.confirm)({
|
|
101
|
+
message: `Copy local extension source to deployment directory? (required for successful deploy)`,
|
|
102
|
+
nonInteractive: options.nonInteractive,
|
|
103
|
+
force: options.force,
|
|
104
|
+
default: true,
|
|
105
|
+
})) {
|
|
106
|
+
const newLocalPath = path.join(dirPath, "src");
|
|
107
|
+
await (0, common_1.copyDirectory)(localPath, newLocalPath, options);
|
|
108
|
+
localPath = newLocalPath.replace(options.projectRoot, ".");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!dirPath) {
|
|
112
|
+
throw new error_1.FirebaseError("Invalid extension definition. Must have either extensionRef or localPath");
|
|
113
|
+
}
|
|
114
|
+
const packageName = makePackageName(extensionRef, spec.name);
|
|
115
|
+
const pkgJson = {
|
|
116
|
+
name: packageName,
|
|
117
|
+
version: `${exports.SDK_GENERATION_VERSION}`,
|
|
118
|
+
description: `Generated SDK for ${spec.displayName}@${spec.version}`,
|
|
119
|
+
main: "./output/index.js",
|
|
120
|
+
private: true,
|
|
121
|
+
scripts: {
|
|
122
|
+
build: "tsc",
|
|
123
|
+
"build:watch": "npm run build && tsc --watch",
|
|
124
|
+
},
|
|
125
|
+
devDependencies: {
|
|
126
|
+
typescript: exports.TYPESCRIPT_VERSION,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
const tsconfigJson = {
|
|
130
|
+
compilerOptions: {
|
|
131
|
+
declaration: true,
|
|
132
|
+
declarationMap: true,
|
|
133
|
+
module: "commonjs",
|
|
134
|
+
strict: true,
|
|
135
|
+
target: "es2017",
|
|
136
|
+
removeComments: false,
|
|
137
|
+
outDir: "output",
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
sdkLines.push("/**");
|
|
141
|
+
sdkLines.push(` * ${spec.displayName} SDK for ${spec.name}@${spec.version}`);
|
|
142
|
+
sdkLines.push(" *");
|
|
143
|
+
sdkLines.push(" * When filing bugs or feature requests please specify:");
|
|
144
|
+
if (extensionRef) {
|
|
145
|
+
sdkLines.push(` * "Extensions SDK v${exports.SDK_GENERATION_VERSION} for ${spec.name}@${spec.version}"`);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
sdkLines.push(` * "Extensions SDK v${exports.SDK_GENERATION_VERSION} for Local extension.`);
|
|
149
|
+
}
|
|
150
|
+
sdkLines.push(" * https://github.com/firebase/firebase-tools/issues/new/choose");
|
|
151
|
+
sdkLines.push(" *");
|
|
152
|
+
sdkLines.push(" * GENERATED FILE. DO NOT EDIT.");
|
|
153
|
+
sdkLines.push(" */\n");
|
|
154
|
+
const hasEvents = spec.events && spec.events.length > 0;
|
|
155
|
+
if (hasEvents) {
|
|
156
|
+
sdkLines.push(`import { CloudEvent } from "firebase-functions/v2";`);
|
|
157
|
+
sdkLines.push(`import { onCustomEventPublished, EventarcTriggerOptions } from "firebase-functions/v2/eventarc";`);
|
|
158
|
+
if (!pkgJson.peerDependencies) {
|
|
159
|
+
pkgJson.peerDependencies = {};
|
|
160
|
+
}
|
|
161
|
+
pkgJson.peerDependencies["firebase-functions"] = exports.FIREBASE_FUNCTIONS_VERSION;
|
|
162
|
+
}
|
|
163
|
+
const usesSecrets = secretsUtils.usesSecrets(spec);
|
|
164
|
+
if (usesSecrets) {
|
|
165
|
+
sdkLines.push(`import { defineSecret } from "firebase-functions/params";`);
|
|
166
|
+
if (!pkgJson.peerDependencies) {
|
|
167
|
+
pkgJson.peerDependencies = {};
|
|
168
|
+
}
|
|
169
|
+
pkgJson.peerDependencies["firebase-functions"] = exports.FIREBASE_FUNCTIONS_VERSION;
|
|
170
|
+
}
|
|
171
|
+
if (hasEvents || usesSecrets) {
|
|
172
|
+
sdkLines.push("");
|
|
173
|
+
}
|
|
174
|
+
if (hasEvents) {
|
|
175
|
+
sdkLines.push(`export type EventCallback<T> = (event: CloudEvent<T>) => unknown | Promise<unknown>;`);
|
|
176
|
+
sdkLines.push(`export type SimpleEventarcTriggerOptions = Omit<EventarcTriggerOptions, 'eventType' | 'channel' | 'region'>;`);
|
|
177
|
+
sdkLines.push(`export type EventArcRegionType = "${askUserForEventsConfig_1.ALLOWED_EVENT_ARC_REGIONS.join('" | "')}";`);
|
|
178
|
+
}
|
|
179
|
+
if (usesSecrets) {
|
|
180
|
+
sdkLines.push("export type SecretParam = ReturnType<typeof defineSecret>;");
|
|
181
|
+
}
|
|
182
|
+
if (spec.params && Array.isArray(spec.params) && spec.params.length > 0) {
|
|
183
|
+
for (const param of spec.params) {
|
|
184
|
+
let line;
|
|
185
|
+
if (param.type === types_1.ParamType.SELECT ||
|
|
186
|
+
param.type === types_1.ParamType.MULTISELECT ||
|
|
187
|
+
param.type === extensionsHelper_1.SpecParamType.SELECT ||
|
|
188
|
+
param.type === extensionsHelper_1.SpecParamType.MULTISELECT) {
|
|
189
|
+
line = `export type ${makeTypeName(param.param)} =`;
|
|
190
|
+
(_a = param.options) === null || _a === void 0 ? void 0 : _a.forEach((opt, i) => {
|
|
191
|
+
if (i === 0) {
|
|
192
|
+
line = line.concat(` "${opt.value}"`);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
line = line.concat(` | "${opt.value}"`);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
line = line.concat(";");
|
|
199
|
+
sdkLines.push(line);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
sdkLines.push("");
|
|
204
|
+
if (spec.systemParams && Array.isArray(spec.systemParams) && spec.systemParams.length > 0) {
|
|
205
|
+
for (const sysParam of spec.systemParams) {
|
|
206
|
+
let line;
|
|
207
|
+
if (sysParam.type === types_1.ParamType.SELECT || sysParam.type === types_1.ParamType.MULTISELECT) {
|
|
208
|
+
line = `export type ${makeSystemTypeName(sysParam.param)} =`;
|
|
209
|
+
(_b = sysParam.options) === null || _b === void 0 ? void 0 : _b.forEach((opt, i) => {
|
|
210
|
+
if (i === 0) {
|
|
211
|
+
line = line.concat(` "${opt.value}"`);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
line = line.concat(` | "${opt.value}"`);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
line = line.concat(";");
|
|
218
|
+
sdkLines.push(line);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
sdkLines.push("");
|
|
223
|
+
sdkLines.push("/**");
|
|
224
|
+
sdkLines.push(` * Parameters for ${spec.name}@${spec.version} extension`);
|
|
225
|
+
sdkLines.push(" */");
|
|
226
|
+
sdkLines.push(`export interface ${className}Params {`);
|
|
227
|
+
for (const param of spec.params) {
|
|
228
|
+
const opt = param.required ? "" : "?";
|
|
229
|
+
sdkLines.push(" /**");
|
|
230
|
+
sdkLines.push(` * ${param.label}`);
|
|
231
|
+
if (param.validationRegex && !param.validationRegex.includes("*/")) {
|
|
232
|
+
sdkLines.push(` * - Validation regex: ${param.validationRegex}`);
|
|
233
|
+
}
|
|
234
|
+
sdkLines.push(" */");
|
|
235
|
+
switch (param.type) {
|
|
236
|
+
case types_1.ParamType.STRING:
|
|
237
|
+
case extensionsHelper_1.SpecParamType.STRING:
|
|
238
|
+
sdkLines.push(` ${param.param}${opt}: string;`);
|
|
239
|
+
break;
|
|
240
|
+
case types_1.ParamType.MULTISELECT:
|
|
241
|
+
case extensionsHelper_1.SpecParamType.MULTISELECT:
|
|
242
|
+
sdkLines.push(` ${param.param}${opt}: ${makeTypeName(param.param)}[];`);
|
|
243
|
+
break;
|
|
244
|
+
case types_1.ParamType.SELECT:
|
|
245
|
+
case extensionsHelper_1.SpecParamType.SELECT:
|
|
246
|
+
sdkLines.push(` ${param.param}${opt}: ${makeTypeName(param.param)};`);
|
|
247
|
+
break;
|
|
248
|
+
case types_1.ParamType.SECRET:
|
|
249
|
+
case extensionsHelper_1.SpecParamType.SECRET:
|
|
250
|
+
sdkLines.push(` ${param.param}${opt}: SecretParam;`);
|
|
251
|
+
break;
|
|
252
|
+
case types_1.ParamType.SELECT_RESOURCE:
|
|
253
|
+
case extensionsHelper_1.SpecParamType.SELECTRESOURCE:
|
|
254
|
+
sdkLines.push(` ${param.param}${opt}: string;`);
|
|
255
|
+
break;
|
|
256
|
+
default:
|
|
257
|
+
sdkLines.push(` ${param.param}${opt}: string; // Assuming string for unknown type`);
|
|
258
|
+
}
|
|
259
|
+
sdkLines.push("");
|
|
260
|
+
}
|
|
261
|
+
if (hasEvents) {
|
|
262
|
+
sdkLines.push(" /**");
|
|
263
|
+
sdkLines.push(` * Event Arc Region`);
|
|
264
|
+
sdkLines.push(" */");
|
|
265
|
+
sdkLines.push(" _EVENT_ARC_REGION?: EventArcRegionType\n");
|
|
266
|
+
}
|
|
267
|
+
for (const sysParam of spec.systemParams) {
|
|
268
|
+
const opt = sysParam.required ? "" : "?";
|
|
269
|
+
sdkLines.push(" /**");
|
|
270
|
+
sdkLines.push(` * ${sysParam.label}`);
|
|
271
|
+
if (sysParam.validationRegex && !sysParam.validationRegex.includes("*/")) {
|
|
272
|
+
sdkLines.push(` * - Validation regex: ${sysParam.validationRegex}`);
|
|
273
|
+
}
|
|
274
|
+
sdkLines.push(" */");
|
|
275
|
+
switch (sysParam.type) {
|
|
276
|
+
case types_1.ParamType.STRING:
|
|
277
|
+
sdkLines.push(` ${makeSystemParamName(sysParam.param)}${opt}: string;`);
|
|
278
|
+
break;
|
|
279
|
+
case types_1.ParamType.MULTISELECT:
|
|
280
|
+
sdkLines.push(` ${makeSystemParamName(sysParam.param)}${opt}: ${makeSystemTypeName(sysParam.param)}[];`);
|
|
281
|
+
break;
|
|
282
|
+
case types_1.ParamType.SELECT:
|
|
283
|
+
sdkLines.push(` ${makeSystemParamName(sysParam.param)}${opt}: ${makeSystemTypeName(sysParam.param)};`);
|
|
284
|
+
break;
|
|
285
|
+
case types_1.ParamType.SECRET:
|
|
286
|
+
sdkLines.push(` ${makeSystemParamName(sysParam.param)}${opt}: SecretParam;`);
|
|
287
|
+
break;
|
|
288
|
+
case types_1.ParamType.SELECT_RESOURCE:
|
|
289
|
+
sdkLines.push(` ${sysParam.param}${opt}: string;`);
|
|
290
|
+
break;
|
|
291
|
+
default:
|
|
292
|
+
throw new error_1.FirebaseError(`Error: Unknown systemParam type: ${sysParam.type}.`);
|
|
293
|
+
}
|
|
294
|
+
sdkLines.push("");
|
|
295
|
+
}
|
|
296
|
+
sdkLines.push("}\n");
|
|
297
|
+
const lowerClassName = (0, common_1.lowercaseFirstLetter)(className);
|
|
298
|
+
sdkLines.push(`export function ${lowerClassName}(instanceId: string, params: ${className}Params) {`);
|
|
299
|
+
sdkLines.push(` return new ${className}(instanceId, params);`);
|
|
300
|
+
sdkLines.push("}\n");
|
|
301
|
+
sdkLines.push(`/**`);
|
|
302
|
+
sdkLines.push(` * ${spec.displayName}`);
|
|
303
|
+
(_c = spec.description) === null || _c === void 0 ? void 0 : _c.split("\n").forEach((val) => {
|
|
304
|
+
sdkLines.push(` * ${val.replace(/\*\//g, "* /")}`);
|
|
305
|
+
});
|
|
306
|
+
sdkLines.push(` */`);
|
|
307
|
+
sdkLines.push(`export class ${className} {`);
|
|
308
|
+
if (hasEvents) {
|
|
309
|
+
sdkLines.push(` events: string[] = [];`);
|
|
310
|
+
}
|
|
311
|
+
if (extensionRef) {
|
|
312
|
+
sdkLines.push(` readonly FIREBASE_EXTENSION_REFERENCE = "${extensionRef}";`);
|
|
313
|
+
sdkLines.push(` readonly EXTENSION_VERSION = "${extensionRef.split("@")[1]}";\n`);
|
|
314
|
+
}
|
|
315
|
+
else if (localPath) {
|
|
316
|
+
sdkLines.push(` readonly FIREBASE_EXTENSION_LOCAL_PATH = "${localPath}";`);
|
|
317
|
+
}
|
|
318
|
+
sdkLines.push(` constructor(private instanceId: string, private params: ${className}Params) {}\n`);
|
|
319
|
+
sdkLines.push(` getInstanceId(): string { return this.instanceId; }\n`);
|
|
320
|
+
sdkLines.push(` getParams(): ${className}Params { return this.params; }\n`);
|
|
321
|
+
if (spec.events) {
|
|
322
|
+
const prefix = (0, common_1.longestCommonPrefix)(spec.events.map((e) => e.type));
|
|
323
|
+
for (const event of spec.events) {
|
|
324
|
+
const eventName = makeEventName(event.type, prefix);
|
|
325
|
+
sdkLines.push(" /**");
|
|
326
|
+
sdkLines.push(` * ${event.description}`);
|
|
327
|
+
sdkLines.push(` */`);
|
|
328
|
+
sdkLines.push(` ${eventName}<T = unknown>(callback: EventCallback<T>, options?: SimpleEventarcTriggerOptions) {`);
|
|
329
|
+
sdkLines.push(` this.events.push("${event.type}");`);
|
|
330
|
+
sdkLines.push(` return onCustomEventPublished({`);
|
|
331
|
+
sdkLines.push(` ...options,`);
|
|
332
|
+
sdkLines.push(` "eventType": "${event.type}",`);
|
|
333
|
+
sdkLines.push(' "channel": `projects/locations/${this.params._EVENT_ARC_REGION}/channels/firebase`,');
|
|
334
|
+
sdkLines.push(' "region": `${this.params._EVENT_ARC_REGION}`');
|
|
335
|
+
sdkLines.push(" },");
|
|
336
|
+
sdkLines.push(" callback);");
|
|
337
|
+
sdkLines.push(` }\n`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
sdkLines.push(`}`);
|
|
341
|
+
const shortDirPath = dirPath.replace(process.cwd(), ".");
|
|
342
|
+
await (0, common_1.writeFile)(`${dirPath}/index.ts`, sdkLines.join("\n"), options);
|
|
343
|
+
await (0, common_1.writeFile)(`${dirPath}/package.json`, JSON.stringify(pkgJson, null, 2), options);
|
|
344
|
+
await (0, common_1.writeFile)(`${dirPath}/tsconfig.json`, JSON.stringify(tsconfigJson, null, 2), options);
|
|
345
|
+
(0, utils_1.logLabeledBullet)("extensions", `running 'npm --prefix ${shortDirPath} install'`);
|
|
346
|
+
try {
|
|
347
|
+
(0, child_process_1.execFileSync)("npm", ["--prefix", dirPath, "install"]);
|
|
348
|
+
}
|
|
349
|
+
catch (err) {
|
|
350
|
+
const errMsg = (0, common_1.getErrorMessage)(err, "unknown error");
|
|
351
|
+
throw new error_1.FirebaseError(`Error during npm install in ${shortDirPath}: ${errMsg}`);
|
|
352
|
+
}
|
|
353
|
+
(0, utils_1.logLabeledBullet)("extensions", `running 'npm --prefix ${shortDirPath} run build'`);
|
|
354
|
+
try {
|
|
355
|
+
(0, child_process_1.execFileSync)("npm", ["--prefix", dirPath, "run", "build"]);
|
|
356
|
+
}
|
|
357
|
+
catch (err) {
|
|
358
|
+
const errMsg = (0, common_1.getErrorMessage)(err, "unknown error");
|
|
359
|
+
throw new error_1.FirebaseError(`Error during npm run build in ${shortDirPath}: ${errMsg}`);
|
|
360
|
+
}
|
|
361
|
+
const codebaseDir = (0, common_1.getCodebaseDir)(options);
|
|
362
|
+
const shortCodebaseDir = codebaseDir.replace(process.cwd(), ".");
|
|
363
|
+
let installCmd = "";
|
|
364
|
+
if (await (0, prompt_1.confirm)({
|
|
365
|
+
message: `Do you want to install the SDK with npm now?`,
|
|
366
|
+
nonInteractive: options.nonInteractive,
|
|
367
|
+
force: options.force,
|
|
368
|
+
default: true,
|
|
369
|
+
})) {
|
|
370
|
+
(0, utils_1.logLabeledBullet)("extensions", `running 'npm --prefix ${shortCodebaseDir} install --save ${shortDirPath}'`);
|
|
371
|
+
try {
|
|
372
|
+
(0, child_process_1.execFileSync)("npm", ["--prefix", codebaseDir, "install", "--save", dirPath]);
|
|
373
|
+
}
|
|
374
|
+
catch (err) {
|
|
375
|
+
const errMsg = (0, common_1.getErrorMessage)(err, "unknown error");
|
|
376
|
+
throw new error_1.FirebaseError(`Error during npm install in ${codebaseDir}: ${errMsg}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
installCmd = `npm --prefix ${shortCodebaseDir} install --save ${shortDirPath}`;
|
|
381
|
+
}
|
|
382
|
+
let sampleImport;
|
|
383
|
+
if ((0, common_1.isTypescriptCodebase)(codebaseDir)) {
|
|
384
|
+
sampleImport =
|
|
385
|
+
"```typescript\n" + `import { ${lowerClassName} } from "${packageName}";` + "\n```";
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
sampleImport = "```js\n" + `const { ${lowerClassName} } = require("${packageName}");` + "\n```";
|
|
389
|
+
}
|
|
390
|
+
const prefix = installCmd
|
|
391
|
+
? `\nTo install the SDK to your project run:\n ${installCmd}\n\nThen you `
|
|
392
|
+
: "\nYou ";
|
|
393
|
+
const instructions = prefix +
|
|
394
|
+
`can add this to your codebase to begin using the SDK:\n\n` +
|
|
395
|
+
(0, common_1.fixDarkBlueText)(await (0, marked_1.marked)(sampleImport)) +
|
|
396
|
+
`See also: ${(0, common_1.fixDarkBlueText)(await (0, marked_1.marked)("[Extension SDKs documentation](https://firebase.google.com/docs/extensions/install-extensions?interface=sdk#config)"))}`;
|
|
397
|
+
return instructions;
|
|
398
|
+
}
|
|
399
|
+
exports.writeSDK = writeSDK;
|