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
|
@@ -16,26 +16,33 @@ const utils_1 = require("../utils");
|
|
|
16
16
|
const cloudsqladmin_1 = require("../gcp/cloudsql/cloudsqladmin");
|
|
17
17
|
const cloudSqlAdminClient = require("../gcp/cloudsql/cloudsqladmin");
|
|
18
18
|
const errors = require("./errors");
|
|
19
|
+
const provisionCloudSql_1 = require("./provisionCloudSql");
|
|
19
20
|
async function setupSchemaIfNecessary(instanceId, databaseId, options) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
schemaInfo.setupStatus
|
|
24
|
-
|
|
21
|
+
try {
|
|
22
|
+
await (0, connect_1.setupIAMUsers)(instanceId, options);
|
|
23
|
+
const schemaInfo = await (0, permissionsSetup_1.getSchemaMetadata)(instanceId, databaseId, permissions_1.DEFAULT_SCHEMA, options);
|
|
24
|
+
switch (schemaInfo.setupStatus) {
|
|
25
|
+
case permissionsSetup_1.SchemaSetupStatus.BrownField:
|
|
26
|
+
case permissionsSetup_1.SchemaSetupStatus.GreenField:
|
|
27
|
+
return schemaInfo.setupStatus;
|
|
28
|
+
case permissionsSetup_1.SchemaSetupStatus.NotSetup:
|
|
29
|
+
case permissionsSetup_1.SchemaSetupStatus.NotFound:
|
|
30
|
+
return await (0, permissionsSetup_1.setupSQLPermissions)(instanceId, databaseId, schemaInfo, options, true);
|
|
31
|
+
default:
|
|
32
|
+
throw new error_1.FirebaseError(`Unexpected schema setup status: ${schemaInfo.setupStatus}`);
|
|
33
|
+
}
|
|
25
34
|
}
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
catch (err) {
|
|
36
|
+
throw new error_1.FirebaseError(`Cannot setup SQL schema permissions of ${instanceId}:${databaseId}\n${err}`);
|
|
28
37
|
}
|
|
29
|
-
return schemaInfo.setupStatus;
|
|
30
38
|
}
|
|
31
39
|
async function diffSchema(options, schema, schemaValidation) {
|
|
32
|
-
(0, utils_1.logLabeledBullet)("dataconnect", `
|
|
40
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `Generating SQL schema migrations...`);
|
|
33
41
|
const { serviceName, instanceName, databaseId, instanceId } = getIdentifiers(schema);
|
|
34
42
|
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, false);
|
|
35
|
-
let diffs = [];
|
|
36
|
-
await setupSchemaIfNecessary(instanceId, databaseId, options);
|
|
37
43
|
let validationMode = schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE";
|
|
38
44
|
setSchemaValidationMode(schema, validationMode);
|
|
45
|
+
let diffs = [];
|
|
39
46
|
try {
|
|
40
47
|
await (0, client_1.upsertSchema)(schema, true);
|
|
41
48
|
if (validationMode === "STRICT") {
|
|
@@ -95,18 +102,34 @@ async function diffSchema(options, schema, schemaValidation) {
|
|
|
95
102
|
}
|
|
96
103
|
exports.diffSchema = diffSchema;
|
|
97
104
|
async function migrateSchema(args) {
|
|
98
|
-
|
|
105
|
+
var _a;
|
|
106
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `Generating SQL schema migrations...`);
|
|
99
107
|
const { options, schema, validateOnly, schemaValidation } = args;
|
|
108
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
100
109
|
const { serviceName, instanceId, instanceName, databaseId } = getIdentifiers(schema);
|
|
101
110
|
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, true);
|
|
102
|
-
await (
|
|
103
|
-
|
|
111
|
+
const existingInstance = await cloudSqlAdminClient.getInstance(projectId, instanceId);
|
|
112
|
+
if (existingInstance.state === "PENDING_CREATE") {
|
|
113
|
+
const postgresql = (_a = schema.datasources.find((d) => d.postgresql)) === null || _a === void 0 ? void 0 : _a.postgresql;
|
|
114
|
+
if (!postgresql) {
|
|
115
|
+
throw new error_1.FirebaseError(`Cannot find Postgres datasource in the schema to deploy: ${serviceName}/schemas/${types_1.SCHEMA_ID}.\nIts datasources: ${JSON.stringify(schema.datasources)}`);
|
|
116
|
+
}
|
|
117
|
+
postgresql.schemaValidation = "NONE";
|
|
118
|
+
postgresql.schemaMigration = undefined;
|
|
119
|
+
await (0, client_1.upsertSchema)(schema, validateOnly);
|
|
120
|
+
postgresql.schemaValidation = undefined;
|
|
121
|
+
postgresql.schemaMigration = "MIGRATE_COMPATIBLE";
|
|
122
|
+
await (0, client_1.upsertSchema)(schema, validateOnly, true);
|
|
123
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `Skip SQL schema migration because Cloud SQL is still being created`);
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
104
126
|
await setupSchemaIfNecessary(instanceId, databaseId, options);
|
|
105
127
|
let validationMode = schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE";
|
|
106
128
|
setSchemaValidationMode(schema, validationMode);
|
|
129
|
+
let diffs = [];
|
|
107
130
|
try {
|
|
108
131
|
await (0, client_1.upsertSchema)(schema, validateOnly);
|
|
109
|
-
(0, utils_1.
|
|
132
|
+
(0, utils_1.logLabeledSuccess)("dataconnect", `database schema of ${instanceId}:${databaseId} is up to date.`);
|
|
110
133
|
}
|
|
111
134
|
catch (err) {
|
|
112
135
|
if ((err === null || err === void 0 ? void 0 : err.status) !== 400) {
|
|
@@ -169,19 +192,14 @@ exports.migrateSchema = migrateSchema;
|
|
|
169
192
|
async function grantRoleToUserInSchema(options, schema) {
|
|
170
193
|
const role = options.role;
|
|
171
194
|
const email = options.email;
|
|
172
|
-
const { instanceId, databaseId } = getIdentifiers(schema);
|
|
195
|
+
const { serviceName, instanceId, instanceName, databaseId } = getIdentifiers(schema);
|
|
173
196
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
174
197
|
const { user, mode } = (0, connect_1.toDatabaseUser)(email);
|
|
175
198
|
const fdcSqlRole = permissionsSetup_1.fdcSqlRoleMap[role](databaseId);
|
|
176
|
-
await (
|
|
177
|
-
const userIsCSQLAdmin = await (0, cloudsqladmin_1.iamUserIsCSQLAdmin)(options);
|
|
178
|
-
if (!userIsCSQLAdmin) {
|
|
179
|
-
throw new error_1.FirebaseError(`Only users with 'roles/cloudsql.admin' can grant SQL roles. If you do not have this role, ask your database administrator to run this command or manually grant ${fdcSqlRole} to ${user}`);
|
|
180
|
-
}
|
|
199
|
+
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, false);
|
|
181
200
|
const schemaSetupStatus = await setupSchemaIfNecessary(instanceId, databaseId, options);
|
|
182
|
-
if (schemaSetupStatus !== permissionsSetup_1.SchemaSetupStatus.GreenField &&
|
|
183
|
-
|
|
184
|
-
throw new error_1.FirebaseError(`Owner rule isn't available in brownfield databases. If you would like Data Connect to manage and own your database schema, run 'firebase dataconnect:sql:setup'`);
|
|
201
|
+
if (schemaSetupStatus !== permissionsSetup_1.SchemaSetupStatus.GreenField && role === "owner") {
|
|
202
|
+
throw new error_1.FirebaseError(`Owner rule isn't available in ${schemaSetupStatus} databases. If you would like Data Connect to manage and own your database schema, run 'firebase dataconnect:sql:setup'`);
|
|
185
203
|
}
|
|
186
204
|
await cloudSqlAdminClient.createUser(projectId, instanceId, mode, user);
|
|
187
205
|
await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, [`GRANT "${fdcSqlRole}" TO "${user}"`], false);
|
|
@@ -207,15 +225,15 @@ function setSchemaValidationMode(schema, schemaValidation) {
|
|
|
207
225
|
}
|
|
208
226
|
}
|
|
209
227
|
function getIdentifiers(schema) {
|
|
210
|
-
var _a, _b;
|
|
228
|
+
var _a, _b, _c;
|
|
211
229
|
const postgresDatasource = schema.datasources.find((d) => d.postgresql);
|
|
212
230
|
const databaseId = (_a = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.database;
|
|
213
231
|
if (!databaseId) {
|
|
214
|
-
throw new error_1.FirebaseError("
|
|
232
|
+
throw new error_1.FirebaseError("Data Connect schema must have a postgres datasource with a database name.");
|
|
215
233
|
}
|
|
216
|
-
const instanceName = (_b = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.cloudSql.instance;
|
|
234
|
+
const instanceName = (_c = (_b = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.cloudSql) === null || _c === void 0 ? void 0 : _c.instance;
|
|
217
235
|
if (!instanceName) {
|
|
218
|
-
throw new error_1.FirebaseError("
|
|
236
|
+
throw new error_1.FirebaseError("Data Connect schema must have a postgres datasource with a CloudSQL instance.");
|
|
219
237
|
}
|
|
220
238
|
const instanceId = instanceName.split("/").pop();
|
|
221
239
|
const serviceName = schema.name.replace(`/schemas/${types_1.SCHEMA_ID}`, "");
|
|
@@ -347,14 +365,24 @@ function displayInvalidConnectors(invalidConnectors) {
|
|
|
347
365
|
(0, utils_1.logLabeledWarning)("dataconnect", `The schema you are deploying is incompatible with the following existing connectors: ${connectorIds}.`);
|
|
348
366
|
(0, utils_1.logLabeledWarning)("dataconnect", `This is a ${clc.red("breaking")} change and may break existing apps.`);
|
|
349
367
|
}
|
|
350
|
-
async function ensureServiceIsConnectedToCloudSql(serviceName,
|
|
368
|
+
async function ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, linkIfNotConnected) {
|
|
369
|
+
var _a, _b, _c, _d;
|
|
351
370
|
let currentSchema = await (0, client_1.getSchema)(serviceName);
|
|
352
|
-
|
|
371
|
+
let postgresql = (_b = (_a = currentSchema === null || currentSchema === void 0 ? void 0 : currentSchema.datasources) === null || _a === void 0 ? void 0 : _a.find((d) => d.postgresql)) === null || _b === void 0 ? void 0 : _b.postgresql;
|
|
372
|
+
if ((currentSchema === null || currentSchema === void 0 ? void 0 : currentSchema.reconciling) &&
|
|
373
|
+
(postgresql === null || postgresql === void 0 ? void 0 : postgresql.ephemeral) &&
|
|
374
|
+
((_c = postgresql === null || postgresql === void 0 ? void 0 : postgresql.cloudSql) === null || _c === void 0 ? void 0 : _c.instance) &&
|
|
375
|
+
(postgresql === null || postgresql === void 0 ? void 0 : postgresql.schemaValidation) === "NONE") {
|
|
376
|
+
const [, , , , , serviceId] = serviceName.split("/");
|
|
377
|
+
const [, projectId, , , , instanceId] = postgresql.cloudSql.instance.split("/");
|
|
378
|
+
throw new error_1.FirebaseError(`While checking the service ${serviceId}, ` + (0, provisionCloudSql_1.cloudSQLBeingCreated)(projectId, instanceId));
|
|
379
|
+
}
|
|
380
|
+
if (!currentSchema || !postgresql) {
|
|
353
381
|
if (!linkIfNotConnected) {
|
|
354
382
|
(0, utils_1.logLabeledWarning)("dataconnect", `Not yet linked to the Cloud SQL instance.`);
|
|
355
383
|
return;
|
|
356
384
|
}
|
|
357
|
-
(0, utils_1.logLabeledBullet)("dataconnect", `
|
|
385
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `Linking the Cloud SQL instance...`);
|
|
358
386
|
currentSchema = {
|
|
359
387
|
name: `${serviceName}/schemas/${types_1.SCHEMA_ID}`,
|
|
360
388
|
source: {
|
|
@@ -362,37 +390,37 @@ async function ensureServiceIsConnectedToCloudSql(serviceName, instanceId, datab
|
|
|
362
390
|
},
|
|
363
391
|
datasources: [
|
|
364
392
|
{
|
|
365
|
-
postgresql: {
|
|
366
|
-
database: databaseId,
|
|
367
|
-
schemaValidation: "NONE",
|
|
368
|
-
cloudSql: {
|
|
369
|
-
instance: instanceId,
|
|
370
|
-
},
|
|
371
|
-
},
|
|
393
|
+
postgresql: { ephemeral: true },
|
|
372
394
|
},
|
|
373
395
|
],
|
|
374
396
|
};
|
|
375
397
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
398
|
+
if (!postgresql) {
|
|
399
|
+
postgresql = currentSchema.datasources[0].postgresql;
|
|
400
|
+
}
|
|
401
|
+
let alreadyConnected = !postgresql.ephemeral || false;
|
|
402
|
+
if (((_d = postgresql.cloudSql) === null || _d === void 0 ? void 0 : _d.instance) && postgresql.cloudSql.instance !== instanceName) {
|
|
403
|
+
alreadyConnected = false;
|
|
404
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `Switching connected Cloud SQL instance\n From ${postgresql.cloudSql.instance}\n To ${instanceName}`);
|
|
380
405
|
}
|
|
381
|
-
if (
|
|
382
|
-
|
|
406
|
+
if (postgresql.database && postgresql.database !== databaseId) {
|
|
407
|
+
alreadyConnected = false;
|
|
408
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `Switching connected Postgres database from ${postgresql.database} to ${databaseId}`);
|
|
383
409
|
}
|
|
384
|
-
if (
|
|
410
|
+
if (alreadyConnected) {
|
|
385
411
|
return;
|
|
386
412
|
}
|
|
387
|
-
postgresql.schemaValidation = "STRICT";
|
|
388
413
|
try {
|
|
414
|
+
postgresql.schemaValidation = "STRICT";
|
|
415
|
+
postgresql.database = databaseId;
|
|
416
|
+
postgresql.cloudSql = { instance: instanceName };
|
|
389
417
|
await (0, client_1.upsertSchema)(currentSchema, false);
|
|
390
418
|
}
|
|
391
419
|
catch (err) {
|
|
392
420
|
if ((err === null || err === void 0 ? void 0 : err.status) >= 500) {
|
|
393
421
|
throw err;
|
|
394
422
|
}
|
|
395
|
-
logger_1.logger.debug(err);
|
|
423
|
+
logger_1.logger.debug(`Failed to ensure service is connected to Cloud SQL: ${err.message}`);
|
|
396
424
|
}
|
|
397
425
|
}
|
|
398
426
|
exports.ensureServiceIsConnectedToCloudSql = ensureServiceIsConnectedToCloudSql;
|
|
@@ -33,12 +33,12 @@ async function default_1(context, options) {
|
|
|
33
33
|
utils.logLabeledSuccess("dataconnect", `Created service ${s.serviceName}`);
|
|
34
34
|
}));
|
|
35
35
|
if (servicesToDelete.length) {
|
|
36
|
+
const serviceToDeleteList = servicesToDelete.map((s) => " - " + s.name).join("\n");
|
|
36
37
|
if (await (0, prompt_1.confirm)({
|
|
37
|
-
force:
|
|
38
|
+
force: false,
|
|
38
39
|
nonInteractive: options.nonInteractive,
|
|
39
|
-
message: `The following services exist on ${projectId} but are not listed in your 'firebase.json'\n${
|
|
40
|
-
|
|
41
|
-
.join("\n")}\nWould you like to delete these services?`,
|
|
40
|
+
message: `The following services exist on ${projectId} but are not listed in your 'firebase.json'\n${serviceToDeleteList}\nWould you like to delete these services?`,
|
|
41
|
+
default: false,
|
|
42
42
|
})) {
|
|
43
43
|
await Promise.all(servicesToDelete.map(async (s) => {
|
|
44
44
|
await client.deleteService(s.name);
|
|
@@ -52,22 +52,20 @@ async function default_1(context, options) {
|
|
|
52
52
|
return !filters || (filters === null || filters === void 0 ? void 0 : filters.some((f) => si.dataConnectYaml.serviceId === f.serviceId));
|
|
53
53
|
})
|
|
54
54
|
.map(async (s) => {
|
|
55
|
-
var _a, _b;
|
|
55
|
+
var _a, _b, _c;
|
|
56
56
|
const postgresDatasource = s.schema.datasources.find((d) => d.postgresql);
|
|
57
57
|
if (postgresDatasource) {
|
|
58
|
-
const instanceId = (_a = postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instance.split("/").pop();
|
|
59
|
-
const databaseId = (
|
|
58
|
+
const instanceId = (_b = (_a = postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql) === null || _b === void 0 ? void 0 : _b.instance.split("/").pop();
|
|
59
|
+
const databaseId = (_c = postgresDatasource.postgresql) === null || _c === void 0 ? void 0 : _c.database;
|
|
60
60
|
if (!instanceId || !databaseId) {
|
|
61
61
|
return Promise.resolve();
|
|
62
62
|
}
|
|
63
|
-
|
|
64
|
-
return (0, provisionCloudSql_1.provisionCloudSql)({
|
|
63
|
+
return (0, provisionCloudSql_1.setupCloudSql)({
|
|
65
64
|
projectId,
|
|
66
65
|
location: (0, names_1.parseServiceName)(s.serviceName).location,
|
|
67
66
|
instanceId,
|
|
68
67
|
databaseId,
|
|
69
|
-
|
|
70
|
-
waitForCreation: true,
|
|
68
|
+
requireGoogleMlIntegration: (0, types_1.requiresVector)(s.deploymentMetadata),
|
|
71
69
|
});
|
|
72
70
|
}
|
|
73
71
|
}));
|
|
@@ -19,7 +19,7 @@ const types_1 = require("../../dataconnect/types");
|
|
|
19
19
|
const schemaMigration_1 = require("../../dataconnect/schemaMigration");
|
|
20
20
|
const freeTrial_1 = require("../../dataconnect/freeTrial");
|
|
21
21
|
async function default_1(context, options) {
|
|
22
|
-
var _a;
|
|
22
|
+
var _a, _b, _c;
|
|
23
23
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
24
24
|
if (!(await (0, cloudbilling_1.checkBillingEnabled)(projectId))) {
|
|
25
25
|
throw new error_1.FirebaseError((0, freeTrial_1.upgradeInstructions)(projectId));
|
|
@@ -27,7 +27,6 @@ async function default_1(context, options) {
|
|
|
27
27
|
await (0, ensureApis_1.ensureApis)(projectId);
|
|
28
28
|
await (0, requireTosAcceptance_1.requireTosAcceptance)(firedata_1.DATA_CONNECT_TOS_ID)(options);
|
|
29
29
|
const serviceCfgs = (0, fileUtils_1.readFirebaseJson)(options.config);
|
|
30
|
-
utils.logLabeledBullet("dataconnect", `Preparing to deploy`);
|
|
31
30
|
const filters = (0, filters_1.getResourceFilters)(options);
|
|
32
31
|
const serviceInfos = await Promise.all(serviceCfgs.map((c) => (0, load_1.load)(projectId, options.config, c.source)));
|
|
33
32
|
for (const si of serviceInfos) {
|
|
@@ -50,10 +49,10 @@ async function default_1(context, options) {
|
|
|
50
49
|
serviceInfos,
|
|
51
50
|
filters,
|
|
52
51
|
};
|
|
53
|
-
utils.logLabeledBullet("dataconnect", `Successfully
|
|
52
|
+
utils.logLabeledBullet("dataconnect", `Successfully compiled schema and connectors`);
|
|
54
53
|
if (options.dryRun) {
|
|
55
54
|
for (const si of serviceInfos) {
|
|
56
|
-
await (0, schemaMigration_1.diffSchema)(options, si.schema, (_a = si.dataConnectYaml.schema.datasource.postgresql) === null ||
|
|
55
|
+
await (0, schemaMigration_1.diffSchema)(options, si.schema, (_c = (_b = (_a = si.dataConnectYaml.schema) === null || _a === void 0 ? void 0 : _a.datasource) === null || _b === void 0 ? void 0 : _b.postgresql) === null || _c === void 0 ? void 0 : _c.schemaValidation);
|
|
57
56
|
}
|
|
58
57
|
utils.logLabeledBullet("dataconnect", "Checking for CloudSQL resources...");
|
|
59
58
|
await Promise.all(serviceInfos
|
|
@@ -61,23 +60,21 @@ async function default_1(context, options) {
|
|
|
61
60
|
return !filters || (filters === null || filters === void 0 ? void 0 : filters.some((f) => si.dataConnectYaml.serviceId === f.serviceId));
|
|
62
61
|
})
|
|
63
62
|
.map(async (s) => {
|
|
64
|
-
var _a, _b;
|
|
63
|
+
var _a, _b, _c;
|
|
65
64
|
const postgresDatasource = s.schema.datasources.find((d) => d.postgresql);
|
|
66
65
|
if (postgresDatasource) {
|
|
67
|
-
const instanceId = (_a = postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instance.split("/").pop();
|
|
68
|
-
const databaseId = (
|
|
66
|
+
const instanceId = (_b = (_a = postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql) === null || _b === void 0 ? void 0 : _b.instance.split("/").pop();
|
|
67
|
+
const databaseId = (_c = postgresDatasource.postgresql) === null || _c === void 0 ? void 0 : _c.database;
|
|
69
68
|
if (!instanceId || !databaseId) {
|
|
70
69
|
return Promise.resolve();
|
|
71
70
|
}
|
|
72
|
-
|
|
73
|
-
return (0, provisionCloudSql_1.provisionCloudSql)({
|
|
71
|
+
return (0, provisionCloudSql_1.setupCloudSql)({
|
|
74
72
|
projectId,
|
|
75
73
|
location: (0, names_1.parseServiceName)(s.serviceName).location,
|
|
76
74
|
instanceId,
|
|
77
75
|
databaseId,
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
dryRun: options.dryRun,
|
|
76
|
+
requireGoogleMlIntegration: (0, types_1.requiresVector)(s.deploymentMetadata),
|
|
77
|
+
dryRun: true,
|
|
81
78
|
});
|
|
82
79
|
}
|
|
83
80
|
}));
|
|
@@ -5,6 +5,7 @@ const client_1 = require("../../dataconnect/client");
|
|
|
5
5
|
const prompts_1 = require("../../dataconnect/prompts");
|
|
6
6
|
const schemaMigration_1 = require("../../dataconnect/schemaMigration");
|
|
7
7
|
const projectUtils_1 = require("../../projectUtils");
|
|
8
|
+
const names_1 = require("../../dataconnect/names");
|
|
8
9
|
async function default_1(context, options) {
|
|
9
10
|
const project = (0, projectUtils_1.needProjectId)(options);
|
|
10
11
|
const serviceInfos = context.dataconnect.serviceInfos;
|
|
@@ -17,14 +18,13 @@ async function default_1(context, options) {
|
|
|
17
18
|
}));
|
|
18
19
|
})
|
|
19
20
|
.map((s) => {
|
|
20
|
-
var _a;
|
|
21
|
+
var _a, _b, _c, _d;
|
|
21
22
|
return ({
|
|
22
23
|
schema: s.schema,
|
|
23
|
-
validationMode: (_a = s.dataConnectYaml.schema.datasource.postgresql) === null ||
|
|
24
|
+
validationMode: (_d = (_c = (_b = (_a = s.dataConnectYaml) === null || _a === void 0 ? void 0 : _a.schema) === null || _b === void 0 ? void 0 : _b.datasource) === null || _c === void 0 ? void 0 : _c.postgresql) === null || _d === void 0 ? void 0 : _d.schemaValidation,
|
|
24
25
|
});
|
|
25
26
|
});
|
|
26
27
|
if (wantSchemas.length) {
|
|
27
|
-
utils.logLabeledBullet("dataconnect", "Deploying Data Connect schemas...");
|
|
28
28
|
for (const s of wantSchemas) {
|
|
29
29
|
await (0, schemaMigration_1.migrateSchema)({
|
|
30
30
|
options,
|
|
@@ -32,8 +32,8 @@ async function default_1(context, options) {
|
|
|
32
32
|
validateOnly: false,
|
|
33
33
|
schemaValidation: s.validationMode,
|
|
34
34
|
});
|
|
35
|
+
utils.logLabeledSuccess("dataconnect", `Migrated schema ${s.schema.name}`);
|
|
35
36
|
}
|
|
36
|
-
utils.logLabeledBullet("dataconnect", "Schemas deployed.");
|
|
37
37
|
}
|
|
38
38
|
let wantConnectors = [];
|
|
39
39
|
wantConnectors = wantConnectors.concat(...serviceInfos.map((si) => si.connectorInfo
|
|
@@ -50,7 +50,6 @@ async function default_1(context, options) {
|
|
|
50
50
|
? []
|
|
51
51
|
: haveConnectors.filter((h) => !wantConnectors.some((w) => w.name === h.name));
|
|
52
52
|
if (wantConnectors.length) {
|
|
53
|
-
utils.logLabeledBullet("dataconnect", "Deploying connectors...");
|
|
54
53
|
await Promise.all(wantConnectors.map(async (c) => {
|
|
55
54
|
await (0, client_1.upsertConnector)(c);
|
|
56
55
|
utils.logLabeledSuccess("dataconnect", `Deployed connector ${c.name}`);
|
|
@@ -58,12 +57,19 @@ async function default_1(context, options) {
|
|
|
58
57
|
for (const c of connectorsToDelete) {
|
|
59
58
|
await (0, prompts_1.promptDeleteConnector)(options, c.name);
|
|
60
59
|
}
|
|
61
|
-
utils.logLabeledBullet("dataconnect", "Connectors deployed.");
|
|
62
60
|
}
|
|
63
61
|
else {
|
|
64
62
|
utils.logLabeledBullet("dataconnect", "No connectors to deploy.");
|
|
65
63
|
}
|
|
66
|
-
|
|
64
|
+
let consolePath = "/dataconnect";
|
|
65
|
+
if (serviceInfos.length === 1) {
|
|
66
|
+
const sn = (0, names_1.parseServiceName)(serviceInfos[0].serviceName);
|
|
67
|
+
consolePath += `/locations/${sn.location}/services/${sn.serviceId}/schema`;
|
|
68
|
+
}
|
|
69
|
+
utils.logLabeledSuccess("dataconnect", `Deployment complete! View your deployed schema and connectors at
|
|
70
|
+
|
|
71
|
+
${utils.consoleUrl(project, consolePath)}
|
|
72
|
+
`);
|
|
67
73
|
return;
|
|
68
74
|
}
|
|
69
75
|
exports.default = default_1;
|
|
@@ -22,6 +22,15 @@ async function createDatabase(context, options) {
|
|
|
22
22
|
if (!firestoreCfg.database) {
|
|
23
23
|
firestoreCfg.database = "(default)";
|
|
24
24
|
}
|
|
25
|
+
let edition = types.DatabaseEdition.STANDARD;
|
|
26
|
+
if (firestoreCfg.edition) {
|
|
27
|
+
const upperEdition = firestoreCfg.edition.toUpperCase();
|
|
28
|
+
if (upperEdition !== types.DatabaseEdition.STANDARD &&
|
|
29
|
+
upperEdition !== types.DatabaseEdition.ENTERPRISE) {
|
|
30
|
+
throw new error_1.FirebaseError(`Invalid edition specified for database in firebase.json: ${firestoreCfg.edition}`);
|
|
31
|
+
}
|
|
32
|
+
edition = upperEdition;
|
|
33
|
+
}
|
|
25
34
|
const api = new api_1.FirestoreApi();
|
|
26
35
|
try {
|
|
27
36
|
await api.getDatabase(options.projectId, firestoreCfg.database);
|
|
@@ -34,6 +43,7 @@ async function createDatabase(context, options) {
|
|
|
34
43
|
databaseId: firestoreCfg.database,
|
|
35
44
|
locationId: firestoreCfg.location || "nam5",
|
|
36
45
|
type: types.DatabaseType.FIRESTORE_NATIVE,
|
|
46
|
+
databaseEdition: edition,
|
|
37
47
|
deleteProtectionState: types.DatabaseDeleteProtectionState.DISABLED,
|
|
38
48
|
pointInTimeRecoveryEnablement: types.PointInTimeRecoveryEnablement.DISABLED,
|
|
39
49
|
};
|
|
@@ -94,7 +94,7 @@ function secretVersionName(s) {
|
|
|
94
94
|
return `projects/${s.projectId}/secrets/${s.secret}/versions/${(_a = s.version) !== null && _a !== void 0 ? _a : "latest"}`;
|
|
95
95
|
}
|
|
96
96
|
exports.secretVersionName = secretVersionName;
|
|
97
|
-
exports.AllFunctionsPlatforms = ["gcfv1", "gcfv2"];
|
|
97
|
+
exports.AllFunctionsPlatforms = ["gcfv1", "gcfv2", "run"];
|
|
98
98
|
function isHttpsTriggered(triggered) {
|
|
99
99
|
return {}.hasOwnProperty.call(triggered, "httpsTrigger");
|
|
100
100
|
}
|
|
@@ -184,6 +184,7 @@ async function loadExistingBackend(ctx) {
|
|
|
184
184
|
ctx.unreachableRegions = {
|
|
185
185
|
gcfV1: [],
|
|
186
186
|
gcfV2: [],
|
|
187
|
+
run: [],
|
|
187
188
|
};
|
|
188
189
|
const gcfV1Results = await gcf.listAllFunctions(ctx.projectId);
|
|
189
190
|
for (const apiFunction of gcfV1Results.functions) {
|
|
@@ -212,7 +213,7 @@ async function loadExistingBackend(ctx) {
|
|
|
212
213
|
}
|
|
213
214
|
}
|
|
214
215
|
async function checkAvailability(context, want) {
|
|
215
|
-
var _a, _b, _c, _d;
|
|
216
|
+
var _a, _b, _c, _d, _e;
|
|
216
217
|
if (!context.loadedExistingBackend) {
|
|
217
218
|
await loadExistingBackend(context);
|
|
218
219
|
}
|
|
@@ -248,6 +249,11 @@ async function checkAvailability(context, want) {
|
|
|
248
249
|
context.unreachableRegions.gcfV2.join("\n") +
|
|
249
250
|
"\nCloud Functions in these regions won't be deleted.");
|
|
250
251
|
}
|
|
252
|
+
if ((_e = context.unreachableRegions) === null || _e === void 0 ? void 0 : _e.run.length) {
|
|
253
|
+
utils.logLabeledWarning("functions", "The following Cloud Run regions are currently unreachable:\n" +
|
|
254
|
+
context.unreachableRegions.run.join("\n") +
|
|
255
|
+
"\nCloud Run services in these regions won't be deleted.");
|
|
256
|
+
}
|
|
251
257
|
}
|
|
252
258
|
exports.checkAvailability = checkAvailability;
|
|
253
259
|
function allEndpoints(backend) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toBackend = exports.envWithTypes = exports.resolveBackend = exports.AllIngressSettings = exports.AllVpcEgressSettings = exports.AllFunctionsPlatforms = exports.isBlockingTriggered = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.of = exports.empty = void 0;
|
|
3
|
+
exports.applyPrefix = exports.toBackend = exports.envWithTypes = exports.resolveBackend = exports.AllIngressSettings = exports.AllVpcEgressSettings = exports.AllFunctionsPlatforms = exports.isBlockingTriggered = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.of = exports.empty = void 0;
|
|
4
4
|
const backend = require("./backend");
|
|
5
5
|
const proto = require("../../gcp/proto");
|
|
6
6
|
const api = require("../../.../../api");
|
|
@@ -324,3 +324,25 @@ function discoverTrigger(endpoint, region, r) {
|
|
|
324
324
|
}
|
|
325
325
|
(0, functional_1.assertExhaustive)(endpoint);
|
|
326
326
|
}
|
|
327
|
+
function applyPrefix(build, prefix) {
|
|
328
|
+
if (!prefix) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const newEndpoints = {};
|
|
332
|
+
for (const [id, endpoint] of Object.entries(build.endpoints)) {
|
|
333
|
+
const newId = `${prefix}-${id}`;
|
|
334
|
+
if (newId.length > 63) {
|
|
335
|
+
throw new error_1.FirebaseError(`Function id '${newId}' exceeds 63 characters after applying prefix '${prefix}'. Please shorten the prefix or function name.`);
|
|
336
|
+
}
|
|
337
|
+
const fnIdRegex = /^[a-zA-Z][a-zA-Z0-9_-]{0,62}$/;
|
|
338
|
+
if (!fnIdRegex.test(newId)) {
|
|
339
|
+
throw new error_1.FirebaseError(`Function id '${newId}' is invalid after applying prefix '${prefix}'. Function names must start with a letter and can contain letters, numbers, underscores, and hyphens, with a maximum length of 63 characters.`);
|
|
340
|
+
}
|
|
341
|
+
newEndpoints[newId] = endpoint;
|
|
342
|
+
if (endpoint.secretEnvironmentVariables) {
|
|
343
|
+
endpoint.secretEnvironmentVariables = endpoint.secretEnvironmentVariables.map((secret) => (Object.assign(Object.assign({}, secret), { secret: `${prefix}-${secret.secret}` })));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
build.endpoints = newEndpoints;
|
|
347
|
+
}
|
|
348
|
+
exports.applyPrefix = applyPrefix;
|
|
@@ -23,7 +23,7 @@ async function defaultServiceAccount(e) {
|
|
|
23
23
|
if (e.platform === "gcfv1") {
|
|
24
24
|
return `${metadata.projectId}@appspot.gserviceaccount.com`;
|
|
25
25
|
}
|
|
26
|
-
else if (e.platform === "gcfv2") {
|
|
26
|
+
else if (e.platform === "gcfv2" || e.platform === "run") {
|
|
27
27
|
return await (0, computeEngine_1.getDefaultServiceAccount)(metadata.projectNumber);
|
|
28
28
|
}
|
|
29
29
|
(0, functional_1.assertExhaustive)(e.platform);
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.isEndpointFiltered = exports.isCodebaseFiltered = exports.groupEndpointsByCodebase = exports.targetCodebases = exports.getFunctionLabel = exports.getHumanFriendlyPlatformName = exports.getEndpointFilters = exports.parseFunctionSelector = exports.endpointMatchesFilter = exports.endpointMatchesAnyFilter = void 0;
|
|
4
4
|
const backend = require("./backend");
|
|
5
5
|
const projectConfig_1 = require("../../functions/projectConfig");
|
|
6
|
+
const functional_1 = require("../../functional");
|
|
6
7
|
function endpointMatchesAnyFilter(endpoint, filters) {
|
|
7
8
|
if (!filters) {
|
|
8
9
|
return true;
|
|
@@ -71,7 +72,13 @@ function getHumanFriendlyPlatformName(platform) {
|
|
|
71
72
|
if (platform === "gcfv1") {
|
|
72
73
|
return "1st Gen";
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
+
else if (platform === "gcfv2") {
|
|
76
|
+
return "2nd Gen";
|
|
77
|
+
}
|
|
78
|
+
else if (platform === "run") {
|
|
79
|
+
return "Cloud Run";
|
|
80
|
+
}
|
|
81
|
+
(0, functional_1.assertExhaustive)(platform);
|
|
75
82
|
}
|
|
76
83
|
exports.getHumanFriendlyPlatformName = getHumanFriendlyPlatformName;
|
|
77
84
|
function getFunctionLabel(fn) {
|
|
@@ -99,12 +99,12 @@ async function prepare(context, options, payload) {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
for (const endpoint of backend.allEndpoints(wantBackend)) {
|
|
102
|
-
endpoint.environmentVariables = Object.assign({}, wantBackend.environmentVariables
|
|
102
|
+
endpoint.environmentVariables = Object.assign({}, (wantBackend.environmentVariables || {}));
|
|
103
103
|
let resource;
|
|
104
104
|
if (endpoint.platform === "gcfv1") {
|
|
105
105
|
resource = `projects/${endpoint.project}/locations/${endpoint.region}/functions/${endpoint.id}`;
|
|
106
106
|
}
|
|
107
|
-
else if (endpoint.platform === "gcfv2") {
|
|
107
|
+
else if (endpoint.platform === "gcfv2" || endpoint.platform === "run") {
|
|
108
108
|
resource = `projects/${endpoint.project}/locations/${endpoint.region}/services/${endpoint.id}`;
|
|
109
109
|
}
|
|
110
110
|
else {
|
|
@@ -309,8 +309,10 @@ async function loadCodebases(config, options, firebaseConfig, runtimeConfig, fil
|
|
|
309
309
|
await runtimeDelegate.build();
|
|
310
310
|
const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId);
|
|
311
311
|
(0, utils_1.logLabeledBullet)("functions", `Loading and analyzing source code for codebase ${codebase} to determine what to deploy`);
|
|
312
|
-
|
|
313
|
-
|
|
312
|
+
const discoveredBuild = await runtimeDelegate.discoverBuild(runtimeConfig, Object.assign(Object.assign({}, firebaseEnvs), { GOOGLE_CLOUD_QUOTA_PROJECT: projectId }));
|
|
313
|
+
discoveredBuild.runtime = codebaseConfig.runtime;
|
|
314
|
+
build.applyPrefix(discoveredBuild, codebaseConfig.prefix || "");
|
|
315
|
+
wantBuilds[codebase] = discoveredBuild;
|
|
314
316
|
}
|
|
315
317
|
return wantBuilds;
|
|
316
318
|
}
|
|
@@ -74,7 +74,9 @@ async function packageSource(sourceDir, config, runtimeConfig) {
|
|
|
74
74
|
name: CONFIG_DEST_FILE,
|
|
75
75
|
mode: 420,
|
|
76
76
|
});
|
|
77
|
-
(
|
|
77
|
+
if (Object.keys(runtimeConfig).some((k) => k !== "firebase")) {
|
|
78
|
+
(0, deprecationWarnings_1.logFunctionsConfigDeprecationWarning)();
|
|
79
|
+
}
|
|
78
80
|
}
|
|
79
81
|
await pipeAsync(archive, fileStream);
|
|
80
82
|
}
|
|
@@ -130,6 +130,7 @@ function monthlyMinInstanceCost(endpoints) {
|
|
|
130
130
|
const usage = {
|
|
131
131
|
gcfv1: { 1: { ram: 0, cpu: 0 }, 2: { ram: 0, cpu: 0 } },
|
|
132
132
|
gcfv2: { 1: { ram: 0, cpu: 0 }, 2: { ram: 0, cpu: 0 } },
|
|
133
|
+
run: { 1: { ram: 0, cpu: 0 }, 2: { ram: 0, cpu: 0 } },
|
|
133
134
|
};
|
|
134
135
|
for (const endpoint of endpoints) {
|
|
135
136
|
if (endpoint.minInstances === undefined || endpoint.minInstances === null) {
|
|
@@ -147,10 +148,10 @@ function monthlyMinInstanceCost(endpoints) {
|
|
|
147
148
|
}
|
|
148
149
|
else {
|
|
149
150
|
const tier = V2_REGION_TO_TIER[endpoint.region];
|
|
150
|
-
usage[
|
|
151
|
-
usage[
|
|
152
|
-
usage[
|
|
153
|
-
usage[
|
|
151
|
+
usage[endpoint.platform][tier].ram =
|
|
152
|
+
usage[endpoint.platform][tier].ram + ramGb * SECONDS_PER_MONTH * endpoint.minInstances;
|
|
153
|
+
usage[endpoint.platform][tier].cpu =
|
|
154
|
+
usage[endpoint.platform][tier].cpu +
|
|
154
155
|
endpoint.cpu * SECONDS_PER_MONTH * endpoint.minInstances;
|
|
155
156
|
}
|
|
156
157
|
}
|
|
@@ -166,6 +167,12 @@ function monthlyMinInstanceCost(endpoints) {
|
|
|
166
167
|
let v2CpuBill = usage["gcfv2"][1].cpu * exports.V2_RATES.idleVCpu[1] + usage["gcfv2"][2].cpu * exports.V2_RATES.idleVCpu[2];
|
|
167
168
|
v2CpuBill -= exports.V2_FREE_TIER.vCpu * exports.V2_RATES.vCpu[1];
|
|
168
169
|
v2CpuBill = Math.max(v2CpuBill, 0);
|
|
169
|
-
|
|
170
|
+
let runMemoryBill = usage["run"][1].ram * exports.V2_RATES.memoryGb[1] + usage["run"][2].ram * exports.V2_RATES.memoryGb[2];
|
|
171
|
+
runMemoryBill -= exports.V2_FREE_TIER.memoryGb * exports.V2_RATES.memoryGb[1];
|
|
172
|
+
runMemoryBill = Math.max(runMemoryBill, 0);
|
|
173
|
+
let runCpuBill = usage["run"][1].cpu * exports.V2_RATES.idleVCpu[1] + usage["run"][2].cpu * exports.V2_RATES.idleVCpu[2];
|
|
174
|
+
runCpuBill -= exports.V2_FREE_TIER.vCpu * exports.V2_RATES.vCpu[1];
|
|
175
|
+
runCpuBill = Math.max(runCpuBill, 0);
|
|
176
|
+
return v1MemoryBill + v1CpuBill + v2MemoryBill + v2CpuBill + runMemoryBill + runCpuBill;
|
|
170
177
|
}
|
|
171
178
|
exports.monthlyMinInstanceCost = monthlyMinInstanceCost;
|