firebase-tools 13.18.0 → 13.20.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-sdk-generate.js +5 -2
- package/lib/commands/emulators-start.js +3 -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 +2 -0
- package/lib/commands/init.js +20 -16
- package/lib/commands/setup-emulators-dataconnect.js +0 -14
- package/lib/dataconnect/ensureApis.js +0 -1
- package/lib/dataconnect/fileUtils.js +16 -4
- package/lib/dataconnect/freeTrial.js +8 -6
- package/lib/dataconnect/provisionCloudSql.js +4 -4
- package/lib/dataconnect/types.js +1 -0
- package/lib/dataconnect/webhook.js +31 -0
- package/lib/deploy/dataconnect/deploy.js +2 -0
- package/lib/deploy/dataconnect/prepare.js +2 -0
- package/lib/deploy/dataconnect/release.js +10 -5
- package/lib/deploy/extensions/deploymentSummary.js +3 -2
- package/lib/deploy/extensions/planner.js +32 -3
- package/lib/deploy/extensions/prepare.js +15 -56
- package/lib/deploy/extensions/release.js +11 -10
- package/lib/deploy/extensions/tasks.js +32 -21
- package/lib/deploy/functions/prepare.js +8 -0
- package/lib/deploy/functions/runtimes/node/index.js +5 -0
- package/lib/emulator/commandUtils.js +6 -1
- package/lib/emulator/constants.js +1 -1
- package/lib/emulator/controller.js +17 -3
- package/lib/emulator/dataconnect/pg-gateway/auth/base-auth-flow.js +11 -0
- package/lib/emulator/dataconnect/pg-gateway/auth/cert.js +69 -0
- package/lib/emulator/dataconnect/pg-gateway/auth/index.js +22 -0
- package/lib/emulator/dataconnect/pg-gateway/auth/md5.js +135 -0
- package/lib/emulator/dataconnect/pg-gateway/auth/password.js +65 -0
- package/lib/emulator/dataconnect/pg-gateway/auth/sasl/sasl-mechanism.js +34 -0
- package/lib/emulator/dataconnect/pg-gateway/auth/sasl/scram-sha-256.js +298 -0
- package/lib/emulator/dataconnect/pg-gateway/auth/trust.js +2 -0
- package/lib/emulator/dataconnect/pg-gateway/backend-error.js +75 -0
- package/lib/emulator/dataconnect/pg-gateway/buffer-reader.js +55 -0
- package/lib/emulator/dataconnect/pg-gateway/buffer-writer.js +79 -0
- package/lib/emulator/dataconnect/pg-gateway/connection.js +419 -0
- package/lib/emulator/dataconnect/pg-gateway/connection.types.js +8 -0
- package/lib/emulator/dataconnect/pg-gateway/crypto.js +40 -0
- package/lib/emulator/dataconnect/pg-gateway/duplex.js +53 -0
- package/lib/emulator/dataconnect/pg-gateway/index.js +27 -0
- package/lib/emulator/dataconnect/pg-gateway/message-buffer.js +96 -0
- package/lib/emulator/dataconnect/pg-gateway/message-codes.js +54 -0
- package/lib/emulator/dataconnect/pg-gateway/platforms/node/index.js +13 -0
- package/lib/emulator/dataconnect/pg-gateway/polyfills/readable-stream-async-iterator.js +36 -0
- package/lib/emulator/dataconnect/pg-gateway/utils.js +40 -0
- package/lib/emulator/dataconnect/pgliteServer.js +134 -0
- package/lib/emulator/dataconnectEmulator.js +55 -73
- package/lib/emulator/dataconnectToolkitController.js +44 -0
- package/lib/emulator/downloadableEmulators.js +22 -11
- package/lib/emulator/hub.js +2 -1
- package/lib/emulator/portUtils.js +9 -11
- package/lib/emulator/storage/rules/runtime.js +1 -1
- package/lib/experiments.js +1 -0
- 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/init/features/dataconnect/index.js +148 -111
- package/lib/init/features/dataconnect/sdk.js +40 -27
- package/lib/init/features/emulators.js +2 -14
- package/lib/prompt.js +1 -1
- package/lib/rc.js +1 -9
- package/package.json +3 -1
- package/schema/connector-yaml.json +14 -0
- package/schema/firebase-config.json +6 -0
- package/templates/init/dataconnect/dataconnect-fdccompatiblemode.yaml +1 -1
- package/templates/init/dataconnect/queries.gql +1 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.start = exports.downloadIfNecessary = exports.stop = exports.getPID = exports.get = exports.getDownloadDetails = exports.requiresJava = exports.handleEmulatorProcessError = exports._getCommand = exports.getLogFileName = exports.DownloadDetails = void 0;
|
|
4
|
+
const lsofi = require("lsofi");
|
|
4
5
|
const types_1 = require("./types");
|
|
5
6
|
const constants_1 = require("./constants");
|
|
6
7
|
const error_1 = require("../error");
|
|
@@ -14,6 +15,7 @@ const os = require("os");
|
|
|
14
15
|
const registry_1 = require("./registry");
|
|
15
16
|
const download_1 = require("../emulator/download");
|
|
16
17
|
const experiments = require("../experiments");
|
|
18
|
+
const process = require("process");
|
|
17
19
|
const EMULATOR_INSTANCE_KILL_TIMEOUT = 4000;
|
|
18
20
|
const CACHE_DIR = process.env.FIREBASE_EMULATORS_PATH || path.join(os.homedir(), ".cache", "firebase", "emulators");
|
|
19
21
|
const EMULATOR_UPDATE_DETAILS = {
|
|
@@ -46,20 +48,20 @@ const EMULATOR_UPDATE_DETAILS = {
|
|
|
46
48
|
},
|
|
47
49
|
dataconnect: process.platform === "darwin"
|
|
48
50
|
? {
|
|
49
|
-
version: "1.
|
|
50
|
-
expectedSize:
|
|
51
|
-
expectedChecksum: "
|
|
51
|
+
version: "1.4.2",
|
|
52
|
+
expectedSize: 25125632,
|
|
53
|
+
expectedChecksum: "25c7dfe5816f4bfba82f002bcab10340",
|
|
52
54
|
}
|
|
53
55
|
: process.platform === "win32"
|
|
54
56
|
? {
|
|
55
|
-
version: "1.
|
|
56
|
-
expectedSize:
|
|
57
|
-
expectedChecksum: "
|
|
57
|
+
version: "1.4.2",
|
|
58
|
+
expectedSize: 25548800,
|
|
59
|
+
expectedChecksum: "854ddf17fd9adeafd19531282ccc2a46",
|
|
58
60
|
}
|
|
59
61
|
: {
|
|
60
|
-
version: "1.
|
|
61
|
-
expectedSize:
|
|
62
|
-
expectedChecksum: "
|
|
62
|
+
version: "1.4.2",
|
|
63
|
+
expectedSize: 25034904,
|
|
64
|
+
expectedChecksum: "ed21d946879647f24fade5cdd93c9eb3",
|
|
63
65
|
},
|
|
64
66
|
};
|
|
65
67
|
exports.DownloadDetails = {
|
|
@@ -295,6 +297,7 @@ function _getCommand(emulator, args) {
|
|
|
295
297
|
optionalArgs: baseCmd.optionalArgs,
|
|
296
298
|
joinArgs: baseCmd.joinArgs,
|
|
297
299
|
shell: baseCmd.shell,
|
|
300
|
+
port: args.port,
|
|
298
301
|
};
|
|
299
302
|
}
|
|
300
303
|
exports._getCommand = _getCommand;
|
|
@@ -308,11 +311,15 @@ async function _fatal(emulator, errorMsg) {
|
|
|
308
311
|
process.exit(1);
|
|
309
312
|
}
|
|
310
313
|
}
|
|
311
|
-
async function handleEmulatorProcessError(emulator, err) {
|
|
314
|
+
async function handleEmulatorProcessError(emulator, err, port) {
|
|
312
315
|
const description = constants_1.Constants.description(emulator);
|
|
313
316
|
if (err.path === "java" && err.code === "ENOENT") {
|
|
314
317
|
await _fatal(emulator, `${description} has exited because java is not installed, you can install it from https://openjdk.java.net/install/`);
|
|
315
318
|
}
|
|
319
|
+
else if (err.code === "EADDRINUSE") {
|
|
320
|
+
const ps = port ? await lsofi(port) : false;
|
|
321
|
+
await _fatal(emulator, `${description} has exited because its configured port is already in use${ps ? ` by process number ${ps}` : ""}. Are you running another copy of the emulator suite?`);
|
|
322
|
+
}
|
|
316
323
|
else {
|
|
317
324
|
await _fatal(emulator, `${description} has exited: ${err}`);
|
|
318
325
|
}
|
|
@@ -366,9 +373,13 @@ async function _runBinary(emulator, command, extraEnv) {
|
|
|
366
373
|
if (data.toString().includes("java.lang.UnsupportedClassVersionError")) {
|
|
367
374
|
logger.logLabeled("WARN", emulator.name, "Unsupported java version, make sure java --version reports 1.8 or higher.");
|
|
368
375
|
}
|
|
376
|
+
if (data.toString().includes("address already in use")) {
|
|
377
|
+
const message = `${description} has exited because its configured port ${command.port} is already in use. Are you running another copy of the emulator suite?`;
|
|
378
|
+
logger.logLabeled("ERROR", emulator.name, message);
|
|
379
|
+
}
|
|
369
380
|
});
|
|
370
381
|
emulator.instance.on("error", (err) => {
|
|
371
|
-
handleEmulatorProcessError(emulator.name, err);
|
|
382
|
+
void handleEmulatorProcessError(emulator.name, err, command.port);
|
|
372
383
|
});
|
|
373
384
|
emulator.instance.once("exit", async (code, signal) => {
|
|
374
385
|
if (signal) {
|
package/lib/emulator/hub.js
CHANGED
|
@@ -10,6 +10,7 @@ const types_1 = require("./types");
|
|
|
10
10
|
const hubExport_1 = require("./hubExport");
|
|
11
11
|
const registry_1 = require("./registry");
|
|
12
12
|
const ExpressBasedEmulator_1 = require("./ExpressBasedEmulator");
|
|
13
|
+
const utils_1 = require("../utils");
|
|
13
14
|
const pkg = require("../../package.json");
|
|
14
15
|
class EmulatorHub extends ExpressBasedEmulator_1.ExpressBasedEmulator {
|
|
15
16
|
static readLocatorFile(projectId) {
|
|
@@ -19,7 +20,7 @@ class EmulatorHub extends ExpressBasedEmulator_1.ExpressBasedEmulator {
|
|
|
19
20
|
}
|
|
20
21
|
const data = fs.readFileSync(locatorPath, "utf8").toString();
|
|
21
22
|
const locator = JSON.parse(data);
|
|
22
|
-
if (locator.version !== this.CLI_VERSION) {
|
|
23
|
+
if (!utils_1.isVSCodeExtension && locator.version !== this.CLI_VERSION) {
|
|
23
24
|
logger_1.logger.debug(`Found locator with mismatched version, ignoring: ${JSON.stringify(locator)}`);
|
|
24
25
|
return undefined;
|
|
25
26
|
}
|
|
@@ -11,7 +11,6 @@ const types_1 = require("./types");
|
|
|
11
11
|
const constants_1 = require("./constants");
|
|
12
12
|
const emulatorLogger_1 = require("./emulatorLogger");
|
|
13
13
|
const node_child_process_1 = require("node:child_process");
|
|
14
|
-
const dataconnectEmulator_1 = require("./dataconnectEmulator");
|
|
15
14
|
const RESTRICTED_PORTS = new Set([
|
|
16
15
|
1,
|
|
17
16
|
7,
|
|
@@ -147,6 +146,7 @@ const EMULATOR_CAN_LISTEN_ON_PRIMARY_ONLY = {
|
|
|
147
146
|
firestore: true,
|
|
148
147
|
"firestore.websocket": true,
|
|
149
148
|
pubsub: true,
|
|
149
|
+
"dataconnect.postgres": true,
|
|
150
150
|
dataconnect: false,
|
|
151
151
|
hub: false,
|
|
152
152
|
ui: false,
|
|
@@ -185,7 +185,11 @@ async function resolveHostAndAssignPorts(listenConfig) {
|
|
|
185
185
|
lookupForHost.set(host, lookup);
|
|
186
186
|
}
|
|
187
187
|
const findAddrs = lookup.then(async (addrs) => {
|
|
188
|
-
const emuLogger = emulatorLogger_1.EmulatorLogger.forEmulator(name === "firestore.websocket"
|
|
188
|
+
const emuLogger = emulatorLogger_1.EmulatorLogger.forEmulator(name === "firestore.websocket"
|
|
189
|
+
? types_1.Emulators.FIRESTORE
|
|
190
|
+
: name === "dataconnect.postgres"
|
|
191
|
+
? types_1.Emulators.DATACONNECT
|
|
192
|
+
: name);
|
|
189
193
|
if (addrs.some((addr) => addr.address === dns_1.IPV6_UNSPECIFIED.address)) {
|
|
190
194
|
if (!addrs.some((addr) => addr.address === dns_1.IPV4_UNSPECIFIED.address)) {
|
|
191
195
|
emuLogger.logLabeled("DEBUG", name, `testing listening on IPv4 wildcard in addition to IPv6. To listen on IPv6 only, use "::0" instead.`);
|
|
@@ -222,14 +226,6 @@ async function resolveHostAndAssignPorts(listenConfig) {
|
|
|
222
226
|
available.push(listen);
|
|
223
227
|
}
|
|
224
228
|
else {
|
|
225
|
-
if (/^dataconnect/i.exec(name)) {
|
|
226
|
-
const alreadyRunning = await (0, dataconnectEmulator_1.checkIfDataConnectEmulatorRunningOnAddress)(listen);
|
|
227
|
-
if (alreadyRunning) {
|
|
228
|
-
emuLogger.logLabeled("DEBUG", "dataconnect", `Detected already running emulator on ${listen.address}:${listen.port}. Will attempt to reuse it.`);
|
|
229
|
-
}
|
|
230
|
-
available.push(listen);
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
229
|
if (!portFixed) {
|
|
234
230
|
if (i > 0) {
|
|
235
231
|
emuLogger.logLabeled("DEBUG", name, `Port ${p} taken on secondary address ${addr.address}, will keep searching to find a better port.`);
|
|
@@ -281,7 +277,9 @@ exports.resolveHostAndAssignPorts = resolveHostAndAssignPorts;
|
|
|
281
277
|
function portDescription(name) {
|
|
282
278
|
return name === "firestore.websocket"
|
|
283
279
|
? `websocket server for ${types_1.Emulators.FIRESTORE}`
|
|
284
|
-
:
|
|
280
|
+
: name === "dataconnect.postgres"
|
|
281
|
+
? `postgres server for ${types_1.Emulators.DATACONNECT}`
|
|
282
|
+
: constants_1.Constants.description(name);
|
|
285
283
|
}
|
|
286
284
|
function warnPartiallyAvailablePort(emuLogger, port, available, unavailable) {
|
|
287
285
|
emuLogger.logLabeled("WARN", `Port ${port} is available on ` +
|
|
@@ -109,7 +109,7 @@ class StorageRulesRuntime {
|
|
|
109
109
|
};
|
|
110
110
|
});
|
|
111
111
|
this._childprocess.on("error", (err) => {
|
|
112
|
-
(0, downloadableEmulators_1.handleEmulatorProcessError)(types_2.Emulators.STORAGE, err);
|
|
112
|
+
void (0, downloadableEmulators_1.handleEmulatorProcessError)(types_2.Emulators.STORAGE, err);
|
|
113
113
|
});
|
|
114
114
|
(_a = this._childprocess.stderr) === null || _a === void 0 ? void 0 : _a.on("data", (buf) => {
|
|
115
115
|
const error = buf.toString();
|
package/lib/experiments.js
CHANGED
|
@@ -109,6 +109,7 @@ exports.ALL_EXPERIMENTS = experiments({
|
|
|
109
109
|
fdccompatiblemode: {
|
|
110
110
|
shortDescription: "Enable Data Connect schema migrations in Compatible Mode",
|
|
111
111
|
fullDescription: "Enable Data Connect schema migrations in Compatible Mode",
|
|
112
|
+
default: true,
|
|
112
113
|
public: false,
|
|
113
114
|
},
|
|
114
115
|
});
|
|
@@ -15,10 +15,11 @@ const extensionsApiClient = new apiv2_1.Client({
|
|
|
15
15
|
urlPrefix: (0, api_1.extensionsOrigin)(),
|
|
16
16
|
apiVersion: EXTENSIONS_API_VERSION,
|
|
17
17
|
});
|
|
18
|
-
async function createInstanceHelper(projectId, instanceId, config, validateOnly = false) {
|
|
18
|
+
async function createInstanceHelper(projectId, instanceId, config, labels, validateOnly = false) {
|
|
19
19
|
const createRes = await extensionsApiClient.post(`/projects/${projectId}/instances/`, {
|
|
20
20
|
name: `projects/${projectId}/instances/${instanceId}`,
|
|
21
21
|
config,
|
|
22
|
+
labels,
|
|
22
23
|
}, {
|
|
23
24
|
queryParams: {
|
|
24
25
|
validateOnly: validateOnly ? "true" : "false",
|
|
@@ -63,7 +64,7 @@ async function createInstance(args) {
|
|
|
63
64
|
if (args.eventarcChannel) {
|
|
64
65
|
config.eventarcChannel = args.eventarcChannel;
|
|
65
66
|
}
|
|
66
|
-
return createInstanceHelper(args.projectId, args.instanceId, config, args.validateOnly);
|
|
67
|
+
return await createInstanceHelper(args.projectId, args.instanceId, config, args.labels, args.validateOnly);
|
|
67
68
|
}
|
|
68
69
|
exports.createInstance = createInstance;
|
|
69
70
|
async function deleteInstance(projectId, instanceId) {
|
|
@@ -7,7 +7,7 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
|
7
7
|
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
8
8
|
};
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.diagnoseAndFixProject = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = exports.promptForOfficialExtension = exports.displayReleaseNotes = exports.getPublisherProjectFromName = exports.createSourceFromLocation = exports.getMissingPublisherError = exports.uploadExtensionVersionFromLocalSource = exports.uploadExtensionVersionFromGitHubSource = exports.unpackExtensionState = exports.getNextVersionByStage = exports.ensureExtensionsPublisherApiEnabled = exports.ensureExtensionsApiEnabled = exports.checkExtensionsApiEnabled = exports.promptForExtensionRoot = exports.promptForValidRepoURI = exports.promptForValidInstanceId = exports.validateSpec = exports.validateCommandLineParams = exports.populateDefaultParams = exports.substituteSecretParams = exports.substituteParams = exports.getFirebaseProjectParams = exports.getDBInstanceFromURL = exports.resourceTypeToNiceName = exports.
|
|
10
|
+
exports.diagnoseAndFixProject = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = exports.promptForOfficialExtension = exports.displayReleaseNotes = exports.getPublisherProjectFromName = exports.createSourceFromLocation = exports.getMissingPublisherError = exports.uploadExtensionVersionFromLocalSource = exports.uploadExtensionVersionFromGitHubSource = exports.unpackExtensionState = exports.getNextVersionByStage = exports.ensureExtensionsPublisherApiEnabled = exports.ensureExtensionsApiEnabled = exports.checkExtensionsApiEnabled = exports.promptForExtensionRoot = exports.promptForValidRepoURI = exports.promptForValidInstanceId = exports.validateSpec = exports.validateCommandLineParams = exports.populateDefaultParams = exports.substituteSecretParams = exports.substituteParams = exports.getFirebaseProjectParams = exports.getDBInstanceFromURL = exports.resourceTypeToNiceName = exports.AUTOPOPULATED_PARAM_PLACEHOLDERS = exports.EXTENSIONS_BUCKET_NAME = exports.URL_REGEX = exports.logPrefix = exports.SourceOrigin = exports.SpecParamType = void 0;
|
|
11
11
|
const clc = require("colorette");
|
|
12
12
|
const ora = require("ora");
|
|
13
13
|
const semver = require("semver");
|
|
@@ -69,7 +69,7 @@ const AUTOPOPULATED_PARAM_NAMES = [
|
|
|
69
69
|
"DATABASE_INSTANCE",
|
|
70
70
|
"DATABASE_URL",
|
|
71
71
|
];
|
|
72
|
-
exports.
|
|
72
|
+
exports.AUTOPOPULATED_PARAM_PLACEHOLDERS = {
|
|
73
73
|
PROJECT_ID: "project-id",
|
|
74
74
|
STORAGE_BUCKET: "project-id.appspot.com",
|
|
75
75
|
EXT_INSTANCE_ID: "extension-id",
|
|
@@ -449,7 +449,7 @@ async function validateExtensionSpec(rootDirectory, extensionId) {
|
|
|
449
449
|
throw new error_1.FirebaseError(`Extension ID '${clc.bold(extensionId)}' does not match the name in extension.yaml '${clc.bold(extensionSpec.name)}'.`);
|
|
450
450
|
}
|
|
451
451
|
const subbedSpec = JSON.parse(JSON.stringify(extensionSpec));
|
|
452
|
-
subbedSpec.params = substituteParams(extensionSpec.params || [], exports.
|
|
452
|
+
subbedSpec.params = substituteParams(extensionSpec.params || [], exports.AUTOPOPULATED_PARAM_PLACEHOLDERS);
|
|
453
453
|
validateSpec(subbedSpec);
|
|
454
454
|
return extensionSpec;
|
|
455
455
|
}
|
|
@@ -6,11 +6,20 @@ const path = require("path");
|
|
|
6
6
|
const yaml = require("yaml");
|
|
7
7
|
const fsutils_1 = require("../fsutils");
|
|
8
8
|
const error_1 = require("../error");
|
|
9
|
+
const types_1 = require("./types");
|
|
9
10
|
const logger_1 = require("../logger");
|
|
11
|
+
const extensionsHelper_1 = require("./extensionsHelper");
|
|
10
12
|
exports.EXTENSIONS_SPEC_FILE = "extension.yaml";
|
|
11
13
|
const EXTENSIONS_PREINSTALL_FILE = "PREINSTALL.md";
|
|
12
14
|
async function getLocalExtensionSpec(directory) {
|
|
13
15
|
const spec = await parseYAML(readFile(path.resolve(directory, exports.EXTENSIONS_SPEC_FILE)));
|
|
16
|
+
if (spec.lifecycleEvents) {
|
|
17
|
+
spec.lifecycleEvents = fixLifecycleEvents(spec.lifecycleEvents);
|
|
18
|
+
}
|
|
19
|
+
if (!(0, types_1.isExtensionSpec)(spec)) {
|
|
20
|
+
(0, extensionsHelper_1.validateSpec)(spec);
|
|
21
|
+
throw new error_1.FirebaseError("Error: extension.yaml does not contain a valid extension specification.");
|
|
22
|
+
}
|
|
14
23
|
try {
|
|
15
24
|
const preinstall = readFile(path.resolve(directory, EXTENSIONS_PREINSTALL_FILE));
|
|
16
25
|
spec.preinstallContent = preinstall;
|
|
@@ -21,6 +30,28 @@ async function getLocalExtensionSpec(directory) {
|
|
|
21
30
|
return spec;
|
|
22
31
|
}
|
|
23
32
|
exports.getLocalExtensionSpec = getLocalExtensionSpec;
|
|
33
|
+
function fixLifecycleEvents(lifecycleEvents) {
|
|
34
|
+
const stages = {
|
|
35
|
+
onInstall: "ON_INSTALL",
|
|
36
|
+
onUpdate: "ON_UPDATE",
|
|
37
|
+
onConfigure: "ON_CONFIGURE",
|
|
38
|
+
stageUnspecified: "STAGE_UNSPECIFIED",
|
|
39
|
+
};
|
|
40
|
+
const arrayLifecycle = [];
|
|
41
|
+
if ((0, types_1.isObject)(lifecycleEvents)) {
|
|
42
|
+
for (const [key, val] of Object.entries(lifecycleEvents)) {
|
|
43
|
+
if ((0, types_1.isObject)(val) &&
|
|
44
|
+
typeof val.function === "string" &&
|
|
45
|
+
typeof val.processingMessage === "string") {
|
|
46
|
+
arrayLifecycle.push({
|
|
47
|
+
stage: stages[key] || stages["stageUnspecified"],
|
|
48
|
+
taskQueueTriggerFunction: val.function,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return arrayLifecycle;
|
|
54
|
+
}
|
|
24
55
|
function findExtensionYaml(directory) {
|
|
25
56
|
while (!(0, fsutils_1.fileExistsSync)(path.resolve(directory, exports.EXTENSIONS_SPEC_FILE))) {
|
|
26
57
|
const parentDir = path.dirname(directory);
|
|
@@ -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;
|