firebase-tools 14.12.1 → 14.13.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 +1 -1
- package/lib/commands/dataconnect-services-list.js +5 -5
- package/lib/commands/dataconnect-sql-grant.js +5 -0
- package/lib/commands/dataconnect-sql-setup.js +1 -3
- package/lib/crashlytics/getIssueDetails.js +41 -0
- package/lib/crashlytics/getSampleCrash.js +48 -0
- package/lib/dataconnect/client.js +23 -15
- package/lib/dataconnect/ensureApis.js +5 -9
- package/lib/dataconnect/fileUtils.js +5 -6
- package/lib/dataconnect/freeTrial.js +16 -39
- package/lib/dataconnect/provisionCloudSql.js +67 -70
- package/lib/dataconnect/schemaMigration.js +75 -47
- package/lib/deploy/dataconnect/deploy.js +9 -11
- package/lib/deploy/dataconnect/prepare.js +7 -10
- package/lib/deploy/dataconnect/release.js +11 -5
- package/lib/deploy/functions/backend.js +8 -2
- package/lib/deploy/functions/build.js +23 -1
- package/lib/deploy/functions/ensure.js +1 -1
- package/lib/deploy/functions/functionsDeployHelper.js +8 -1
- package/lib/deploy/functions/prepare.js +6 -4
- package/lib/deploy/functions/pricing.js +12 -5
- package/lib/deploy/functions/release/fabricator.js +25 -3
- package/lib/emulator/controller.js +2 -1
- package/lib/emulator/downloadableEmulatorInfo.json +18 -18
- package/lib/emulator/functionsEmulator.js +9 -1
- package/lib/experiments.js +4 -0
- package/lib/extensions/extensionsHelper.js +4 -15
- package/lib/extensions/utils.js +1 -12
- package/lib/firestore/api.js +25 -11
- package/lib/firestore/pretty-print.js +7 -0
- package/lib/functional.js +7 -1
- package/lib/functions/projectConfig.js +25 -2
- package/lib/functions/secrets.js +3 -0
- package/lib/gcp/cloudfunctionsv2.js +3 -31
- package/lib/gcp/cloudscheduler.js +1 -1
- package/lib/gcp/cloudsql/cloudsqladmin.js +2 -14
- package/lib/gcp/cloudsql/connect.js +1 -1
- package/lib/gcp/cloudsql/permissionsSetup.js +13 -15
- package/lib/gcp/k8s.js +32 -0
- package/lib/gcp/runv2.js +178 -0
- package/lib/gemini/fdcExperience.js +5 -3
- package/lib/init/features/dataconnect/index.js +266 -162
- package/lib/init/features/dataconnect/sdk.js +32 -16
- package/lib/init/features/project.js +4 -0
- package/lib/management/studio.js +1 -1
- package/lib/mcp/tools/core/init.js +7 -6
- package/lib/mcp/tools/crashlytics/get_issue_details.js +33 -0
- package/lib/mcp/tools/crashlytics/get_sample_crash.js +43 -0
- package/lib/mcp/tools/crashlytics/index.js +7 -1
- package/lib/mcp/tools/crashlytics/list_top_issues.js +2 -1
- package/lib/rtdb.js +1 -1
- package/package.json +1 -1
- package/schema/firebase-config.json +3 -0
- package/lib/extensions/resolveSource.js +0 -24
|
@@ -54,28 +54,28 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dataconnect": {
|
|
56
56
|
"darwin": {
|
|
57
|
-
"version": "2.11.
|
|
58
|
-
"expectedSize":
|
|
59
|
-
"expectedChecksum": "
|
|
60
|
-
"expectedChecksumSHA256": "
|
|
61
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.11.
|
|
62
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.
|
|
57
|
+
"version": "2.11.2",
|
|
58
|
+
"expectedSize": 29447008,
|
|
59
|
+
"expectedChecksum": "13bc7d3bb0a0bbfe601991361e4413c2",
|
|
60
|
+
"expectedChecksumSHA256": "e3b029eb461f0fe6f0c825c7a71d42c7a09c2b8ee4fac10c3e187d78fe5083f6",
|
|
61
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.11.2",
|
|
62
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.2"
|
|
63
63
|
},
|
|
64
64
|
"win32": {
|
|
65
|
-
"version": "2.11.
|
|
66
|
-
"expectedSize":
|
|
67
|
-
"expectedChecksum": "
|
|
68
|
-
"expectedChecksumSHA256": "
|
|
69
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.11.
|
|
70
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.
|
|
65
|
+
"version": "2.11.2",
|
|
66
|
+
"expectedSize": 29934592,
|
|
67
|
+
"expectedChecksum": "032a0749781fc338b446d753dd543bf5",
|
|
68
|
+
"expectedChecksumSHA256": "2d0498b3ef94b4e777d6fc1d526279376caf3842549f39f13a8ca327393bf810",
|
|
69
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.11.2",
|
|
70
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.2.exe"
|
|
71
71
|
},
|
|
72
72
|
"linux": {
|
|
73
|
-
"version": "2.11.
|
|
74
|
-
"expectedSize":
|
|
75
|
-
"expectedChecksum": "
|
|
76
|
-
"expectedChecksumSHA256": "
|
|
77
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.11.
|
|
78
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.
|
|
73
|
+
"version": "2.11.2",
|
|
74
|
+
"expectedSize": 29368504,
|
|
75
|
+
"expectedChecksum": "065a7523f881952040ac678a9a1e9323",
|
|
76
|
+
"expectedChecksumSHA256": "41ca6561cf77107b5d1d829233844d21c46a5e4bb823c150b1b32591dc2463e0",
|
|
77
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.11.2",
|
|
78
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.2"
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
}
|
|
@@ -28,8 +28,9 @@ const utils_1 = require("../utils");
|
|
|
28
28
|
const adminSdkConfig_1 = require("./adminSdkConfig");
|
|
29
29
|
const validate_1 = require("../deploy/functions/validate");
|
|
30
30
|
const secretManager_1 = require("../gcp/secretManager");
|
|
31
|
-
const runtimes = require("../deploy/functions/runtimes");
|
|
32
31
|
const backend = require("../deploy/functions/backend");
|
|
32
|
+
const build = require("../deploy/functions/build");
|
|
33
|
+
const runtimes = require("../deploy/functions/runtimes");
|
|
33
34
|
const functionsEnv = require("../functions/env");
|
|
34
35
|
const v1_1 = require("../functions/events/v1");
|
|
35
36
|
const build_1 = require("../deploy/functions/build");
|
|
@@ -82,6 +83,7 @@ class FunctionsEmulator {
|
|
|
82
83
|
this.blockingFunctionsConfig = {};
|
|
83
84
|
this.staticBackends = [];
|
|
84
85
|
this.dynamicBackends = [];
|
|
86
|
+
this.watchers = [];
|
|
85
87
|
this.debugMode = false;
|
|
86
88
|
this.staticBackends = args.emulatableBackends;
|
|
87
89
|
emulatorLogger_1.EmulatorLogger.setVerbosity(this.args.verbosity ? emulatorLogger_1.Verbosity[this.args.verbosity] : emulatorLogger_1.Verbosity["DEBUG"]);
|
|
@@ -271,6 +273,7 @@ class FunctionsEmulator {
|
|
|
271
273
|
],
|
|
272
274
|
persistent: true,
|
|
273
275
|
});
|
|
276
|
+
this.watchers.push(watcher);
|
|
274
277
|
const debouncedLoadTriggers = (0, utils_1.debounce)(() => this.loadTriggers(backend), 1000);
|
|
275
278
|
watcher.on("change", (filePath) => {
|
|
276
279
|
this.logger.log("DEBUG", `File ${filePath} changed, reloading triggers`);
|
|
@@ -292,6 +295,10 @@ class FunctionsEmulator {
|
|
|
292
295
|
for (const pool of Object.values(this.workerPools)) {
|
|
293
296
|
pool.exit();
|
|
294
297
|
}
|
|
298
|
+
for (const watcher of this.watchers) {
|
|
299
|
+
await watcher.close();
|
|
300
|
+
}
|
|
301
|
+
this.watchers = [];
|
|
295
302
|
if (this.destroyServer) {
|
|
296
303
|
await this.destroyServer();
|
|
297
304
|
}
|
|
@@ -329,6 +336,7 @@ class FunctionsEmulator {
|
|
|
329
336
|
await this.args.extensionsEmulator.addDynamicExtensions(emulatableBackend.codebase, discoveredBuild);
|
|
330
337
|
await this.loadDynamicExtensionBackends();
|
|
331
338
|
}
|
|
339
|
+
build.applyPrefix(discoveredBuild, emulatableBackend.prefix || "");
|
|
332
340
|
const resolution = await (0, build_1.resolveBackend)({
|
|
333
341
|
build: discoveredBuild,
|
|
334
342
|
firebaseConfig: JSON.parse(firebaseConfig),
|
package/lib/experiments.js
CHANGED
|
@@ -48,6 +48,10 @@ exports.ALL_EXPERIMENTS = experiments({
|
|
|
48
48
|
default: true,
|
|
49
49
|
public: true,
|
|
50
50
|
},
|
|
51
|
+
runfunctions: {
|
|
52
|
+
shortDescription: "Functions created using the V2 API target Cloud Run Functions (not production ready)",
|
|
53
|
+
public: false,
|
|
54
|
+
},
|
|
51
55
|
emulatoruisnapshot: {
|
|
52
56
|
shortDescription: "Load pre-release versions of the emulator UI",
|
|
53
57
|
},
|
|
@@ -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.
|
|
10
|
+
exports.diagnoseAndFixProject = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = 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");
|
|
@@ -21,10 +21,8 @@ const unzip_1 = require("./../unzip");
|
|
|
21
21
|
marked_1.marked.use((0, marked_terminal_1.markedTerminal)());
|
|
22
22
|
const api_1 = require("../api");
|
|
23
23
|
const archiveDirectory_1 = require("../archiveDirectory");
|
|
24
|
-
const utils_1 = require("./utils");
|
|
25
24
|
const functionsConfig_1 = require("../functionsConfig");
|
|
26
25
|
const adminSdkConfig_1 = require("../emulator/adminSdkConfig");
|
|
27
|
-
const resolveSource_1 = require("./resolveSource");
|
|
28
26
|
const error_1 = require("../error");
|
|
29
27
|
const diagnose_1 = require("./diagnose");
|
|
30
28
|
const askUserForParam_1 = require("./askUserForParam");
|
|
@@ -37,7 +35,7 @@ const prompt_1 = require("../prompt");
|
|
|
37
35
|
const refs = require("./refs");
|
|
38
36
|
const localHelper_1 = require("./localHelper");
|
|
39
37
|
const logger_1 = require("../logger");
|
|
40
|
-
const
|
|
38
|
+
const utils_1 = require("../utils");
|
|
41
39
|
const change_log_1 = require("./change-log");
|
|
42
40
|
const getProjectNumber_1 = require("../getProjectNumber");
|
|
43
41
|
const constants_1 = require("../emulator/constants");
|
|
@@ -61,7 +59,7 @@ var SourceOrigin;
|
|
|
61
59
|
exports.logPrefix = "extensions";
|
|
62
60
|
const VALID_LICENSES = ["apache-2.0"];
|
|
63
61
|
exports.URL_REGEX = /^https:/;
|
|
64
|
-
exports.EXTENSIONS_BUCKET_NAME = (0,
|
|
62
|
+
exports.EXTENSIONS_BUCKET_NAME = (0, utils_1.envOverride)("FIREBASE_EXTENSIONS_UPLOAD_BUCKET", "firebase-ext-eap-uploads");
|
|
65
63
|
const AUTOPOPULATED_PARAM_NAMES = [
|
|
66
64
|
"PROJECT_ID",
|
|
67
65
|
"STORAGE_BUCKET",
|
|
@@ -104,7 +102,7 @@ async function getFirebaseProjectParams(projectId, emulatorMode = false) {
|
|
|
104
102
|
projectNumber = await (0, getProjectNumber_1.getProjectNumber)({ projectId });
|
|
105
103
|
}
|
|
106
104
|
catch (err) {
|
|
107
|
-
(0,
|
|
105
|
+
(0, utils_1.logLabeledError)("extensions", `Unable to look up project number for ${projectId}.\n` +
|
|
108
106
|
" If this is a real project, ensure that you are logged in and have access to it.\n" +
|
|
109
107
|
" If this is a fake project, please use a project ID starting with 'demo-' to skip production calls.\n" +
|
|
110
108
|
" Continuing with a fake project number - secrets and other features that require production access may behave unexpectedly.");
|
|
@@ -810,15 +808,6 @@ function displayReleaseNotes(args) {
|
|
|
810
808
|
logger_1.logger.info(message);
|
|
811
809
|
}
|
|
812
810
|
exports.displayReleaseNotes = displayReleaseNotes;
|
|
813
|
-
async function promptForOfficialExtension(message) {
|
|
814
|
-
const officialExts = await (0, resolveSource_1.getExtensionRegistry)(true);
|
|
815
|
-
return await (0, prompt_1.select)({
|
|
816
|
-
message,
|
|
817
|
-
choices: (0, utils_1.convertOfficialExtensionsToList)(officialExts),
|
|
818
|
-
pageSize: Object.keys(officialExts).length,
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
exports.promptForOfficialExtension = promptForOfficialExtension;
|
|
822
811
|
async function promptForRepeatInstance(projectName, extensionName) {
|
|
823
812
|
const message = `An extension with the ID '${clc.bold(extensionName)}' already exists in the project '${clc.bold(projectName)}'. What would you like to do?`;
|
|
824
813
|
return await (0, prompt_1.select)({
|
package/lib/extensions/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getResourceRuntime = exports.formatTimestamp = exports.getRandomString = exports.
|
|
3
|
+
exports.getResourceRuntime = exports.formatTimestamp = exports.getRandomString = exports.convertExtensionOptionToLabeledList = void 0;
|
|
4
4
|
const types_1 = require("./types");
|
|
5
5
|
function convertExtensionOptionToLabeledList(options) {
|
|
6
6
|
return options.map((option) => {
|
|
@@ -12,17 +12,6 @@ function convertExtensionOptionToLabeledList(options) {
|
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
14
|
exports.convertExtensionOptionToLabeledList = convertExtensionOptionToLabeledList;
|
|
15
|
-
function convertOfficialExtensionsToList(officialExts) {
|
|
16
|
-
const l = Object.entries(officialExts).map(([key, entry]) => {
|
|
17
|
-
return {
|
|
18
|
-
checked: false,
|
|
19
|
-
value: `${entry.publisher}/${key}`,
|
|
20
|
-
};
|
|
21
|
-
});
|
|
22
|
-
l.sort((a, b) => a.value.localeCompare(b.value));
|
|
23
|
-
return l;
|
|
24
|
-
}
|
|
25
|
-
exports.convertOfficialExtensionsToList = convertOfficialExtensionsToList;
|
|
26
15
|
function getRandomString(length) {
|
|
27
16
|
const SUFFIX_CHAR_SET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
28
17
|
let result = "";
|
package/lib/firestore/api.js
CHANGED
|
@@ -6,6 +6,7 @@ const logger_1 = require("../logger");
|
|
|
6
6
|
const utils = require("../utils");
|
|
7
7
|
const validator = require("./validator");
|
|
8
8
|
const types = require("./api-types");
|
|
9
|
+
const api_types_1 = require("./api-types");
|
|
9
10
|
const sort = require("./api-sort");
|
|
10
11
|
const util = require("./util");
|
|
11
12
|
const prompt_1 = require("../prompt");
|
|
@@ -13,6 +14,7 @@ const api_1 = require("../api");
|
|
|
13
14
|
const error_1 = require("../error");
|
|
14
15
|
const apiv2_1 = require("../apiv2");
|
|
15
16
|
const pretty_print_1 = require("./pretty-print");
|
|
17
|
+
const functional_1 = require("../functional");
|
|
16
18
|
class FirestoreApi {
|
|
17
19
|
constructor() {
|
|
18
20
|
this.apiClient = new apiv2_1.Client({ urlPrefix: (0, api_1.firestoreOrigin)(), apiVersion: "v1" });
|
|
@@ -33,6 +35,7 @@ class FirestoreApi {
|
|
|
33
35
|
});
|
|
34
36
|
}
|
|
35
37
|
async deploy(options, indexes, fieldOverrides, databaseId = "(default)") {
|
|
38
|
+
var _a;
|
|
36
39
|
const spec = this.upgradeOldSpec({
|
|
37
40
|
indexes,
|
|
38
41
|
fieldOverrides,
|
|
@@ -42,8 +45,10 @@ class FirestoreApi {
|
|
|
42
45
|
const fieldOverridesToDeploy = spec.fieldOverrides;
|
|
43
46
|
const existingIndexes = await this.listIndexes(options.project, databaseId);
|
|
44
47
|
const existingFieldOverrides = await this.listFieldOverrides(options.project, databaseId);
|
|
48
|
+
const database = await this.getDatabase(options.project, databaseId);
|
|
49
|
+
const edition = (_a = database.databaseEdition) !== null && _a !== void 0 ? _a : api_types_1.DatabaseEdition.STANDARD;
|
|
45
50
|
const indexesToDelete = existingIndexes.filter((index) => {
|
|
46
|
-
return !indexesToDeploy.some((spec) => this.indexMatchesSpec(index, spec));
|
|
51
|
+
return !indexesToDeploy.some((spec) => this.indexMatchesSpec(index, spec, edition));
|
|
47
52
|
});
|
|
48
53
|
const fieldOverridesToDelete = existingFieldOverrides.filter((field) => {
|
|
49
54
|
return !fieldOverridesToDeploy.some((spec) => {
|
|
@@ -79,7 +84,7 @@ class FirestoreApi {
|
|
|
79
84
|
}
|
|
80
85
|
}
|
|
81
86
|
for (const index of indexesToDeploy) {
|
|
82
|
-
const exists = existingIndexes.some((x) => this.indexMatchesSpec(x, index));
|
|
87
|
+
const exists = existingIndexes.some((x) => this.indexMatchesSpec(x, index, edition));
|
|
83
88
|
if (exists) {
|
|
84
89
|
logger_1.logger.debug(`Skipping existing index: ${JSON.stringify(index)}`);
|
|
85
90
|
}
|
|
@@ -222,8 +227,9 @@ class FirestoreApi {
|
|
|
222
227
|
if (index.multikey) {
|
|
223
228
|
validator.assertType("multikey", index.multikey, "boolean");
|
|
224
229
|
}
|
|
225
|
-
if (index.unique) {
|
|
230
|
+
if (index.unique !== undefined) {
|
|
226
231
|
validator.assertType("unique", index.unique, "boolean");
|
|
232
|
+
throw new error_1.FirebaseError("The `unique` index configuration is not supported yet.");
|
|
227
233
|
}
|
|
228
234
|
validator.assertHas(index, "fields");
|
|
229
235
|
index.fields.forEach((field) => {
|
|
@@ -328,7 +334,18 @@ class FirestoreApi {
|
|
|
328
334
|
const url = index.name;
|
|
329
335
|
return this.apiClient.delete(`/${url}`);
|
|
330
336
|
}
|
|
331
|
-
|
|
337
|
+
optionalApiScopeMatches(lhs, rhs) {
|
|
338
|
+
return (0, functional_1.optionalValueMatches)(lhs, rhs, types.ApiScope.ANY_API);
|
|
339
|
+
}
|
|
340
|
+
optionalDensityMatches(lhs, rhs, edition) {
|
|
341
|
+
const defaultValue = edition === api_types_1.DatabaseEdition.STANDARD ? types.Density.SPARSE_ALL : types.Density.DENSE;
|
|
342
|
+
return (0, functional_1.optionalValueMatches)(lhs, rhs, defaultValue);
|
|
343
|
+
}
|
|
344
|
+
optionalMultikeyMatches(lhs, rhs) {
|
|
345
|
+
const defaultValue = false;
|
|
346
|
+
return (0, functional_1.optionalValueMatches)(lhs, rhs, defaultValue);
|
|
347
|
+
}
|
|
348
|
+
indexMatchesSpec(index, spec, edition) {
|
|
332
349
|
const collection = util.parseIndexName(index.name).collectionGroupId;
|
|
333
350
|
if (collection !== spec.collectionGroup) {
|
|
334
351
|
return false;
|
|
@@ -336,16 +353,13 @@ class FirestoreApi {
|
|
|
336
353
|
if (index.queryScope !== spec.queryScope) {
|
|
337
354
|
return false;
|
|
338
355
|
}
|
|
339
|
-
if (index.apiScope
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
if (index.density !== spec.density) {
|
|
356
|
+
if (!this.optionalApiScopeMatches(index.apiScope, spec.apiScope)) {
|
|
343
357
|
return false;
|
|
344
358
|
}
|
|
345
|
-
if (index.
|
|
359
|
+
if (!this.optionalDensityMatches(index.density, spec.density, edition)) {
|
|
346
360
|
return false;
|
|
347
361
|
}
|
|
348
|
-
if (index.
|
|
362
|
+
if (!this.optionalMultikeyMatches(index.multikey, spec.multikey)) {
|
|
349
363
|
return false;
|
|
350
364
|
}
|
|
351
365
|
if (index.fields.length !== spec.fields.length) {
|
|
@@ -364,7 +378,7 @@ class FirestoreApi {
|
|
|
364
378
|
if (iField.arrayConfig !== sField.arrayConfig) {
|
|
365
379
|
return false;
|
|
366
380
|
}
|
|
367
|
-
if (iField.vectorConfig
|
|
381
|
+
if (!utils.deepEqual(iField.vectorConfig, sField.vectorConfig)) {
|
|
368
382
|
return false;
|
|
369
383
|
}
|
|
370
384
|
i++;
|
|
@@ -177,6 +177,13 @@ class PrettyPrint {
|
|
|
177
177
|
}
|
|
178
178
|
result += `(${field.fieldPath},${configString}) `;
|
|
179
179
|
});
|
|
180
|
+
result += " -- ";
|
|
181
|
+
if (index.density !== undefined) {
|
|
182
|
+
result += clc.cyan(`Density:${index.density} `);
|
|
183
|
+
}
|
|
184
|
+
if (index.multikey !== undefined) {
|
|
185
|
+
result += clc.cyan(`Multikey:${index.multikey ? "YES" : "NO"}`);
|
|
186
|
+
}
|
|
180
187
|
return result;
|
|
181
188
|
}
|
|
182
189
|
prettyBackupString(backup) {
|
package/lib/functional.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.nullsafeVisitor = exports.mapObject = exports.partitionRecord = exports.partition = exports.assertExhaustive = exports.zipIn = exports.zip = exports.reduceFlat = exports.flatten = exports.flattenArray = exports.flattenObject = void 0;
|
|
3
|
+
exports.optionalValueMatches = exports.nullsafeVisitor = exports.mapObject = exports.partitionRecord = exports.partition = exports.assertExhaustive = exports.zipIn = exports.zip = exports.reduceFlat = exports.flatten = exports.flattenArray = exports.flattenObject = void 0;
|
|
4
4
|
function* flattenObject(obj) {
|
|
5
5
|
function* helper(path, obj) {
|
|
6
6
|
for (const [k, v] of Object.entries(obj)) {
|
|
@@ -85,3 +85,9 @@ const nullsafeVisitor = (func, ...rest) => (first) => {
|
|
|
85
85
|
return func(first, ...rest);
|
|
86
86
|
};
|
|
87
87
|
exports.nullsafeVisitor = nullsafeVisitor;
|
|
88
|
+
function optionalValueMatches(lhs, rhs, defaultValue) {
|
|
89
|
+
lhs = lhs === undefined ? defaultValue : lhs;
|
|
90
|
+
rhs = rhs === undefined ? defaultValue : rhs;
|
|
91
|
+
return lhs === rhs;
|
|
92
|
+
}
|
|
93
|
+
exports.optionalValueMatches = optionalValueMatches;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.configForCodebase = exports.normalizeAndValidate = exports.validate = exports.assertUnique = exports.validateCodebase = exports.normalize = exports.DEFAULT_CODEBASE = void 0;
|
|
3
|
+
exports.configForCodebase = exports.normalizeAndValidate = exports.validate = exports.assertUnique = exports.validatePrefix = exports.validateCodebase = exports.normalize = exports.DEFAULT_CODEBASE = void 0;
|
|
4
4
|
const error_1 = require("../error");
|
|
5
5
|
exports.DEFAULT_CODEBASE = "default";
|
|
6
6
|
function normalize(config) {
|
|
@@ -23,6 +23,15 @@ function validateCodebase(codebase) {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
exports.validateCodebase = validateCodebase;
|
|
26
|
+
function validatePrefix(prefix) {
|
|
27
|
+
if (prefix.length > 30) {
|
|
28
|
+
throw new error_1.FirebaseError("Invalid prefix. Prefix must be 30 characters or less.");
|
|
29
|
+
}
|
|
30
|
+
if (!/^[a-z](?:[a-z0-9-]*[a-z0-9])?$/.test(prefix)) {
|
|
31
|
+
throw new error_1.FirebaseError("Invalid prefix. Prefix must start with a lowercase letter, can contain only lowercase letters, numeric characters, and dashes, and cannot start or end with a dash.");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.validatePrefix = validatePrefix;
|
|
26
35
|
function validateSingle(config) {
|
|
27
36
|
if (!config.source) {
|
|
28
37
|
throw new error_1.FirebaseError("codebase source must be specified");
|
|
@@ -31,6 +40,9 @@ function validateSingle(config) {
|
|
|
31
40
|
config.codebase = exports.DEFAULT_CODEBASE;
|
|
32
41
|
}
|
|
33
42
|
validateCodebase(config.codebase);
|
|
43
|
+
if (config.prefix) {
|
|
44
|
+
validatePrefix(config.prefix);
|
|
45
|
+
}
|
|
34
46
|
return Object.assign(Object.assign({}, config), { source: config.source, codebase: config.codebase });
|
|
35
47
|
}
|
|
36
48
|
function assertUnique(config, property, propval) {
|
|
@@ -47,10 +59,21 @@ function assertUnique(config, property, propval) {
|
|
|
47
59
|
}
|
|
48
60
|
}
|
|
49
61
|
exports.assertUnique = assertUnique;
|
|
62
|
+
function assertUniqueSourcePrefixPair(config) {
|
|
63
|
+
var _a;
|
|
64
|
+
const sourcePrefixPairs = new Set();
|
|
65
|
+
for (const c of config) {
|
|
66
|
+
const key = JSON.stringify({ source: c.source, prefix: c.prefix || "" });
|
|
67
|
+
if (sourcePrefixPairs.has(key)) {
|
|
68
|
+
throw new error_1.FirebaseError(`More than one functions config specifies the same source directory ('${c.source}') and prefix ('${(_a = c.prefix) !== null && _a !== void 0 ? _a : ""}'). Please add a unique 'prefix' to each function configuration that shares this source to resolve the conflict.`);
|
|
69
|
+
}
|
|
70
|
+
sourcePrefixPairs.add(key);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
50
73
|
function validate(config) {
|
|
51
74
|
const validated = config.map((cfg) => validateSingle(cfg));
|
|
52
|
-
assertUnique(validated, "source");
|
|
53
75
|
assertUnique(validated, "codebase");
|
|
76
|
+
assertUniqueSourcePrefixPair(validated);
|
|
54
77
|
return validated;
|
|
55
78
|
}
|
|
56
79
|
exports.validate = validate;
|
package/lib/functions/secrets.js
CHANGED
|
@@ -235,6 +235,9 @@ async function updateEndpointSecret(projectInfo, secretVersion, endpoint) {
|
|
|
235
235
|
const cfn = await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { operationResourceName: op.name }));
|
|
236
236
|
return gcfV2.endpointFromFunction(cfn);
|
|
237
237
|
}
|
|
238
|
+
else if (endpoint.platform === "run") {
|
|
239
|
+
throw new error_1.FirebaseError("Updating Cloud Run functions is not yet implemented.");
|
|
240
|
+
}
|
|
238
241
|
else {
|
|
239
242
|
(0, functional_1.assertExhaustive)(endpoint.platform);
|
|
240
243
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.endpointFromFunction = exports.functionFromEndpoint = exports.deleteFunction = exports.updateFunction = exports.listAllFunctions = exports.listFunctions = exports.getFunction = exports.createFunction = exports.generateUploadUrl = exports.
|
|
3
|
+
exports.endpointFromFunction = exports.functionFromEndpoint = exports.deleteFunction = exports.updateFunction = exports.listAllFunctions = exports.listFunctions = exports.getFunction = exports.createFunction = exports.generateUploadUrl = exports.API_VERSION = void 0;
|
|
4
4
|
const apiv2_1 = require("../apiv2");
|
|
5
5
|
const error_1 = require("../error");
|
|
6
6
|
const api_1 = require("../api");
|
|
@@ -13,6 +13,7 @@ const utils = require("../utils");
|
|
|
13
13
|
const projectConfig = require("../functions/projectConfig");
|
|
14
14
|
const constants_1 = require("../functions/constants");
|
|
15
15
|
const cloudfunctions_1 = require("./cloudfunctions");
|
|
16
|
+
const k8s_1 = require("./k8s");
|
|
16
17
|
exports.API_VERSION = "v2";
|
|
17
18
|
const DEFAULT_MAX_INSTANCE_COUNT = 100;
|
|
18
19
|
const client = new apiv2_1.Client({
|
|
@@ -20,35 +21,6 @@ const client = new apiv2_1.Client({
|
|
|
20
21
|
auth: true,
|
|
21
22
|
apiVersion: exports.API_VERSION,
|
|
22
23
|
});
|
|
23
|
-
const BYTES_PER_UNIT = {
|
|
24
|
-
"": 1,
|
|
25
|
-
k: 1e3,
|
|
26
|
-
M: 1e6,
|
|
27
|
-
G: 1e9,
|
|
28
|
-
T: 1e12,
|
|
29
|
-
Ki: 1 << 10,
|
|
30
|
-
Mi: 1 << 20,
|
|
31
|
-
Gi: 1 << 30,
|
|
32
|
-
Ti: 1 << 40,
|
|
33
|
-
};
|
|
34
|
-
function mebibytes(memory) {
|
|
35
|
-
const re = /^([0-9]+(\.[0-9]*)?)(Ki|Mi|Gi|Ti|k|M|G|T|([eE]([0-9]+)))?$/;
|
|
36
|
-
const matches = re.exec(memory);
|
|
37
|
-
if (!matches) {
|
|
38
|
-
throw new Error(`Invalid memory quantity "${memory}""`);
|
|
39
|
-
}
|
|
40
|
-
const quantity = Number.parseFloat(matches[1]);
|
|
41
|
-
let bytes;
|
|
42
|
-
if (matches[5]) {
|
|
43
|
-
bytes = quantity * Math.pow(10, Number.parseFloat(matches[5]));
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
const suffix = matches[3] || "";
|
|
47
|
-
bytes = quantity * BYTES_PER_UNIT[suffix];
|
|
48
|
-
}
|
|
49
|
-
return bytes / (1 << 20);
|
|
50
|
-
}
|
|
51
|
-
exports.mebibytes = mebibytes;
|
|
52
24
|
function functionsOpLogReject(func, type, err) {
|
|
53
25
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
54
26
|
if ((_a = err === null || err === void 0 ? void 0 : err.message) === null || _a === void 0 ? void 0 : _a.includes("Runtime validation errors")) {
|
|
@@ -360,7 +332,7 @@ function endpointFromFunction(gcfFunction) {
|
|
|
360
332
|
logger_1.logger.debug("Prod should always return a valid memory amount");
|
|
361
333
|
return prod;
|
|
362
334
|
}
|
|
363
|
-
const mem = mebibytes(prod);
|
|
335
|
+
const mem = (0, k8s_1.mebibytes)(prod);
|
|
364
336
|
if (!backend.isValidMemoryOption(mem)) {
|
|
365
337
|
logger_1.logger.debug("Converting a function to an endpoint with an invalid memory option", mem);
|
|
366
338
|
}
|
|
@@ -124,7 +124,7 @@ async function jobFromEndpoint(endpoint, location, projectNumber) {
|
|
|
124
124
|
},
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
|
-
else if (endpoint.platform === "gcfv2") {
|
|
127
|
+
else if (endpoint.platform === "gcfv2" || endpoint.platform === "run") {
|
|
128
128
|
job.timeZone = endpoint.scheduleTrigger.timeZone || DEFAULT_TIME_ZONE_V2;
|
|
129
129
|
job.httpTarget = {
|
|
130
130
|
uri: endpoint.uri,
|
|
@@ -55,9 +55,8 @@ async function createInstance(args) {
|
|
|
55
55
|
if (args.enableGoogleMlIntegration) {
|
|
56
56
|
databaseFlags.push({ name: "cloudsql.enable_google_ml_integration", value: "on" });
|
|
57
57
|
}
|
|
58
|
-
let op;
|
|
59
58
|
try {
|
|
60
|
-
|
|
59
|
+
await client.post(`projects/${args.projectId}/instances`, {
|
|
61
60
|
name: args.instanceId,
|
|
62
61
|
region: args.location,
|
|
63
62
|
databaseVersion: "POSTGRES_15",
|
|
@@ -78,23 +77,12 @@ async function createInstance(args) {
|
|
|
78
77
|
},
|
|
79
78
|
},
|
|
80
79
|
});
|
|
80
|
+
return;
|
|
81
81
|
}
|
|
82
82
|
catch (err) {
|
|
83
83
|
handleAllowlistError(err, args.location);
|
|
84
84
|
throw err;
|
|
85
85
|
}
|
|
86
|
-
if (!args.waitForCreation) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
const opName = `projects/${args.projectId}/operations/${op.body.name}`;
|
|
90
|
-
const pollRes = await operationPoller.pollOperation({
|
|
91
|
-
apiOrigin: (0, api_1.cloudSQLAdminOrigin)(),
|
|
92
|
-
apiVersion: API_VERSION,
|
|
93
|
-
operationResourceName: opName,
|
|
94
|
-
doneFn: (op) => op.status === "DONE",
|
|
95
|
-
masterTimeout: 1200000,
|
|
96
|
-
});
|
|
97
|
-
return pollRes;
|
|
98
86
|
}
|
|
99
87
|
exports.createInstance = createInstance;
|
|
100
88
|
async function updateInstanceForDataConnect(instance, enableGoogleMlIntegration) {
|
|
@@ -128,7 +128,7 @@ async function getIAMUser(options) {
|
|
|
128
128
|
return toDatabaseUser(account);
|
|
129
129
|
}
|
|
130
130
|
exports.getIAMUser = getIAMUser;
|
|
131
|
-
async function setupIAMUsers(instanceId,
|
|
131
|
+
async function setupIAMUsers(instanceId, options) {
|
|
132
132
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
133
133
|
const { user, mode } = await getIAMUser(options);
|
|
134
134
|
await cloudSqlAdminClient.createUser(projectId, instanceId, mode, user);
|
|
@@ -4,14 +4,13 @@ exports.brownfieldSqlSetup = exports.setupBrownfieldAsGreenfield = exports.getSc
|
|
|
4
4
|
const clc = require("colorette");
|
|
5
5
|
const permissions_1 = require("./permissions");
|
|
6
6
|
const cloudsqladmin_1 = require("./cloudsqladmin");
|
|
7
|
-
const connect_1 = require("./connect");
|
|
8
7
|
const logger_1 = require("../../logger");
|
|
9
8
|
const prompt_1 = require("../../prompt");
|
|
10
9
|
const error_1 = require("../../error");
|
|
11
10
|
const projectUtils_1 = require("../../projectUtils");
|
|
12
|
-
const
|
|
11
|
+
const connect_1 = require("./connect");
|
|
13
12
|
const lodash_1 = require("lodash");
|
|
14
|
-
const
|
|
13
|
+
const connect_2 = require("./connect");
|
|
15
14
|
const utils = require("../../utils");
|
|
16
15
|
var SchemaSetupStatus;
|
|
17
16
|
(function (SchemaSetupStatus) {
|
|
@@ -53,7 +52,7 @@ async function checkSQLRoleIsGranted(options, instanceId, databaseId, grantedRol
|
|
|
53
52
|
END $$;
|
|
54
53
|
`;
|
|
55
54
|
try {
|
|
56
|
-
await (0,
|
|
55
|
+
await (0, connect_1.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, [checkCmd], true);
|
|
57
56
|
return true;
|
|
58
57
|
}
|
|
59
58
|
catch (e) {
|
|
@@ -77,7 +76,6 @@ async function setupSQLPermissions(instanceId, databaseId, schemaInfo, options,
|
|
|
77
76
|
if (!userIsCSQLAdmin) {
|
|
78
77
|
throw new error_1.FirebaseError(`Missing required IAM permission to setup SQL schemas. SQL schema setup requires 'roles/cloudsql.admin' or an equivalent role.`);
|
|
79
78
|
}
|
|
80
|
-
await (0, connect_1.setupIAMUsers)(instanceId, databaseId, options);
|
|
81
79
|
let runGreenfieldSetup = false;
|
|
82
80
|
if (schemaInfo.setupStatus === SchemaSetupStatus.GreenField) {
|
|
83
81
|
runGreenfieldSetup = true;
|
|
@@ -89,7 +87,7 @@ async function setupSQLPermissions(instanceId, databaseId, schemaInfo, options,
|
|
|
89
87
|
}
|
|
90
88
|
if (runGreenfieldSetup) {
|
|
91
89
|
const greenfieldSetupCmds = await greenFieldSchemaSetup(instanceId, databaseId, schema, options);
|
|
92
|
-
await (0,
|
|
90
|
+
await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, greenfieldSetupCmds, silent, true);
|
|
93
91
|
logFn(clc.green("Database setup complete."));
|
|
94
92
|
return SchemaSetupStatus.GreenField;
|
|
95
93
|
}
|
|
@@ -125,15 +123,15 @@ async function greenFieldSchemaSetup(instanceId, databaseId, schema, options) {
|
|
|
125
123
|
logger_1.logger.warn("Detected cloudsqlsuperuser was previously given to firebase owner, revoking to improve database security.");
|
|
126
124
|
revokes.push(`REVOKE "cloudsqlsuperuser" FROM "${(0, permissions_1.firebaseowner)(databaseId)}"`);
|
|
127
125
|
}
|
|
128
|
-
const user = (await (0,
|
|
126
|
+
const user = (await (0, connect_1.getIAMUser)(options)).user;
|
|
129
127
|
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
130
|
-
const { user: fdcP4SAUser } = (0,
|
|
128
|
+
const { user: fdcP4SAUser } = (0, connect_2.toDatabaseUser)((0, connect_2.getDataConnectP4SA)(projectNumber));
|
|
131
129
|
const sqlRoleSetupCmds = (0, lodash_1.concat)(revokes, [`CREATE SCHEMA IF NOT EXISTS "${schema}"`], (0, permissions_1.ownerRolePermissions)(databaseId, permissions_1.FIREBASE_SUPER_USER, schema), (0, permissions_1.writerRolePermissions)(databaseId, permissions_1.FIREBASE_SUPER_USER, schema), (0, permissions_1.readerRolePermissions)(databaseId, permissions_1.FIREBASE_SUPER_USER, schema), `GRANT "${(0, permissions_1.firebaseowner)(databaseId, schema)}" TO "${user}"`, `GRANT "${(0, permissions_1.firebasewriter)(databaseId, schema)}" TO "${fdcP4SAUser}"`, (0, permissions_1.defaultPermissions)(databaseId, schema, (0, permissions_1.firebaseowner)(databaseId, schema)));
|
|
132
130
|
return sqlRoleSetupCmds;
|
|
133
131
|
}
|
|
134
132
|
exports.greenFieldSchemaSetup = greenFieldSchemaSetup;
|
|
135
133
|
async function getSchemaMetadata(instanceId, databaseId, schema, options) {
|
|
136
|
-
const checkSchemaExists = await (0,
|
|
134
|
+
const checkSchemaExists = await (0, connect_1.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, [
|
|
137
135
|
`SELECT pg_get_userbyid(nspowner)
|
|
138
136
|
FROM pg_namespace
|
|
139
137
|
WHERE nspname = '${schema}';`,
|
|
@@ -148,7 +146,7 @@ async function getSchemaMetadata(instanceId, databaseId, schema, options) {
|
|
|
148
146
|
}
|
|
149
147
|
const schemaOwner = checkSchemaExists[0].rows[0].pg_get_userbyid;
|
|
150
148
|
const cmd = `SELECT tablename, tableowner FROM pg_tables WHERE schemaname='${schema}'`;
|
|
151
|
-
const res = await (0,
|
|
149
|
+
const res = await (0, connect_1.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, [cmd], true);
|
|
152
150
|
const tables = res[0].rows.map((row) => {
|
|
153
151
|
return {
|
|
154
152
|
name: row.tablename,
|
|
@@ -157,7 +155,7 @@ async function getSchemaMetadata(instanceId, databaseId, schema, options) {
|
|
|
157
155
|
});
|
|
158
156
|
const checkRoleExists = async (role) => {
|
|
159
157
|
const cmd = [`SELECT to_regrole('"${role}"') IS NOT NULL AS exists;`];
|
|
160
|
-
const result = await (0,
|
|
158
|
+
const result = await (0, connect_1.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, cmd, true);
|
|
161
159
|
return result[0].rows[0].exists;
|
|
162
160
|
};
|
|
163
161
|
let setupStatus;
|
|
@@ -198,7 +196,7 @@ async function setupBrownfieldAsGreenfield(instanceId, databaseId, schemaInfo, o
|
|
|
198
196
|
...alterTableCmds,
|
|
199
197
|
...revokeOwnersFromSuperuserCmds,
|
|
200
198
|
];
|
|
201
|
-
await (0,
|
|
199
|
+
await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, setupCmds, silent, true);
|
|
202
200
|
}
|
|
203
201
|
exports.setupBrownfieldAsGreenfield = setupBrownfieldAsGreenfield;
|
|
204
202
|
async function brownfieldSqlSetup(instanceId, databaseId, schemaInfo, options, silent = false) {
|
|
@@ -206,9 +204,9 @@ async function brownfieldSqlSetup(instanceId, databaseId, schemaInfo, options, s
|
|
|
206
204
|
const uniqueTablesOwners = filterTableOwners(schemaInfo, databaseId);
|
|
207
205
|
const grantOwnersToFirebasesuperuser = uniqueTablesOwners.map((owner) => `GRANT "${owner}" TO "${permissions_1.FIREBASE_SUPER_USER}"`);
|
|
208
206
|
const revokeOwnersFromFirebasesuperuser = uniqueTablesOwners.map((owner) => `REVOKE "${owner}" FROM "${permissions_1.FIREBASE_SUPER_USER}"`);
|
|
209
|
-
const iamUser = (await (0,
|
|
207
|
+
const iamUser = (await (0, connect_1.getIAMUser)(options)).user;
|
|
210
208
|
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
211
|
-
const { user: fdcP4SAUser } = (0,
|
|
209
|
+
const { user: fdcP4SAUser } = (0, connect_2.toDatabaseUser)((0, connect_2.getDataConnectP4SA)(projectNumber));
|
|
212
210
|
const firebaseDefaultPermissions = uniqueTablesOwners.flatMap((owner) => (0, permissions_1.defaultPermissions)(databaseId, schema, owner));
|
|
213
211
|
const brownfieldSetupCmds = [
|
|
214
212
|
...grantOwnersToFirebasesuperuser,
|
|
@@ -219,6 +217,6 @@ async function brownfieldSqlSetup(instanceId, databaseId, schemaInfo, options, s
|
|
|
219
217
|
...firebaseDefaultPermissions,
|
|
220
218
|
...revokeOwnersFromFirebasesuperuser,
|
|
221
219
|
];
|
|
222
|
-
await (0,
|
|
220
|
+
await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, brownfieldSetupCmds, silent, true);
|
|
223
221
|
}
|
|
224
222
|
exports.brownfieldSqlSetup = brownfieldSqlSetup;
|