firebase-tools 14.12.1 → 14.14.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/errors.js +7 -1
- 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 +222 -170
- package/lib/deploy/dataconnect/deploy.js +9 -11
- package/lib/deploy/dataconnect/prepare.js +7 -10
- package/lib/deploy/dataconnect/release.js +42 -30
- 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 +8 -4
- package/lib/deploy/functions/pricing.js +12 -5
- package/lib/deploy/functions/release/fabricator.js +25 -3
- package/lib/emulator/controller.js +7 -3
- package/lib/emulator/downloadableEmulatorInfo.json +18 -18
- package/lib/emulator/functionsEmulator.js +11 -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/env.js +19 -15
- 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 +3 -2
- package/lib/gcp/cloudsql/permissionsSetup.js +23 -16
- 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 +36 -20
- 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 +6 -0
- package/lib/extensions/resolveSource.js +0 -24
|
@@ -20,19 +20,20 @@ const sdk = require("./sdk");
|
|
|
20
20
|
const fileUtils_1 = require("../../../dataconnect/fileUtils");
|
|
21
21
|
const fdcExperience_1 = require("../../../gemini/fdcExperience");
|
|
22
22
|
const configstore_1 = require("../../../configstore");
|
|
23
|
+
const track_1 = require("../../../track");
|
|
23
24
|
const DATACONNECT_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/dataconnect.yaml");
|
|
24
25
|
const CONNECTOR_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/connector.yaml");
|
|
25
26
|
const SCHEMA_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/schema.gql");
|
|
26
27
|
const QUERIES_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/queries.gql");
|
|
27
28
|
const MUTATIONS_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/mutations.gql");
|
|
28
29
|
const emptyConnector = {
|
|
29
|
-
id: "
|
|
30
|
-
path: "./
|
|
30
|
+
id: "example",
|
|
31
|
+
path: "./example",
|
|
31
32
|
files: [],
|
|
32
33
|
};
|
|
33
34
|
const defaultConnector = {
|
|
34
|
-
id: "
|
|
35
|
-
path: "./
|
|
35
|
+
id: "example",
|
|
36
|
+
path: "./example",
|
|
36
37
|
files: [
|
|
37
38
|
{
|
|
38
39
|
path: "queries.gql",
|
|
@@ -46,42 +47,33 @@ const defaultConnector = {
|
|
|
46
47
|
};
|
|
47
48
|
const defaultSchema = { path: "schema.gql", content: SCHEMA_TEMPLATE };
|
|
48
49
|
async function askQuestions(setup) {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
let info = {
|
|
50
|
+
const info = {
|
|
51
|
+
analyticsFlow: "cli",
|
|
52
|
+
appDescription: "",
|
|
54
53
|
serviceId: "",
|
|
55
54
|
locationId: "",
|
|
56
55
|
cloudSqlInstanceId: "",
|
|
57
|
-
isNewInstance: false,
|
|
58
56
|
cloudSqlDatabase: "",
|
|
59
|
-
isNewDatabase: false,
|
|
60
|
-
connectors: [],
|
|
61
|
-
schemaGql: [],
|
|
62
|
-
shouldProvisionCSQL: false,
|
|
63
57
|
};
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
info.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
default: true,
|
|
84
|
-
})));
|
|
58
|
+
if (setup.projectId) {
|
|
59
|
+
const hasBilling = await (0, cloudbilling_1.isBillingEnabled)(setup);
|
|
60
|
+
await (0, ensureApis_1.ensureApis)(setup.projectId);
|
|
61
|
+
await promptForExistingServices(setup, info);
|
|
62
|
+
if (!info.serviceGql) {
|
|
63
|
+
if (!configstore_1.configstore.get("gemini")) {
|
|
64
|
+
(0, utils_1.logBullet)("Learn more about Gemini in Firebase and how it uses your data: https://firebase.google.com/docs/gemini-in-firebase#how-gemini-in-firebase-uses-your-data");
|
|
65
|
+
}
|
|
66
|
+
info.appDescription = await (0, prompt_1.input)({
|
|
67
|
+
message: `Describe your app to automatically generate a schema [Enter to skip]:`,
|
|
68
|
+
});
|
|
69
|
+
if (info.appDescription) {
|
|
70
|
+
configstore_1.configstore.set("gemini", true);
|
|
71
|
+
await (0, ensureApis_1.ensureGIFApis)(setup.projectId);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (hasBilling) {
|
|
75
|
+
await promptForCloudSQL(setup, info);
|
|
76
|
+
}
|
|
85
77
|
}
|
|
86
78
|
setup.featureInfo = setup.featureInfo || {};
|
|
87
79
|
setup.featureInfo.dataconnect = info;
|
|
@@ -96,65 +88,192 @@ async function actuate(setup, config, options) {
|
|
|
96
88
|
if (!info) {
|
|
97
89
|
throw new Error("Data Connect feature RequiredInfo is not provided");
|
|
98
90
|
}
|
|
99
|
-
|
|
100
|
-
info.
|
|
101
|
-
info.cloudSqlInstanceId =
|
|
102
|
-
info.cloudSqlInstanceId || `${info.serviceId.toLowerCase() || "app"}-fdc`;
|
|
91
|
+
info.serviceId = info.serviceId || defaultServiceId();
|
|
92
|
+
info.cloudSqlInstanceId = info.cloudSqlInstanceId || `${info.serviceId.toLowerCase()}-fdc`;
|
|
103
93
|
info.locationId = info.locationId || `us-central1`;
|
|
104
94
|
info.cloudSqlDatabase = info.cloudSqlDatabase || `fdcdb`;
|
|
105
|
-
|
|
106
|
-
info
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
95
|
+
try {
|
|
96
|
+
await actuateWithInfo(setup, config, info, options);
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
void (0, track_1.trackGA4)("dataconnect_init", {
|
|
100
|
+
project_status: setup.projectId ? (setup.isBillingEnabled ? "blaze" : "spark") : "missing",
|
|
101
|
+
flow: info.analyticsFlow,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.actuate = actuate;
|
|
106
|
+
async function actuateWithInfo(setup, config, info, options) {
|
|
107
|
+
const projectId = setup.projectId;
|
|
108
|
+
if (!projectId) {
|
|
109
|
+
info.analyticsFlow += "_save_template";
|
|
110
|
+
return await writeFiles(config, info, { schemaGql: [defaultSchema], connectors: [defaultConnector] }, options);
|
|
111
|
+
}
|
|
112
|
+
const hasBilling = await (0, cloudbilling_1.isBillingEnabled)(setup);
|
|
113
|
+
if (hasBilling) {
|
|
114
|
+
await (0, provisionCloudSql_1.setupCloudSql)({
|
|
115
|
+
projectId: projectId,
|
|
113
116
|
location: info.locationId,
|
|
114
117
|
instanceId: info.cloudSqlInstanceId,
|
|
115
118
|
databaseId: info.cloudSqlDatabase,
|
|
116
|
-
|
|
117
|
-
waitForCreation: false,
|
|
119
|
+
requireGoogleMlIntegration: false,
|
|
118
120
|
});
|
|
119
121
|
}
|
|
122
|
+
if (!info.appDescription) {
|
|
123
|
+
if (info.serviceGql) {
|
|
124
|
+
info.analyticsFlow += "_save_downloaded";
|
|
125
|
+
return await writeFiles(config, info, info.serviceGql, options);
|
|
126
|
+
}
|
|
127
|
+
info.analyticsFlow += "_save_template";
|
|
128
|
+
return await writeFiles(config, info, { schemaGql: [defaultSchema], connectors: [defaultConnector] }, options);
|
|
129
|
+
}
|
|
130
|
+
const serviceName = `projects/${projectId}/locations/${info.locationId}/services/${info.serviceId}`;
|
|
131
|
+
const serviceAlreadyExists = !(await (0, client_1.createService)(projectId, info.locationId, info.serviceId));
|
|
132
|
+
const schemaGql = await (0, utils_1.promiseWithSpinner)(() => (0, fdcExperience_1.generateSchema)(info.appDescription, projectId), "Generating the Data Connect Schema...");
|
|
133
|
+
const schemaFiles = [{ path: "schema.gql", content: schemaGql }];
|
|
134
|
+
if (serviceAlreadyExists) {
|
|
135
|
+
(0, utils_1.logLabeledError)("dataconnect", `Data Connect Service ${serviceName} already exists. Skip saving them...`);
|
|
136
|
+
info.analyticsFlow += "_save_gemini_service_already_exists";
|
|
137
|
+
return await writeFiles(config, info, { schemaGql: schemaFiles, connectors: [] }, options);
|
|
138
|
+
}
|
|
139
|
+
await (0, utils_1.promiseWithSpinner)(async () => {
|
|
140
|
+
const [saveSchemaGql, waitForCloudSQLProvision] = schemasDeploySequence(projectId, info, schemaFiles, hasBilling);
|
|
141
|
+
await (0, client_1.upsertSchema)(saveSchemaGql);
|
|
142
|
+
if (waitForCloudSQLProvision) {
|
|
143
|
+
void (0, client_1.upsertSchema)(waitForCloudSQLProvision);
|
|
144
|
+
}
|
|
145
|
+
}, "Saving the Data Connect Schema...");
|
|
146
|
+
try {
|
|
147
|
+
const [operationGql, seedDataGql] = await (0, utils_1.promiseWithSpinner)(() => Promise.all([
|
|
148
|
+
(0, fdcExperience_1.generateOperation)(fdcExperience_1.PROMPT_GENERATE_CONNECTOR, serviceName, projectId),
|
|
149
|
+
(0, fdcExperience_1.generateOperation)(fdcExperience_1.PROMPT_GENERATE_SEED_DATA, serviceName, projectId),
|
|
150
|
+
]), "Generating the Data Connect Operations...");
|
|
151
|
+
const connectors = [
|
|
152
|
+
{
|
|
153
|
+
id: "example",
|
|
154
|
+
path: "./example",
|
|
155
|
+
files: [
|
|
156
|
+
{
|
|
157
|
+
path: "queries.gql",
|
|
158
|
+
content: operationGql,
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
info.analyticsFlow += "_save_gemini";
|
|
164
|
+
await writeFiles(config, info, { schemaGql: schemaFiles, connectors: connectors, seedDataGql: seedDataGql }, options);
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
(0, utils_1.logLabeledError)("dataconnect", `Operation Generation failed...`);
|
|
168
|
+
info.analyticsFlow += "_save_gemini_operation_error";
|
|
169
|
+
await writeFiles(config, info, { schemaGql: schemaFiles, connectors: [] }, options);
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
120
172
|
}
|
|
121
|
-
|
|
122
|
-
|
|
173
|
+
function schemasDeploySequence(projectId, info, schemaFiles, linkToCloudSql) {
|
|
174
|
+
const serviceName = `projects/${projectId}/locations/${info.locationId}/services/${info.serviceId}`;
|
|
175
|
+
if (!linkToCloudSql) {
|
|
176
|
+
return [
|
|
177
|
+
{
|
|
178
|
+
name: `${serviceName}/schemas/${types_1.SCHEMA_ID}`,
|
|
179
|
+
datasources: [{ postgresql: {} }],
|
|
180
|
+
source: {
|
|
181
|
+
files: schemaFiles,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
return [
|
|
187
|
+
{
|
|
188
|
+
name: `${serviceName}/schemas/${types_1.SCHEMA_ID}`,
|
|
189
|
+
datasources: [
|
|
190
|
+
{
|
|
191
|
+
postgresql: {
|
|
192
|
+
database: info.cloudSqlDatabase,
|
|
193
|
+
cloudSql: {
|
|
194
|
+
instance: `projects/${projectId}/locations/${info.locationId}/instances/${info.cloudSqlInstanceId}`,
|
|
195
|
+
},
|
|
196
|
+
schemaValidation: "NONE",
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
source: {
|
|
201
|
+
files: schemaFiles,
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: `${serviceName}/schemas/${types_1.SCHEMA_ID}`,
|
|
206
|
+
datasources: [
|
|
207
|
+
{
|
|
208
|
+
postgresql: {
|
|
209
|
+
database: info.cloudSqlDatabase,
|
|
210
|
+
cloudSql: {
|
|
211
|
+
instance: `projects/${projectId}/locations/${info.locationId}/instances/${info.cloudSqlInstanceId}`,
|
|
212
|
+
},
|
|
213
|
+
schemaMigration: "MIGRATE_COMPATIBLE",
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
source: {
|
|
218
|
+
files: schemaFiles,
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
];
|
|
222
|
+
}
|
|
223
|
+
async function postSetup(setup, config, options) {
|
|
224
|
+
var _a;
|
|
225
|
+
const info = (_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.dataconnect;
|
|
226
|
+
if (!info) {
|
|
227
|
+
throw new Error("Data Connect feature RequiredInfo is not provided");
|
|
228
|
+
}
|
|
229
|
+
const instructions = [];
|
|
123
230
|
const cwdPlatformGuess = await (0, fileUtils_1.getPlatformFromFolder)(process.cwd());
|
|
124
231
|
if (cwdPlatformGuess !== types_1.Platform.NONE || (0, utils_1.envOverride)("FDC_CONNECTOR", "")) {
|
|
125
|
-
await sdk.doSetup(setup, config);
|
|
232
|
+
await sdk.doSetup(setup, config, options);
|
|
126
233
|
}
|
|
127
234
|
else {
|
|
128
|
-
|
|
235
|
+
instructions.push(`To add the generated SDK to your app, run ${clc.bold("firebase init dataconnect:sdk")}`);
|
|
236
|
+
}
|
|
237
|
+
if (info.appDescription) {
|
|
238
|
+
instructions.push(`You can visualize the Data Connect Schema in Firebase Console:
|
|
239
|
+
|
|
240
|
+
https://console.firebase.google.com/project/${setup.projectId}/dataconnect/locations/${info.locationId}/services/${info.serviceId}/schema`);
|
|
129
241
|
}
|
|
130
242
|
if (setup.projectId && !setup.isBillingEnabled) {
|
|
131
|
-
|
|
243
|
+
instructions.push((0, freeTrial_1.upgradeInstructions)(setup.projectId));
|
|
244
|
+
}
|
|
245
|
+
logger_1.logger.info(`\n${clc.bold("To get started with Firebase Data Connect:")}`);
|
|
246
|
+
for (const i of instructions) {
|
|
247
|
+
(0, utils_1.logBullet)(i + "\n");
|
|
132
248
|
}
|
|
133
249
|
}
|
|
134
250
|
exports.postSetup = postSetup;
|
|
135
|
-
async function writeFiles(config, info, options) {
|
|
251
|
+
async function writeFiles(config, info, serviceGql, options) {
|
|
136
252
|
const dir = config.get("dataconnect.source") || "dataconnect";
|
|
137
|
-
const subbedDataconnectYaml = subDataconnectYamlValues(Object.assign(Object.assign({}, info), { connectorDirs:
|
|
253
|
+
const subbedDataconnectYaml = subDataconnectYamlValues(Object.assign(Object.assign({}, info), { connectorDirs: serviceGql.connectors.map((c) => c.path) }));
|
|
138
254
|
config.set("dataconnect", { source: dir });
|
|
139
255
|
await config.askWriteProjectFile((0, path_1.join)(dir, "dataconnect.yaml"), subbedDataconnectYaml, !!options.force, true);
|
|
140
|
-
if (
|
|
141
|
-
|
|
256
|
+
if (serviceGql.seedDataGql) {
|
|
257
|
+
await config.askWriteProjectFile((0, path_1.join)(dir, "seed_data.gql"), serviceGql.seedDataGql, !!options.force);
|
|
258
|
+
}
|
|
259
|
+
if (serviceGql.schemaGql.length) {
|
|
260
|
+
for (const f of serviceGql.schemaGql) {
|
|
142
261
|
await config.askWriteProjectFile((0, path_1.join)(dir, "schema", f.path), f.content, !!options.force);
|
|
143
262
|
}
|
|
144
263
|
}
|
|
145
264
|
else {
|
|
146
265
|
fs.ensureFileSync((0, path_1.join)(dir, "schema", "schema.gql"));
|
|
147
266
|
}
|
|
148
|
-
for (const c of
|
|
149
|
-
await writeConnectorFiles(config, c);
|
|
267
|
+
for (const c of serviceGql.connectors) {
|
|
268
|
+
await writeConnectorFiles(config, c, options);
|
|
150
269
|
}
|
|
151
270
|
}
|
|
152
|
-
async function writeConnectorFiles(config, connectorInfo) {
|
|
271
|
+
async function writeConnectorFiles(config, connectorInfo, options) {
|
|
153
272
|
const subbedConnectorYaml = subConnectorYamlValues({ connectorId: connectorInfo.id });
|
|
154
273
|
const dir = config.get("dataconnect.source") || "dataconnect";
|
|
155
|
-
await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, "connector.yaml"), subbedConnectorYaml);
|
|
274
|
+
await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, "connector.yaml"), subbedConnectorYaml, !!options.force);
|
|
156
275
|
for (const f of connectorInfo.files) {
|
|
157
|
-
await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, f.path), f.content);
|
|
276
|
+
await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, f.path), f.content, !!options.force);
|
|
158
277
|
}
|
|
159
278
|
}
|
|
160
279
|
function subDataconnectYamlValues(replacementValues) {
|
|
@@ -182,53 +301,63 @@ function subConnectorYamlValues(replacementValues) {
|
|
|
182
301
|
return replaced;
|
|
183
302
|
}
|
|
184
303
|
async function promptForExistingServices(setup, info) {
|
|
185
|
-
var _a, _b, _c, _d;
|
|
304
|
+
var _a, _b, _c, _d, _e;
|
|
186
305
|
if (!setup.projectId) {
|
|
187
|
-
return
|
|
306
|
+
return;
|
|
188
307
|
}
|
|
189
308
|
const existingServices = await (0, client_1.listAllServices)(setup.projectId);
|
|
309
|
+
if (!existingServices.length) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
190
312
|
const existingServicesAndSchemas = await Promise.all(existingServices.map(async (s) => {
|
|
191
313
|
return { service: s, schema: await (0, client_1.getSchema)(s.name) };
|
|
192
314
|
}));
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
315
|
+
const choice = await chooseExistingService(existingServicesAndSchemas);
|
|
316
|
+
if (!choice) {
|
|
317
|
+
const existingServiceIds = existingServices.map((s) => s.name.split("/").pop());
|
|
318
|
+
info.serviceId = newUniqueId(defaultServiceId(), existingServiceIds);
|
|
319
|
+
info.analyticsFlow += "_pick_new_service";
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
info.analyticsFlow += "_pick_existing_service";
|
|
323
|
+
const serviceName = (0, names_1.parseServiceName)(choice.service.name);
|
|
324
|
+
info.serviceId = serviceName.serviceId;
|
|
325
|
+
info.locationId = serviceName.location;
|
|
326
|
+
info.serviceGql = {
|
|
327
|
+
schemaGql: [],
|
|
328
|
+
connectors: [emptyConnector],
|
|
329
|
+
};
|
|
330
|
+
if (choice.schema) {
|
|
331
|
+
const primaryDatasource = choice.schema.datasources.find((d) => d.postgresql);
|
|
332
|
+
if ((_b = (_a = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql) === null || _b === void 0 ? void 0 : _b.instance) {
|
|
333
|
+
const instanceName = (0, names_1.parseCloudSQLInstanceName)(primaryDatasource.postgresql.cloudSql.instance);
|
|
334
|
+
info.cloudSqlInstanceId = instanceName.instanceId;
|
|
335
|
+
}
|
|
336
|
+
if ((_c = choice.schema.source.files) === null || _c === void 0 ? void 0 : _c.length) {
|
|
337
|
+
info.serviceGql.schemaGql = choice.schema.source.files;
|
|
338
|
+
}
|
|
339
|
+
info.cloudSqlDatabase = (_e = (_d = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _d === void 0 ? void 0 : _d.database) !== null && _e !== void 0 ? _e : "";
|
|
340
|
+
const connectors = await (0, client_1.listConnectors)(choice.service.name, [
|
|
341
|
+
"connectors.name",
|
|
342
|
+
"connectors.source.files",
|
|
343
|
+
]);
|
|
344
|
+
if (connectors.length) {
|
|
345
|
+
info.serviceGql.connectors = connectors.map((c) => {
|
|
346
|
+
const id = c.name.split("/").pop();
|
|
347
|
+
return {
|
|
348
|
+
id,
|
|
349
|
+
path: connectors.length === 1 ? "./connector" : `./${id}`,
|
|
350
|
+
files: c.source.files || [],
|
|
351
|
+
};
|
|
352
|
+
});
|
|
226
353
|
}
|
|
227
354
|
}
|
|
228
|
-
return
|
|
355
|
+
return;
|
|
229
356
|
}
|
|
230
357
|
async function chooseExistingService(existing) {
|
|
231
|
-
const
|
|
358
|
+
const fdcConnector = (0, utils_1.envOverride)("FDC_CONNECTOR", "");
|
|
359
|
+
const fdcService = (0, utils_1.envOverride)("FDC_SERVICE", "");
|
|
360
|
+
const serviceEnvVar = fdcConnector || fdcService;
|
|
232
361
|
if (serviceEnvVar) {
|
|
233
362
|
const [serviceLocationFromEnvVar, serviceIdFromEnvVar] = serviceEnvVar.split("/");
|
|
234
363
|
const serviceFromEnvVar = existing.find((s) => {
|
|
@@ -240,7 +369,8 @@ async function chooseExistingService(existing) {
|
|
|
240
369
|
(0, utils_1.logBullet)(`Picking up the existing service ${clc.bold(serviceLocationFromEnvVar + "/" + serviceIdFromEnvVar)}.`);
|
|
241
370
|
return serviceFromEnvVar;
|
|
242
371
|
}
|
|
243
|
-
|
|
372
|
+
const envVarName = fdcConnector ? "FDC_CONNECTOR" : "FDC_SERVICE";
|
|
373
|
+
(0, utils_1.logWarning)(`Unable to pick up an existing service based on ${envVarName}=${serviceEnvVar}.`);
|
|
244
374
|
}
|
|
245
375
|
const choices = existing.map((s) => {
|
|
246
376
|
const serviceName = (0, names_1.parseServiceName)(s.service.name);
|
|
@@ -256,7 +386,10 @@ async function chooseExistingService(existing) {
|
|
|
256
386
|
});
|
|
257
387
|
}
|
|
258
388
|
async function promptForCloudSQL(setup, info) {
|
|
259
|
-
if (
|
|
389
|
+
if (!setup.projectId) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (info.cloudSqlInstanceId === "") {
|
|
260
393
|
const instances = await cloudsql.listInstances(setup.projectId);
|
|
261
394
|
let choices = instances.map((i) => {
|
|
262
395
|
var _a;
|
|
@@ -279,78 +412,37 @@ async function promptForCloudSQL(setup, info) {
|
|
|
279
412
|
choices,
|
|
280
413
|
});
|
|
281
414
|
if (info.cloudSqlInstanceId !== "") {
|
|
415
|
+
info.analyticsFlow += "_pick_existing_csql";
|
|
282
416
|
info.locationId = choices.find((c) => c.value === info.cloudSqlInstanceId).location;
|
|
283
417
|
}
|
|
418
|
+
else {
|
|
419
|
+
info.analyticsFlow += "_pick_new_csql";
|
|
420
|
+
info.cloudSqlInstanceId = await (0, prompt_1.input)({
|
|
421
|
+
message: `What ID would you like to use for your new CloudSQL instance?`,
|
|
422
|
+
default: newUniqueId(`${defaultServiceId().toLowerCase()}-fdc`, instances.map((i) => i.name)),
|
|
423
|
+
});
|
|
424
|
+
}
|
|
284
425
|
}
|
|
285
426
|
}
|
|
286
|
-
if (info.cloudSqlInstanceId === "") {
|
|
287
|
-
info.isNewInstance = true;
|
|
288
|
-
info.cloudSqlInstanceId = await (0, prompt_1.input)({
|
|
289
|
-
message: `What ID would you like to use for your new CloudSQL instance?`,
|
|
290
|
-
default: `${info.serviceId.toLowerCase() || "app"}-fdc`,
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
427
|
if (info.locationId === "") {
|
|
294
428
|
const choices = await locationChoices(setup);
|
|
295
429
|
info.locationId = await (0, prompt_1.select)({
|
|
296
430
|
message: "What location would like to use?",
|
|
297
431
|
choices,
|
|
432
|
+
default: "us-central1",
|
|
298
433
|
});
|
|
299
434
|
}
|
|
300
|
-
if (info.
|
|
435
|
+
if (info.cloudSqlInstanceId !== "" && info.cloudSqlDatabase === "") {
|
|
301
436
|
try {
|
|
302
437
|
const dbs = await cloudsql.listDatabases(setup.projectId, info.cloudSqlInstanceId);
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
});
|
|
306
|
-
choices.push({ name: "Create a new database", value: "" });
|
|
307
|
-
if (dbs.length) {
|
|
308
|
-
info.cloudSqlDatabase = await (0, prompt_1.select)({
|
|
309
|
-
message: `Which database in ${info.cloudSqlInstanceId} would you like to use?`,
|
|
310
|
-
choices,
|
|
311
|
-
});
|
|
312
|
-
}
|
|
438
|
+
const existing = dbs.map((d) => d.name);
|
|
439
|
+
info.cloudSqlDatabase = newUniqueId("fdcdb", existing);
|
|
313
440
|
}
|
|
314
441
|
catch (err) {
|
|
315
442
|
logger_1.logger.debug(`[dataconnect] Cannot list databases during init: ${err}`);
|
|
316
443
|
}
|
|
317
444
|
}
|
|
318
|
-
|
|
319
|
-
info.isNewDatabase = true;
|
|
320
|
-
info.cloudSqlDatabase = await (0, prompt_1.input)({
|
|
321
|
-
message: `What ID would you like to use for your new database in ${info.cloudSqlInstanceId}?`,
|
|
322
|
-
default: `fdcdb`,
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
return info;
|
|
326
|
-
}
|
|
327
|
-
async function promptForSchema(setup, info) {
|
|
328
|
-
if (info.serviceId === "") {
|
|
329
|
-
info.serviceId = await (0, prompt_1.input)({
|
|
330
|
-
message: "What ID would you like to use for this service?",
|
|
331
|
-
default: (0, path_1.basename)(process.cwd()),
|
|
332
|
-
});
|
|
333
|
-
if (setup.projectId) {
|
|
334
|
-
if (!configstore_1.configstore.get("gemini")) {
|
|
335
|
-
(0, utils_1.logBullet)("Learn more about Gemini in Firebase and how it uses your data: https://firebase.google.com/docs/gemini-in-firebase#how-gemini-in-firebase-uses-your-data");
|
|
336
|
-
}
|
|
337
|
-
if (await (0, prompt_1.confirm)({
|
|
338
|
-
message: `Do you want Gemini in Firebase to help generate a schema for your service?`,
|
|
339
|
-
default: false,
|
|
340
|
-
})) {
|
|
341
|
-
configstore_1.configstore.set("gemini", true);
|
|
342
|
-
await (0, ensureApis_1.ensureGIFApis)(setup.projectId);
|
|
343
|
-
const prompt = await (0, prompt_1.input)({
|
|
344
|
-
message: "Describe the app you are building:",
|
|
345
|
-
default: "movie rating app",
|
|
346
|
-
});
|
|
347
|
-
const schema = await (0, utils_1.promiseWithSpinner)(() => (0, fdcExperience_1.generateSchema)(prompt, setup.projectId), "Generating the Data Connect Schema...");
|
|
348
|
-
info.schemaGql = [{ path: "schema.gql", content: (0, fdcExperience_1.extractCodeBlock)(schema) }];
|
|
349
|
-
info.connectors = [emptyConnector];
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
return info;
|
|
445
|
+
return;
|
|
354
446
|
}
|
|
355
447
|
async function locationChoices(setup) {
|
|
356
448
|
if (setup.projectId) {
|
|
@@ -372,17 +464,29 @@ async function locationChoices(setup) {
|
|
|
372
464
|
];
|
|
373
465
|
}
|
|
374
466
|
}
|
|
467
|
+
function newUniqueId(recommended, existingIDs) {
|
|
468
|
+
let id = recommended;
|
|
469
|
+
let i = 1;
|
|
470
|
+
while (existingIDs.includes(id)) {
|
|
471
|
+
id = `${recommended}-${i}`;
|
|
472
|
+
i++;
|
|
473
|
+
}
|
|
474
|
+
return id;
|
|
475
|
+
}
|
|
476
|
+
function defaultServiceId() {
|
|
477
|
+
return toDNSCompatibleId((0, path_1.basename)(process.cwd()));
|
|
478
|
+
}
|
|
375
479
|
function toDNSCompatibleId(id) {
|
|
376
|
-
|
|
480
|
+
id = (0, path_1.basename)(id)
|
|
377
481
|
.toLowerCase()
|
|
378
482
|
.replaceAll(/[^a-z0-9-]/g, "")
|
|
379
483
|
.slice(0, 63);
|
|
380
|
-
while (
|
|
381
|
-
|
|
484
|
+
while (id.endsWith("-") && id.length) {
|
|
485
|
+
id = id.slice(0, id.length - 1);
|
|
382
486
|
}
|
|
383
|
-
while (
|
|
384
|
-
|
|
487
|
+
while (id.startsWith("-") && id.length) {
|
|
488
|
+
id = id.slice(1, id.length);
|
|
385
489
|
}
|
|
386
|
-
return
|
|
490
|
+
return id || "app";
|
|
387
491
|
}
|
|
388
492
|
exports.toDNSCompatibleId = toDNSCompatibleId;
|