firebase-tools 14.12.0 → 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/commands/firestore-databases-create.js +11 -0
- package/lib/crashlytics/buildToolsJarHelper.js +1 -2
- 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 +9 -12
- package/lib/deploy/dataconnect/release.js +13 -7
- package/lib/deploy/firestore/deploy.js +10 -0
- 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/prepareFunctionsUpload.js +3 -1
- 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-sort.js +96 -3
- package/lib/firestore/api-types.js +14 -1
- package/lib/firestore/api.js +85 -4
- package/lib/firestore/pretty-print.js +7 -0
- package/lib/firestore/validator.js +1 -1
- package/lib/functional.js +7 -1
- package/lib/functions/deprecationWarnings.js +4 -4
- 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 +2 -2
- 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 -17
- package/lib/init/features/project.js +4 -0
- package/lib/management/studio.js +1 -1
- package/lib/mcp/index.js +75 -2
- package/lib/mcp/prompt.js +10 -0
- package/lib/mcp/prompts/core/deploy.js +58 -0
- package/lib/mcp/prompts/core/index.js +5 -0
- package/lib/mcp/prompts/index.js +45 -0
- package/lib/mcp/tools/core/get_sdk_config.js +10 -0
- 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/mcp/tools/database/get_data.js +49 -0
- package/lib/mcp/tools/database/get_rules.js +39 -0
- package/lib/mcp/tools/database/index.js +8 -0
- package/lib/mcp/tools/database/set_data.js +57 -0
- package/lib/mcp/tools/database/set_rules.js +41 -0
- package/lib/mcp/tools/database/validate_rules.js +41 -0
- package/lib/mcp/tools/index.js +4 -1
- package/lib/mcp/tools/rules/get_rules.js +1 -1
- package/lib/mcp/types.js +2 -0
- package/lib/mcp/util.js +2 -0
- package/lib/rtdb.js +10 -6
- package/lib/utils.js +24 -1
- package/package.json +1 -1
- package/schema/firebase-config.json +6 -0
- package/templates/init/firestore/firestore.indexes.json +26 -1
- package/lib/extensions/resolveSource.js +0 -24
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;
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.logFunctionsConfigDeprecationWarning = void 0;
|
|
4
4
|
const utils_1 = require("../utils");
|
|
5
|
-
const FUNCTIONS_CONFIG_DEPRECATION_MESSAGE = `DEPRECATION NOTICE: Action required to deploy after
|
|
5
|
+
const FUNCTIONS_CONFIG_DEPRECATION_MESSAGE = `DEPRECATION NOTICE: Action required to deploy after March 2026
|
|
6
6
|
|
|
7
7
|
functions.config() API is deprecated.
|
|
8
|
-
Cloud Runtime Configuration API, the Google Cloud service used to store function configuration data, will be shut down
|
|
8
|
+
Cloud Runtime Configuration API, the Google Cloud service used to store function configuration data, will be shut down in March 2026. As a result, you must migrate away from using functions.config() to continue deploying your functions after March 2026.
|
|
9
9
|
|
|
10
10
|
What this means for you:
|
|
11
11
|
|
|
12
|
-
- The Firebase CLI commands for managing this configuration (functions:config:set, get, unset, clone, and export) are deprecated. These commands no longer work after
|
|
13
|
-
- firebase deploy command will fail for functions that use the legacy functions.config() API after
|
|
12
|
+
- The Firebase CLI commands for managing this configuration (functions:config:set, get, unset, clone, and export) are deprecated. These commands will no longer work after March 2026.
|
|
13
|
+
- firebase deploy command will fail for functions that use the legacy functions.config() API after March 2026.
|
|
14
14
|
|
|
15
15
|
Existing deployments will continue to work with their current configuration.
|
|
16
16
|
|
|
@@ -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) {
|
|
@@ -103,7 +103,7 @@ exports.executeSqlCmdsAsIamUser = executeSqlCmdsAsIamUser;
|
|
|
103
103
|
async function executeSqlCmdsAsSuperUser(options, instanceId, databaseId, cmds, silent = false, transaction = false) {
|
|
104
104
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
105
105
|
const superuser = "firebasesuperuser";
|
|
106
|
-
const temporaryPassword = utils.
|
|
106
|
+
const temporaryPassword = utils.generatePassword(20);
|
|
107
107
|
await cloudSqlAdminClient.createUser(projectId, instanceId, "BUILT_IN", superuser, temporaryPassword);
|
|
108
108
|
return await execute([`SET ROLE = '${superuser}'`, ...cmds], {
|
|
109
109
|
projectId,
|
|
@@ -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;
|
package/lib/gcp/k8s.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mebibytes = void 0;
|
|
4
|
+
const BYTES_PER_UNIT = {
|
|
5
|
+
"": 1,
|
|
6
|
+
k: 1e3,
|
|
7
|
+
M: 1e6,
|
|
8
|
+
G: 1e9,
|
|
9
|
+
T: 1e12,
|
|
10
|
+
Ki: 1 << 10,
|
|
11
|
+
Mi: 1 << 20,
|
|
12
|
+
Gi: 1 << 30,
|
|
13
|
+
Ti: 1 << 40,
|
|
14
|
+
};
|
|
15
|
+
function mebibytes(memory) {
|
|
16
|
+
const re = /^([0-9]+(\.[0-9]*)?)(Ki|Mi|Gi|Ti|k|M|G|T|([eE]([0-9]+)))?$/;
|
|
17
|
+
const matches = re.exec(memory);
|
|
18
|
+
if (!matches) {
|
|
19
|
+
throw new Error(`Invalid memory quantity "${memory}""`);
|
|
20
|
+
}
|
|
21
|
+
const quantity = Number.parseFloat(matches[1]);
|
|
22
|
+
let bytes;
|
|
23
|
+
if (matches[5]) {
|
|
24
|
+
bytes = quantity * Math.pow(10, Number.parseFloat(matches[5]));
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
const suffix = matches[3] || "";
|
|
28
|
+
bytes = quantity * BYTES_PER_UNIT[suffix];
|
|
29
|
+
}
|
|
30
|
+
return bytes / (1 << 20);
|
|
31
|
+
}
|
|
32
|
+
exports.mebibytes = mebibytes;
|
package/lib/gcp/runv2.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.serviceFromEndpoint = exports.endpointFromService = exports.FIREBASE_FUNCTION_METADTA_ANNOTATION = exports.FUNCTION_SIGNATURE_TYPE_ENV = exports.FUNCTION_TARGET_ENV = exports.FUNCTION_ID_ANNOTATION = exports.FUNCTION_TARGET_ANNOTATION = exports.TRIGGER_TYPE_ANNOTATION = exports.CLIENT_NAME_LABEL = exports.RUNTIME_LABEL = exports.updateService = exports.submitBuild = exports.API_VERSION = void 0;
|
|
4
|
+
const apiv2_1 = require("../apiv2");
|
|
5
|
+
const error_1 = require("../error");
|
|
6
|
+
const api_1 = require("../api");
|
|
7
|
+
const proto = require("./proto");
|
|
8
|
+
const metaprogramming_1 = require("../metaprogramming");
|
|
9
|
+
const operation_poller_1 = require("../operation-poller");
|
|
10
|
+
const backend = require("../deploy/functions/backend");
|
|
11
|
+
const constants_1 = require("../functions/constants");
|
|
12
|
+
const k8s_1 = require("./k8s");
|
|
13
|
+
const supported_1 = require("../deploy/functions/runtimes/supported");
|
|
14
|
+
const __1 = require("..");
|
|
15
|
+
const functional_1 = require("../functional");
|
|
16
|
+
exports.API_VERSION = "v2";
|
|
17
|
+
const client = new apiv2_1.Client({
|
|
18
|
+
urlPrefix: (0, api_1.runOrigin)(),
|
|
19
|
+
auth: true,
|
|
20
|
+
apiVersion: exports.API_VERSION,
|
|
21
|
+
});
|
|
22
|
+
(0, metaprogramming_1.assertImplements)();
|
|
23
|
+
async function submitBuild(projectId, location, build) {
|
|
24
|
+
const res = await client.post(`/projects/${projectId}/locations/${location}/builds`, build);
|
|
25
|
+
if (res.status !== 200) {
|
|
26
|
+
throw new error_1.FirebaseError(`Failed to submit build: ${res.status} ${res.body}`);
|
|
27
|
+
}
|
|
28
|
+
await (0, operation_poller_1.pollOperation)({
|
|
29
|
+
apiOrigin: (0, api_1.cloudbuildOrigin)(),
|
|
30
|
+
apiVersion: "v1",
|
|
31
|
+
operationResourceName: res.body.buildOperation,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
exports.submitBuild = submitBuild;
|
|
35
|
+
async function updateService(service) {
|
|
36
|
+
const fieldMask = proto.fieldMasks(service, "labels", "annotations", "tags");
|
|
37
|
+
fieldMask.push("template.revision");
|
|
38
|
+
const res = await client.post(service.name, service, {
|
|
39
|
+
queryParams: {
|
|
40
|
+
updateMask: fieldMask.join(","),
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
const svc = await (0, operation_poller_1.pollOperation)({
|
|
44
|
+
apiOrigin: (0, api_1.runOrigin)(),
|
|
45
|
+
apiVersion: exports.API_VERSION,
|
|
46
|
+
operationResourceName: res.body.name,
|
|
47
|
+
});
|
|
48
|
+
return svc;
|
|
49
|
+
}
|
|
50
|
+
exports.updateService = updateService;
|
|
51
|
+
function functionNameToServiceName(id) {
|
|
52
|
+
return id.toLowerCase().replace(/_/g, "-");
|
|
53
|
+
}
|
|
54
|
+
exports.RUNTIME_LABEL = "goog-cloudfunctions-runtime";
|
|
55
|
+
exports.CLIENT_NAME_LABEL = "goog-managed-by";
|
|
56
|
+
exports.TRIGGER_TYPE_ANNOTATION = "cloudfunctions.googleapis.com/trigger-type";
|
|
57
|
+
exports.FUNCTION_TARGET_ANNOTATION = "run.googleapis.com/build-function-target";
|
|
58
|
+
exports.FUNCTION_ID_ANNOTATION = "cloudfunctions.googleapis.com/function-id";
|
|
59
|
+
exports.FUNCTION_TARGET_ENV = "FUNCTION_TARGET";
|
|
60
|
+
exports.FUNCTION_SIGNATURE_TYPE_ENV = "FUNCTION_SIGNATURE_TYPE";
|
|
61
|
+
exports.FIREBASE_FUNCTION_METADTA_ANNOTATION = "firebase-functions-metadata";
|
|
62
|
+
function endpointFromService(service) {
|
|
63
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
64
|
+
const [, project, , location, , svcId] = service.name.split("/");
|
|
65
|
+
const metadata = JSON.parse(((_a = service.annotations) === null || _a === void 0 ? void 0 : _a[exports.FIREBASE_FUNCTION_METADTA_ANNOTATION]) || "{}");
|
|
66
|
+
const [env, secretEnv] = (0, functional_1.partition)(service.template.containers[0].env || [], (e) => "value" in e);
|
|
67
|
+
const id = metadata.functionId ||
|
|
68
|
+
((_b = service.annotations) === null || _b === void 0 ? void 0 : _b[exports.FUNCTION_ID_ANNOTATION]) ||
|
|
69
|
+
((_c = service.annotations) === null || _c === void 0 ? void 0 : _c[exports.FUNCTION_TARGET_ANNOTATION]) ||
|
|
70
|
+
((_d = env.find((e) => e.name === exports.FUNCTION_TARGET_ENV)) === null || _d === void 0 ? void 0 : _d.value) ||
|
|
71
|
+
svcId;
|
|
72
|
+
const memory = (0, k8s_1.mebibytes)(service.template.containers[0].resources.limits.memory);
|
|
73
|
+
if (!backend.isValidMemoryOption(memory)) {
|
|
74
|
+
__1.logger.debug("Converting a service to an endpoint with an invalid memory option", memory);
|
|
75
|
+
}
|
|
76
|
+
const cpu = Number(service.template.containers[0].resources.limits.cpu);
|
|
77
|
+
const endpoint = {
|
|
78
|
+
platform: ((_e = service.labels) === null || _e === void 0 ? void 0 : _e[exports.CLIENT_NAME_LABEL]) === "cloud-functions" ? "gcfv2" : "run",
|
|
79
|
+
id,
|
|
80
|
+
project,
|
|
81
|
+
labels: service.labels || {},
|
|
82
|
+
region: location,
|
|
83
|
+
runtime: ((_f = service.labels) === null || _f === void 0 ? void 0 : _f[exports.RUNTIME_LABEL]) || (0, supported_1.latest)("nodejs"),
|
|
84
|
+
availableMemoryMb: memory,
|
|
85
|
+
cpu: cpu,
|
|
86
|
+
entryPoint: ((_g = env.find((e) => e.name === exports.FUNCTION_TARGET_ENV)) === null || _g === void 0 ? void 0 : _g.value) ||
|
|
87
|
+
((_h = service.annotations) === null || _h === void 0 ? void 0 : _h[exports.FUNCTION_TARGET_ANNOTATION]) ||
|
|
88
|
+
((_j = service.annotations) === null || _j === void 0 ? void 0 : _j[exports.FUNCTION_ID_ANNOTATION]) ||
|
|
89
|
+
id,
|
|
90
|
+
httpsTrigger: {},
|
|
91
|
+
};
|
|
92
|
+
proto.renameIfPresent(endpoint, service.template, "concurrency", "containerConcurrency");
|
|
93
|
+
proto.renameIfPresent(endpoint, service.labels || {}, "codebase", constants_1.CODEBASE_LABEL);
|
|
94
|
+
proto.renameIfPresent(endpoint, service.scaling || {}, "minInstances", "minInstanceCount");
|
|
95
|
+
proto.renameIfPresent(endpoint, service.scaling || {}, "maxInstances", "maxInstanceCount");
|
|
96
|
+
endpoint.environmentVariables = env.reduce((acc, e) => {
|
|
97
|
+
acc[e.name] = e.value;
|
|
98
|
+
return acc;
|
|
99
|
+
}, {});
|
|
100
|
+
endpoint.secretEnvironmentVariables = secretEnv.map((e) => {
|
|
101
|
+
const [, projectId, , secret] = e.valueSource.secretKeyRef.secret.split("/");
|
|
102
|
+
return {
|
|
103
|
+
key: e.name,
|
|
104
|
+
projectId,
|
|
105
|
+
secret,
|
|
106
|
+
version: e.valueSource.secretKeyRef.version || "latest",
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
return endpoint;
|
|
110
|
+
}
|
|
111
|
+
exports.endpointFromService = endpointFromService;
|
|
112
|
+
function serviceFromEndpoint(endpoint, image) {
|
|
113
|
+
const labels = Object.assign(Object.assign({}, endpoint.labels), { [exports.RUNTIME_LABEL]: endpoint.runtime, [exports.CLIENT_NAME_LABEL]: "firebase-functions" });
|
|
114
|
+
delete labels["deployment-tool"];
|
|
115
|
+
if (endpoint.codebase) {
|
|
116
|
+
labels[constants_1.CODEBASE_LABEL] = endpoint.codebase;
|
|
117
|
+
}
|
|
118
|
+
const annotations = {
|
|
119
|
+
[exports.FIREBASE_FUNCTION_METADTA_ANNOTATION]: JSON.stringify({
|
|
120
|
+
functionId: endpoint.id,
|
|
121
|
+
}),
|
|
122
|
+
};
|
|
123
|
+
const template = {
|
|
124
|
+
containers: [
|
|
125
|
+
{
|
|
126
|
+
name: "worker",
|
|
127
|
+
image,
|
|
128
|
+
env: [
|
|
129
|
+
...Object.entries(endpoint.environmentVariables || {}).map(([name, value]) => ({
|
|
130
|
+
name,
|
|
131
|
+
value,
|
|
132
|
+
})),
|
|
133
|
+
...(endpoint.secretEnvironmentVariables || []).map((secret) => ({
|
|
134
|
+
name: secret.key,
|
|
135
|
+
valueSource: {
|
|
136
|
+
secretKeyRef: {
|
|
137
|
+
secret: secret.secret,
|
|
138
|
+
version: secret.version,
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
})),
|
|
142
|
+
{
|
|
143
|
+
name: exports.FUNCTION_TARGET_ENV,
|
|
144
|
+
value: endpoint.entryPoint,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: exports.FUNCTION_SIGNATURE_TYPE_ENV,
|
|
148
|
+
value: backend.isEventTriggered(endpoint) ? "cloudevent" : "http",
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
resources: {
|
|
152
|
+
limits: {
|
|
153
|
+
cpu: String(endpoint.cpu || 1),
|
|
154
|
+
memory: `${endpoint.availableMemoryMb || 256}Mi`,
|
|
155
|
+
},
|
|
156
|
+
cpuIdle: true,
|
|
157
|
+
startupCpuBoost: true,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
containerConcurrency: endpoint.concurrency || backend.DEFAULT_CONCURRENCY,
|
|
162
|
+
};
|
|
163
|
+
proto.renameIfPresent(template, endpoint, "containerConcurrency", "concurrency");
|
|
164
|
+
const service = {
|
|
165
|
+
name: `projects/${endpoint.project}/locations/${endpoint.region}/services/${functionNameToServiceName(endpoint.id)}`,
|
|
166
|
+
labels,
|
|
167
|
+
annotations,
|
|
168
|
+
template,
|
|
169
|
+
client: "cli-firebase",
|
|
170
|
+
};
|
|
171
|
+
if (endpoint.minInstances || endpoint.maxInstances) {
|
|
172
|
+
service.scaling = {};
|
|
173
|
+
proto.renameIfPresent(service.scaling, endpoint, "minInstanceCount", "minInstances");
|
|
174
|
+
proto.renameIfPresent(service.scaling, endpoint, "maxInstanceCount", "maxInstances");
|
|
175
|
+
}
|
|
176
|
+
return service;
|
|
177
|
+
}
|
|
178
|
+
exports.serviceFromEndpoint = serviceFromEndpoint;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.extractCodeBlock = exports.generateOperation = exports.chatWithFirebase = exports.generateSchema = void 0;
|
|
3
|
+
exports.extractCodeBlock = exports.generateOperation = exports.chatWithFirebase = exports.generateSchema = exports.PROMPT_GENERATE_SEED_DATA = exports.PROMPT_GENERATE_CONNECTOR = void 0;
|
|
4
4
|
const apiv2_1 = require("../apiv2");
|
|
5
5
|
const api_1 = require("../api");
|
|
6
6
|
const error_1 = require("../error");
|
|
@@ -9,6 +9,8 @@ const SCHEMA_GENERATOR_EXPERIENCE = "/appeco/firebase/fdc-schema-generator";
|
|
|
9
9
|
const GEMINI_IN_FIREBASE_EXPERIENCE = "/appeco/firebase/firebase-chat/free";
|
|
10
10
|
const OPERATION_GENERATION_EXPERIENCE = "/appeco/firebase/fdc-query-generator";
|
|
11
11
|
const FIREBASE_CHAT_REQUEST_CONTEXT_TYPE_NAME = "type.googleapis.com/google.cloud.cloudaicompanion.v1main.FirebaseChatRequestContext";
|
|
12
|
+
exports.PROMPT_GENERATE_CONNECTOR = "Create 4 operations for an app using the instance schema with proper authentication.";
|
|
13
|
+
exports.PROMPT_GENERATE_SEED_DATA = "Create a mutation to populate the database with some seed data.";
|
|
12
14
|
async function generateSchema(prompt, project, chatHistory = []) {
|
|
13
15
|
const res = await apiClient.post(`/v1beta/projects/${project}/locations/global/instances/default:completeTask`, {
|
|
14
16
|
input: { messages: [...chatHistory, { content: prompt, author: "USER" }] },
|
|
@@ -16,7 +18,7 @@ async function generateSchema(prompt, project, chatHistory = []) {
|
|
|
16
18
|
experience: SCHEMA_GENERATOR_EXPERIENCE,
|
|
17
19
|
},
|
|
18
20
|
});
|
|
19
|
-
return res.body.output.messages[0].content;
|
|
21
|
+
return extractCodeBlock(res.body.output.messages[0].content);
|
|
20
22
|
}
|
|
21
23
|
exports.generateSchema = generateSchema;
|
|
22
24
|
async function chatWithFirebase(prompt, project, chatHistory = []) {
|
|
@@ -42,7 +44,7 @@ async function generateOperation(prompt, service, project, chatHistory = []) {
|
|
|
42
44
|
},
|
|
43
45
|
},
|
|
44
46
|
});
|
|
45
|
-
return res.body.output.messages[0].content;
|
|
47
|
+
return extractCodeBlock(res.body.output.messages[0].content);
|
|
46
48
|
}
|
|
47
49
|
exports.generateOperation = generateOperation;
|
|
48
50
|
function extractCodeBlock(text) {
|