firebase-tools 14.14.0 → 14.15.1
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/lib/apphosting/backend.js +40 -15
- package/lib/auth.js +37 -2
- package/lib/command.js +19 -5
- package/lib/commands/apphosting-backends-create.js +8 -5
- package/lib/commands/dataconnect-sdk-generate.js +3 -5
- package/lib/commands/dataconnect-sql-diff.js +2 -2
- package/lib/commands/dataconnect-sql-grant.js +2 -2
- package/lib/commands/dataconnect-sql-migrate.js +2 -2
- package/lib/commands/dataconnect-sql-setup.js +2 -2
- package/lib/commands/dataconnect-sql-shell.js +2 -2
- package/lib/commands/init.js +11 -0
- package/lib/commands/login.js +12 -7
- package/lib/crashlytics/addNote.js +27 -0
- package/lib/crashlytics/deleteNote.js +23 -0
- package/lib/crashlytics/getIssueDetails.js +5 -20
- package/lib/crashlytics/getSampleCrash.js +6 -20
- package/lib/crashlytics/listNotes.js +29 -0
- package/lib/crashlytics/listTopDevices.js +33 -0
- package/lib/crashlytics/listTopIssues.js +8 -23
- package/lib/crashlytics/listTopOperatingSystems.js +32 -0
- package/lib/crashlytics/listTopVersions.js +32 -0
- package/lib/crashlytics/updateIssue.js +35 -0
- package/lib/crashlytics/utils.js +38 -0
- package/lib/dataconnect/appFinder.js +103 -0
- package/lib/dataconnect/load.js +105 -6
- package/lib/deploy/dataconnect/prepare.js +1 -3
- package/lib/emulator/controller.js +2 -2
- package/lib/emulator/downloadableEmulatorInfo.json +17 -17
- package/lib/experiments.js +5 -0
- package/lib/init/features/dataconnect/create_app.js +48 -0
- package/lib/init/features/dataconnect/index.js +19 -45
- package/lib/init/features/dataconnect/sdk.js +218 -161
- package/lib/init/features/index.js +3 -3
- package/lib/init/index.js +5 -2
- package/lib/management/apps.js +3 -3
- package/lib/mcp/prompts/core/deploy.js +51 -8
- package/lib/mcp/prompts/crashlytics/common.js +10 -0
- package/lib/mcp/prompts/crashlytics/fix_issue.js +89 -0
- package/lib/mcp/prompts/crashlytics/index.js +6 -0
- package/lib/mcp/prompts/crashlytics/prioritize_issues.js +79 -0
- package/lib/mcp/prompts/dataconnect/index.js +9 -0
- package/lib/mcp/prompts/dataconnect/schema.js +68 -0
- package/lib/mcp/prompts/index.js +5 -3
- package/lib/mcp/tools/core/init.js +16 -3
- package/lib/mcp/tools/crashlytics/add_note.js +32 -0
- package/lib/mcp/tools/crashlytics/constants.js +11 -0
- package/lib/mcp/tools/crashlytics/delete_note.js +35 -0
- package/lib/mcp/tools/crashlytics/get_issue_details.js +2 -4
- package/lib/mcp/tools/crashlytics/get_sample_crash.js +4 -4
- package/lib/mcp/tools/crashlytics/index.js +16 -2
- package/lib/mcp/tools/crashlytics/list_notes.js +37 -0
- package/lib/mcp/tools/crashlytics/list_top_devices.js +33 -0
- package/lib/mcp/tools/crashlytics/list_top_issues.js +5 -7
- package/lib/mcp/tools/crashlytics/list_top_operating_systems.js +33 -0
- package/lib/mcp/tools/crashlytics/list_top_versions.js +33 -0
- package/lib/mcp/tools/crashlytics/update_issue.js +37 -0
- package/lib/mcp/tools/dataconnect/compile.js +43 -0
- package/lib/mcp/tools/dataconnect/execute_graphql.js +4 -4
- package/lib/mcp/tools/dataconnect/execute_graphql_read.js +4 -4
- package/lib/mcp/tools/dataconnect/execute_mutation.js +4 -4
- package/lib/mcp/tools/dataconnect/execute_query.js +4 -4
- package/lib/mcp/tools/dataconnect/generate_operation.js +2 -2
- package/lib/mcp/tools/dataconnect/get_connector.js +3 -3
- package/lib/mcp/tools/dataconnect/get_schema.js +3 -3
- package/lib/mcp/tools/dataconnect/index.js +2 -0
- package/lib/mcp/tools/firestore/list_collections.js +0 -3
- package/lib/mcp/tools/index.js +3 -0
- package/lib/mcp/util/dataconnect/compile.js +18 -0
- package/lib/mcp/util/dataconnect/content.js +655 -0
- package/lib/utils.js +11 -1
- package/package.json +1 -1
- package/templates/init/dataconnect/connector.yaml +0 -16
- package/templates/init/dataconnect/dataconnect.yaml +2 -1
- package/templates/init/dataconnect/mutations.gql +29 -29
- package/templates/init/dataconnect/queries.gql +73 -73
- package/templates/init/dataconnect/schema.gql +48 -48
- package/lib/dataconnect/fileUtils.js +0 -168
- /package/lib/mcp/{tools → util}/dataconnect/converter.js +0 -0
- /package/lib/mcp/{tools → util}/dataconnect/emulator.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.newUniqueId = exports.toDNSCompatibleId = exports.actuate = exports.askQuestions = void 0;
|
|
4
4
|
const path_1 = require("path");
|
|
5
5
|
const clc = require("colorette");
|
|
6
6
|
const fs = require("fs-extra");
|
|
@@ -15,9 +15,9 @@ const names_1 = require("../../../dataconnect/names");
|
|
|
15
15
|
const logger_1 = require("../../../logger");
|
|
16
16
|
const templates_1 = require("../../../templates");
|
|
17
17
|
const utils_1 = require("../../../utils");
|
|
18
|
+
Object.defineProperty(exports, "newUniqueId", { enumerable: true, get: function () { return utils_1.newUniqueId; } });
|
|
18
19
|
const cloudbilling_1 = require("../../../gcp/cloudbilling");
|
|
19
20
|
const sdk = require("./sdk");
|
|
20
|
-
const fileUtils_1 = require("../../../dataconnect/fileUtils");
|
|
21
21
|
const fdcExperience_1 = require("../../../gemini/fdcExperience");
|
|
22
22
|
const configstore_1 = require("../../../configstore");
|
|
23
23
|
const track_1 = require("../../../track");
|
|
@@ -64,7 +64,7 @@ async function askQuestions(setup) {
|
|
|
64
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
65
|
}
|
|
66
66
|
info.appDescription = await (0, prompt_1.input)({
|
|
67
|
-
message: `Describe your app to automatically generate a schema [Enter to skip]:`,
|
|
67
|
+
message: `Describe your app to automatically generate a schema with Gemini [Enter to skip]:`,
|
|
68
68
|
});
|
|
69
69
|
if (info.appDescription) {
|
|
70
70
|
configstore_1.configstore.set("gemini", true);
|
|
@@ -77,6 +77,7 @@ async function askQuestions(setup) {
|
|
|
77
77
|
}
|
|
78
78
|
setup.featureInfo = setup.featureInfo || {};
|
|
79
79
|
setup.featureInfo.dataconnect = info;
|
|
80
|
+
await sdk.askQuestions(setup);
|
|
80
81
|
}
|
|
81
82
|
exports.askQuestions = askQuestions;
|
|
82
83
|
async function actuate(setup, config, options) {
|
|
@@ -94,6 +95,7 @@ async function actuate(setup, config, options) {
|
|
|
94
95
|
info.cloudSqlDatabase = info.cloudSqlDatabase || `fdcdb`;
|
|
95
96
|
try {
|
|
96
97
|
await actuateWithInfo(setup, config, info, options);
|
|
98
|
+
await sdk.actuate(setup, config);
|
|
97
99
|
}
|
|
98
100
|
finally {
|
|
99
101
|
void (0, track_1.trackGA4)("dataconnect_init", {
|
|
@@ -101,6 +103,15 @@ async function actuate(setup, config, options) {
|
|
|
101
103
|
flow: info.analyticsFlow,
|
|
102
104
|
});
|
|
103
105
|
}
|
|
106
|
+
if (info.appDescription) {
|
|
107
|
+
setup.instructions.push(`You can visualize the Data Connect Schema in Firebase Console:
|
|
108
|
+
|
|
109
|
+
https://console.firebase.google.com/project/${setup.projectId}/dataconnect/locations/${info.locationId}/services/${info.serviceId}/schema`);
|
|
110
|
+
}
|
|
111
|
+
if (!setup.isBillingEnabled) {
|
|
112
|
+
setup.instructions.push((0, freeTrial_1.upgradeInstructions)(setup.projectId || "your-firebase-project"));
|
|
113
|
+
}
|
|
114
|
+
setup.instructions.push(`Install the Data Connect VS Code Extensions. You can explore Data Connect Query on local pgLite and Cloud SQL Postgres Instance.`);
|
|
104
115
|
}
|
|
105
116
|
exports.actuate = actuate;
|
|
106
117
|
async function actuateWithInfo(setup, config, info, options) {
|
|
@@ -220,34 +231,6 @@ function schemasDeploySequence(projectId, info, schemaFiles, linkToCloudSql) {
|
|
|
220
231
|
},
|
|
221
232
|
];
|
|
222
233
|
}
|
|
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 = [];
|
|
230
|
-
const cwdPlatformGuess = await (0, fileUtils_1.getPlatformFromFolder)(process.cwd());
|
|
231
|
-
if (cwdPlatformGuess !== types_1.Platform.NONE || (0, utils_1.envOverride)("FDC_CONNECTOR", "")) {
|
|
232
|
-
await sdk.doSetup(setup, config, options);
|
|
233
|
-
}
|
|
234
|
-
else {
|
|
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`);
|
|
241
|
-
}
|
|
242
|
-
if (setup.projectId && !setup.isBillingEnabled) {
|
|
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");
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
exports.postSetup = postSetup;
|
|
251
234
|
async function writeFiles(config, info, serviceGql, options) {
|
|
252
235
|
const dir = config.get("dataconnect.source") || "dataconnect";
|
|
253
236
|
const subbedDataconnectYaml = subDataconnectYamlValues(Object.assign(Object.assign({}, info), { connectorDirs: serviceGql.connectors.map((c) => c.path) }));
|
|
@@ -271,7 +254,7 @@ async function writeFiles(config, info, serviceGql, options) {
|
|
|
271
254
|
async function writeConnectorFiles(config, connectorInfo, options) {
|
|
272
255
|
const subbedConnectorYaml = subConnectorYamlValues({ connectorId: connectorInfo.id });
|
|
273
256
|
const dir = config.get("dataconnect.source") || "dataconnect";
|
|
274
|
-
await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, "connector.yaml"), subbedConnectorYaml, !!options.force);
|
|
257
|
+
await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, "connector.yaml"), subbedConnectorYaml, !!options.force, true);
|
|
275
258
|
for (const f of connectorInfo.files) {
|
|
276
259
|
await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, f.path), f.content, !!options.force);
|
|
277
260
|
}
|
|
@@ -315,7 +298,7 @@ async function promptForExistingServices(setup, info) {
|
|
|
315
298
|
const choice = await chooseExistingService(existingServicesAndSchemas);
|
|
316
299
|
if (!choice) {
|
|
317
300
|
const existingServiceIds = existingServices.map((s) => s.name.split("/").pop());
|
|
318
|
-
info.serviceId = newUniqueId(defaultServiceId(), existingServiceIds);
|
|
301
|
+
info.serviceId = (0, utils_1.newUniqueId)(defaultServiceId(), existingServiceIds);
|
|
319
302
|
info.analyticsFlow += "_pick_new_service";
|
|
320
303
|
return;
|
|
321
304
|
}
|
|
@@ -346,7 +329,7 @@ async function promptForExistingServices(setup, info) {
|
|
|
346
329
|
const id = c.name.split("/").pop();
|
|
347
330
|
return {
|
|
348
331
|
id,
|
|
349
|
-
path: connectors.length === 1 ? "./
|
|
332
|
+
path: connectors.length === 1 ? "./example" : `./${id}`,
|
|
350
333
|
files: c.source.files || [],
|
|
351
334
|
};
|
|
352
335
|
});
|
|
@@ -419,7 +402,7 @@ async function promptForCloudSQL(setup, info) {
|
|
|
419
402
|
info.analyticsFlow += "_pick_new_csql";
|
|
420
403
|
info.cloudSqlInstanceId = await (0, prompt_1.input)({
|
|
421
404
|
message: `What ID would you like to use for your new CloudSQL instance?`,
|
|
422
|
-
default: newUniqueId(`${defaultServiceId().toLowerCase()}-fdc`, instances.map((i) => i.name)),
|
|
405
|
+
default: (0, utils_1.newUniqueId)(`${defaultServiceId().toLowerCase()}-fdc`, instances.map((i) => i.name)),
|
|
423
406
|
});
|
|
424
407
|
}
|
|
425
408
|
}
|
|
@@ -436,7 +419,7 @@ async function promptForCloudSQL(setup, info) {
|
|
|
436
419
|
try {
|
|
437
420
|
const dbs = await cloudsql.listDatabases(setup.projectId, info.cloudSqlInstanceId);
|
|
438
421
|
const existing = dbs.map((d) => d.name);
|
|
439
|
-
info.cloudSqlDatabase = newUniqueId("fdcdb", existing);
|
|
422
|
+
info.cloudSqlDatabase = (0, utils_1.newUniqueId)("fdcdb", existing);
|
|
440
423
|
}
|
|
441
424
|
catch (err) {
|
|
442
425
|
logger_1.logger.debug(`[dataconnect] Cannot list databases during init: ${err}`);
|
|
@@ -464,15 +447,6 @@ async function locationChoices(setup) {
|
|
|
464
447
|
];
|
|
465
448
|
}
|
|
466
449
|
}
|
|
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
450
|
function defaultServiceId() {
|
|
477
451
|
return toDNSCompatibleId((0, path_1.basename)(process.cwd()));
|
|
478
452
|
}
|
|
@@ -1,116 +1,187 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.addSdkGenerateToConnectorYaml = exports.actuate = exports.askQuestions = exports.FDC_SDK_PLATFORM_ENV = exports.FDC_SDK_FRAMEWORKS_ENV = exports.FDC_APP_FOLDER = void 0;
|
|
4
4
|
const yaml = require("yaml");
|
|
5
5
|
const clc = require("colorette");
|
|
6
6
|
const path = require("path");
|
|
7
|
-
const
|
|
7
|
+
const cwd = process.cwd();
|
|
8
8
|
const prompt_1 = require("../../../prompt");
|
|
9
|
-
const
|
|
9
|
+
const appFinder_1 = require("../../../dataconnect/appFinder");
|
|
10
10
|
const load_1 = require("../../../dataconnect/load");
|
|
11
11
|
const types_1 = require("../../../dataconnect/types");
|
|
12
|
-
const dataconnectEmulator_1 = require("../../../emulator/dataconnectEmulator");
|
|
13
12
|
const error_1 = require("../../../error");
|
|
14
13
|
const lodash_1 = require("lodash");
|
|
15
14
|
const utils_1 = require("../../../utils");
|
|
15
|
+
const dataconnectEmulator_1 = require("../../../emulator/dataconnectEmulator");
|
|
16
16
|
const auth_1 = require("../../../auth");
|
|
17
|
+
const create_app_1 = require("./create_app");
|
|
18
|
+
const track_1 = require("../../../track");
|
|
19
|
+
const fsutils_1 = require("../../../fsutils");
|
|
17
20
|
exports.FDC_APP_FOLDER = "FDC_APP_FOLDER";
|
|
18
21
|
exports.FDC_SDK_FRAMEWORKS_ENV = "FDC_SDK_FRAMEWORKS";
|
|
19
22
|
exports.FDC_SDK_PLATFORM_ENV = "FDC_SDK_PLATFORM";
|
|
20
|
-
async function
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
async function askQuestions(setup) {
|
|
24
|
+
const info = {
|
|
25
|
+
apps: [],
|
|
26
|
+
};
|
|
27
|
+
info.apps = await chooseApp();
|
|
28
|
+
if (!info.apps.length) {
|
|
29
|
+
const existingFilesAndDirs = (0, fsutils_1.listFiles)(cwd);
|
|
30
|
+
const webAppId = (0, utils_1.newUniqueId)("web-app", existingFilesAndDirs);
|
|
31
|
+
const choice = await (0, prompt_1.select)({
|
|
32
|
+
message: `Do you want to create an app template?`,
|
|
33
|
+
choices: [
|
|
34
|
+
{ name: "React", value: "react" },
|
|
35
|
+
{ name: "Next.JS", value: "next" },
|
|
36
|
+
{ name: "no", value: "no" },
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
switch (choice) {
|
|
40
|
+
case "react":
|
|
41
|
+
await (0, create_app_1.createReactApp)(webAppId);
|
|
42
|
+
break;
|
|
43
|
+
case "next":
|
|
44
|
+
await (0, create_app_1.createNextApp)(webAppId);
|
|
45
|
+
break;
|
|
46
|
+
case "no":
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
setup.featureInfo = setup.featureInfo || {};
|
|
51
|
+
setup.featureInfo.dataconnectSdk = info;
|
|
24
52
|
}
|
|
25
|
-
exports.
|
|
26
|
-
async function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
53
|
+
exports.askQuestions = askQuestions;
|
|
54
|
+
async function chooseApp() {
|
|
55
|
+
let apps = await (0, appFinder_1.detectApps)(cwd);
|
|
56
|
+
if (apps.length) {
|
|
57
|
+
(0, utils_1.logLabeledSuccess)("dataconnect", `Detected existing apps ${apps.map((a) => (0, appFinder_1.appDescription)(a)).join(", ")}`);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
(0, utils_1.logLabeledWarning)("dataconnect", "No app exists in the current directory.");
|
|
61
|
+
}
|
|
62
|
+
const envAppFolder = (0, utils_1.envOverride)(exports.FDC_APP_FOLDER, "");
|
|
63
|
+
const envPlatform = (0, utils_1.envOverride)(exports.FDC_SDK_PLATFORM_ENV, types_1.Platform.NONE);
|
|
64
|
+
const envFrameworks = (0, utils_1.envOverride)(exports.FDC_SDK_FRAMEWORKS_ENV, "")
|
|
65
|
+
.split(",")
|
|
66
|
+
.map((f) => f);
|
|
67
|
+
if (envAppFolder && envPlatform !== types_1.Platform.NONE) {
|
|
68
|
+
const envAppRelDir = path.relative(cwd, path.resolve(cwd, envAppFolder));
|
|
69
|
+
const matchedApps = apps.filter((app) => app.directory === envAppRelDir && (!app.platform || app.platform === envPlatform));
|
|
70
|
+
if (matchedApps.length) {
|
|
71
|
+
for (const a of matchedApps) {
|
|
72
|
+
a.frameworks = [...(a.frameworks || []), ...envFrameworks];
|
|
73
|
+
}
|
|
74
|
+
return matchedApps;
|
|
75
|
+
}
|
|
76
|
+
return [
|
|
77
|
+
{
|
|
78
|
+
platform: envPlatform,
|
|
79
|
+
directory: envAppRelDir,
|
|
80
|
+
frameworks: envFrameworks,
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
if (apps.length >= 2) {
|
|
85
|
+
const choices = apps.map((a) => {
|
|
33
86
|
return {
|
|
34
|
-
name:
|
|
35
|
-
value:
|
|
87
|
+
name: (0, appFinder_1.appDescription)(a),
|
|
88
|
+
value: a,
|
|
89
|
+
checked: a.directory === ".",
|
|
36
90
|
};
|
|
37
91
|
});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
throw new error_1.FirebaseError(`Your config has no connectors to set up SDKs for. Run ${clc.bold("firebase init dataconnect")} to set up a service and connectors.`);
|
|
42
|
-
}
|
|
43
|
-
let appDir = process.env[exports.FDC_APP_FOLDER] || process.cwd();
|
|
44
|
-
let targetPlatform = (0, utils_1.envOverride)(exports.FDC_SDK_PLATFORM_ENV, (await (0, fileUtils_1.getPlatformFromFolder)(appDir)) || types_1.Platform.NONE);
|
|
45
|
-
if (options.nonInteractive && targetPlatform === types_1.Platform.NONE) {
|
|
46
|
-
throw new error_1.FirebaseError(`In non-interactive mode, the target platform and app directory must be specified using environment variables if they cannot be automatically detected.
|
|
47
|
-
Please set the ${exports.FDC_SDK_PLATFORM_ENV} and ${exports.FDC_APP_FOLDER} environment variables.
|
|
48
|
-
For example:
|
|
49
|
-
${clc.bold(`${exports.FDC_SDK_PLATFORM_ENV}=WEB ${exports.FDC_APP_FOLDER}=app-dir ${exports.FDC_SDK_FRAMEWORKS_ENV}=react firebase init dataconnect:sdk --non-interactive`)}`);
|
|
50
|
-
}
|
|
51
|
-
if (targetPlatform === types_1.Platform.NONE && !((_a = process.env[exports.FDC_APP_FOLDER]) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
52
|
-
appDir = await (0, utils_1.promptForDirectory)({
|
|
53
|
-
config,
|
|
54
|
-
message: "Where is your app directory? Leave blank to set up a generated SDK in your current directory.",
|
|
92
|
+
const pickedApps = await (0, prompt_1.checkbox)({
|
|
93
|
+
message: "Which apps do you want to set up Data Connect SDKs in?",
|
|
94
|
+
choices,
|
|
55
95
|
});
|
|
56
|
-
|
|
96
|
+
if (!pickedApps.length) {
|
|
97
|
+
throw new error_1.FirebaseError("Command Aborted. Please choose at least one app.");
|
|
98
|
+
}
|
|
99
|
+
apps = pickedApps;
|
|
100
|
+
}
|
|
101
|
+
return apps;
|
|
102
|
+
}
|
|
103
|
+
async function actuate(setup, config) {
|
|
104
|
+
var _a, _b;
|
|
105
|
+
const fdcInfo = (_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.dataconnect;
|
|
106
|
+
const sdkInfo = (_b = setup.featureInfo) === null || _b === void 0 ? void 0 : _b.dataconnectSdk;
|
|
107
|
+
if (!sdkInfo) {
|
|
108
|
+
throw new Error("Data Connect SDK feature RequiredInfo is not provided");
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
await actuateWithInfo(setup, config, sdkInfo);
|
|
57
112
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
113
|
+
finally {
|
|
114
|
+
let flow = "no_app";
|
|
115
|
+
if (sdkInfo.apps.length) {
|
|
116
|
+
const platforms = sdkInfo.apps.map((a) => a.platform.toLowerCase()).sort();
|
|
117
|
+
flow = `${platforms.join("_")}_app`;
|
|
118
|
+
}
|
|
119
|
+
if (fdcInfo) {
|
|
120
|
+
fdcInfo.analyticsFlow += `_${flow}`;
|
|
61
121
|
}
|
|
62
122
|
else {
|
|
63
|
-
(0,
|
|
123
|
+
void (0, track_1.trackGA4)("dataconnect_init", {
|
|
124
|
+
project_status: setup.projectId ? (setup.isBillingEnabled ? "blaze" : "spark") : "missing",
|
|
125
|
+
flow: `cli_sdk_${flow}`,
|
|
126
|
+
});
|
|
64
127
|
}
|
|
65
|
-
const platforms = [
|
|
66
|
-
{ name: "iOS (Swift)", value: types_1.Platform.IOS },
|
|
67
|
-
{ name: "Web (JavaScript)", value: types_1.Platform.WEB },
|
|
68
|
-
{ name: "Android (Kotlin)", value: types_1.Platform.ANDROID },
|
|
69
|
-
{ name: "Flutter (Dart)", value: types_1.Platform.FLUTTER },
|
|
70
|
-
];
|
|
71
|
-
targetPlatform = await (0, prompt_1.select)({
|
|
72
|
-
message: "Which platform do you want to set up a generated SDK for?",
|
|
73
|
-
choices: platforms,
|
|
74
|
-
});
|
|
75
128
|
}
|
|
76
|
-
|
|
77
|
-
|
|
129
|
+
}
|
|
130
|
+
exports.actuate = actuate;
|
|
131
|
+
async function actuateWithInfo(setup, config, info) {
|
|
132
|
+
if (!info.apps.length) {
|
|
133
|
+
info.apps = await (0, appFinder_1.detectApps)(cwd);
|
|
134
|
+
if (!info.apps.length) {
|
|
135
|
+
(0, utils_1.logLabeledBullet)("dataconnect", "No apps to setup Data Connect Generated SDKs");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
78
138
|
}
|
|
79
|
-
const
|
|
139
|
+
const apps = info.apps;
|
|
140
|
+
const connectorInfo = await chooseExistingConnector(setup, config);
|
|
80
141
|
const connectorYaml = JSON.parse(JSON.stringify(connectorInfo.connectorYaml));
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (unusedFrameworks.length > 0) {
|
|
85
|
-
let additionalFrameworks = [];
|
|
86
|
-
if (options.nonInteractive) {
|
|
87
|
-
additionalFrameworks = (0, utils_1.envOverride)(exports.FDC_SDK_FRAMEWORKS_ENV, "")
|
|
88
|
-
.split(",")
|
|
89
|
-
.filter((f) => f);
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
additionalFrameworks = await (0, prompt_1.checkbox)({
|
|
93
|
-
message: "Which frameworks would you like to generate SDKs for in addition to the TypeScript SDK? Press Enter to skip.\n",
|
|
94
|
-
choices: fileUtils_1.SUPPORTED_FRAMEWORKS.map((frameworkStr) => {
|
|
95
|
-
var _a, _b;
|
|
96
|
-
return ({
|
|
97
|
-
value: frameworkStr,
|
|
98
|
-
checked: (_b = (_a = newConnectorYaml === null || newConnectorYaml === void 0 ? void 0 : newConnectorYaml.generate) === null || _a === void 0 ? void 0 : _a.javascriptSdk) === null || _b === void 0 ? void 0 : _b[frameworkStr],
|
|
99
|
-
});
|
|
100
|
-
}),
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
for (const framework of additionalFrameworks) {
|
|
104
|
-
newConnectorYaml.generate.javascriptSdk[framework] = true;
|
|
105
|
-
}
|
|
142
|
+
for (const app of apps) {
|
|
143
|
+
if (!(0, fsutils_1.dirExistsSync)(app.directory)) {
|
|
144
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `App directory ${app.directory} does not exist`);
|
|
106
145
|
}
|
|
146
|
+
addSdkGenerateToConnectorYaml(connectorInfo, connectorYaml, app);
|
|
147
|
+
}
|
|
148
|
+
const connectorYamlContents = yaml.stringify(connectorYaml);
|
|
149
|
+
connectorInfo.connectorYaml = connectorYaml;
|
|
150
|
+
const connectorYamlPath = `${connectorInfo.directory}/connector.yaml`;
|
|
151
|
+
config.writeProjectFile(path.relative(config.projectDir, connectorYamlPath), connectorYamlContents);
|
|
152
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `Installing the generated SDKs ...`);
|
|
153
|
+
const account = (0, auth_1.getGlobalDefaultAccount)();
|
|
154
|
+
await dataconnectEmulator_1.DataConnectEmulator.generate({
|
|
155
|
+
configDir: connectorInfo.directory,
|
|
156
|
+
connectorId: connectorInfo.connectorYaml.connectorId,
|
|
157
|
+
account,
|
|
158
|
+
});
|
|
159
|
+
(0, utils_1.logLabeledSuccess)("dataconnect", `Installed generated SDKs for ${clc.bold(apps.map((a) => (0, appFinder_1.appDescription)(a)).join(", "))}`);
|
|
160
|
+
if (apps.some((a) => a.platform === types_1.Platform.IOS)) {
|
|
161
|
+
(0, utils_1.logBullet)(clc.bold("Please follow the instructions here to add your generated sdk to your XCode project:\n\thttps://firebase.google.com/docs/data-connect/ios-sdk#set-client"));
|
|
162
|
+
}
|
|
163
|
+
if (apps.some((a) => { var _a; return (_a = a.frameworks) === null || _a === void 0 ? void 0 : _a.includes("react"); })) {
|
|
164
|
+
(0, utils_1.logBullet)("Visit https://firebase.google.com/docs/data-connect/web-sdk#react for more information on how to set up React Generated SDKs for Firebase Data Connect");
|
|
165
|
+
}
|
|
166
|
+
if (apps.some((a) => { var _a; return (_a = a.frameworks) === null || _a === void 0 ? void 0 : _a.includes("angular"); })) {
|
|
167
|
+
(0, utils_1.logBullet)("Run `ng add @angular/fire` to install angular sdk dependencies.\nVisit https://github.com/invertase/tanstack-query-firebase/tree/main/packages/angular for more information on how to set up Angular Generated SDKs for Firebase Data Connect");
|
|
107
168
|
}
|
|
108
|
-
const connectorYamlContents = yaml.stringify(newConnectorYaml);
|
|
109
|
-
connectorInfo.connectorYaml = newConnectorYaml;
|
|
110
|
-
const displayIOSWarning = targetPlatform === types_1.Platform.IOS;
|
|
111
|
-
return { connectorYamlContents, connectorInfo, displayIOSWarning };
|
|
112
169
|
}
|
|
113
|
-
async function chooseExistingConnector(
|
|
170
|
+
async function chooseExistingConnector(setup, config) {
|
|
171
|
+
const serviceInfos = await (0, load_1.loadAll)(setup.projectId || "", config);
|
|
172
|
+
const choices = serviceInfos
|
|
173
|
+
.map((si) => {
|
|
174
|
+
return si.connectorInfo.map((ci) => {
|
|
175
|
+
return {
|
|
176
|
+
name: `${si.dataConnectYaml.location}/${si.dataConnectYaml.serviceId}/${ci.connectorYaml.connectorId}`,
|
|
177
|
+
value: ci,
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
})
|
|
181
|
+
.flat();
|
|
182
|
+
if (!choices.length) {
|
|
183
|
+
throw new error_1.FirebaseError(`No Firebase Data Connect workspace found. Run ${clc.bold("firebase init dataconnect")} to set up a service and connector.`);
|
|
184
|
+
}
|
|
114
185
|
if (choices.length === 1) {
|
|
115
186
|
return choices[0].value;
|
|
116
187
|
}
|
|
@@ -123,93 +194,79 @@ async function chooseExistingConnector(choices) {
|
|
|
123
194
|
}
|
|
124
195
|
(0, utils_1.logWarning)(`Unable to pick up an existing connector based on FDC_CONNECTOR=${connectorEnvVar}.`);
|
|
125
196
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
choices: choices,
|
|
129
|
-
});
|
|
197
|
+
(0, utils_1.logWarning)(`Pick up the first connector ${clc.bold(connectorEnvVar)}. Use FDC_CONNECTOR to override it`);
|
|
198
|
+
return choices[0].value;
|
|
130
199
|
}
|
|
131
|
-
|
|
200
|
+
function addSdkGenerateToConnectorYaml(connectorInfo, connectorYaml, app) {
|
|
201
|
+
const connectorDir = connectorInfo.directory;
|
|
202
|
+
const appDir = app.directory;
|
|
132
203
|
if (!connectorYaml.generate) {
|
|
133
204
|
connectorYaml.generate = {};
|
|
134
205
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
206
|
+
const generate = connectorYaml.generate;
|
|
207
|
+
switch (app.platform) {
|
|
208
|
+
case types_1.Platform.WEB: {
|
|
209
|
+
const javascriptSdk = {
|
|
210
|
+
outputDir: path.relative(connectorDir, path.join(appDir, `src/dataconnect-generated`)),
|
|
211
|
+
package: `@dataconnect/generated`,
|
|
212
|
+
packageJsonDir: path.relative(connectorDir, appDir),
|
|
213
|
+
react: false,
|
|
214
|
+
angular: false,
|
|
215
|
+
};
|
|
216
|
+
for (const f of app.frameworks || []) {
|
|
217
|
+
javascriptSdk[f] = true;
|
|
218
|
+
}
|
|
219
|
+
if (!(0, lodash_1.isArray)(generate === null || generate === void 0 ? void 0 : generate.javascriptSdk)) {
|
|
220
|
+
generate.javascriptSdk = generate.javascriptSdk ? [generate.javascriptSdk] : [];
|
|
221
|
+
}
|
|
222
|
+
if (!generate.javascriptSdk.some((s) => s.outputDir === javascriptSdk.outputDir)) {
|
|
223
|
+
generate.javascriptSdk.push(javascriptSdk);
|
|
224
|
+
}
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
case types_1.Platform.FLUTTER: {
|
|
228
|
+
const dartSdk = {
|
|
229
|
+
outputDir: path.relative(connectorDir, path.join(appDir, `lib/dataconnect_generated`)),
|
|
230
|
+
package: "dataconnect_generated",
|
|
231
|
+
};
|
|
232
|
+
if (!(0, lodash_1.isArray)(generate === null || generate === void 0 ? void 0 : generate.dartSdk)) {
|
|
233
|
+
generate.dartSdk = generate.dartSdk ? [generate.dartSdk] : [];
|
|
234
|
+
}
|
|
235
|
+
if (!generate.dartSdk.some((s) => s.outputDir === dartSdk.outputDir)) {
|
|
236
|
+
generate.dartSdk.push(dartSdk);
|
|
156
237
|
}
|
|
238
|
+
break;
|
|
157
239
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
};
|
|
166
|
-
connectorYaml.generate.dartSdk = dartSdk;
|
|
167
|
-
}
|
|
168
|
-
if (targetPlatform === types_1.Platform.ANDROID) {
|
|
169
|
-
const kotlinSdk = {
|
|
170
|
-
outputDir: path.relative(connectorDir, path.join(appDir, `dataconnect-generated/kotlin`)),
|
|
171
|
-
package: `com.google.firebase.dataconnect.generated`,
|
|
172
|
-
};
|
|
173
|
-
for (const candidateSubdir of ["app/src/main/java", "app/src/main/kotlin"]) {
|
|
174
|
-
const candidateDir = path.join(appDir, candidateSubdir);
|
|
175
|
-
if ((0, fsutils_1.dirExistsSync)(candidateDir)) {
|
|
176
|
-
kotlinSdk.outputDir = path.relative(connectorDir, candidateDir);
|
|
240
|
+
case types_1.Platform.ANDROID: {
|
|
241
|
+
const kotlinSdk = {
|
|
242
|
+
outputDir: path.relative(connectorDir, path.join(appDir, `src/main/kotlin`)),
|
|
243
|
+
package: `com.google.firebase.dataconnect.generated`,
|
|
244
|
+
};
|
|
245
|
+
if (!(0, lodash_1.isArray)(generate === null || generate === void 0 ? void 0 : generate.kotlinSdk)) {
|
|
246
|
+
generate.kotlinSdk = generate.kotlinSdk ? [generate.kotlinSdk] : [];
|
|
177
247
|
}
|
|
248
|
+
if (!generate.kotlinSdk.some((s) => s.outputDir === kotlinSdk.outputDir)) {
|
|
249
|
+
generate.kotlinSdk.push(kotlinSdk);
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
178
252
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const account = (0, auth_1.getGlobalDefaultAccount)();
|
|
190
|
-
await dataconnectEmulator_1.DataConnectEmulator.generate({
|
|
191
|
-
configDir: sdkInfo.connectorInfo.directory,
|
|
192
|
-
connectorId: sdkInfo.connectorInfo.connectorYaml.connectorId,
|
|
193
|
-
account,
|
|
194
|
-
});
|
|
195
|
-
(0, utils_1.logBullet)(`Generated SDK code for ${sdkInfo.connectorInfo.connectorYaml.connectorId}`);
|
|
196
|
-
if (((_a = sdkInfo.connectorInfo.connectorYaml.generate) === null || _a === void 0 ? void 0 : _a.swiftSdk) && sdkInfo.displayIOSWarning) {
|
|
197
|
-
(0, utils_1.logBullet)(clc.bold("Please follow the instructions here to add your generated sdk to your XCode project:\n\thttps://firebase.google.com/docs/data-connect/ios-sdk#set-client"));
|
|
198
|
-
}
|
|
199
|
-
if ((_b = sdkInfo.connectorInfo.connectorYaml.generate) === null || _b === void 0 ? void 0 : _b.javascriptSdk) {
|
|
200
|
-
for (const framework of fileUtils_1.SUPPORTED_FRAMEWORKS) {
|
|
201
|
-
if (sdkInfo.connectorInfo.connectorYaml.generate.javascriptSdk[framework]) {
|
|
202
|
-
logInfoForFramework(framework);
|
|
253
|
+
case types_1.Platform.IOS: {
|
|
254
|
+
const swiftSdk = {
|
|
255
|
+
outputDir: path.relative(connectorDir, path.join(app.directory, `../FirebaseDataConnectGenerated`)),
|
|
256
|
+
package: "DataConnectGenerated",
|
|
257
|
+
};
|
|
258
|
+
if (!(0, lodash_1.isArray)(generate === null || generate === void 0 ? void 0 : generate.swiftSdk)) {
|
|
259
|
+
generate.swiftSdk = generate.swiftSdk ? [generate.swiftSdk] : [];
|
|
260
|
+
}
|
|
261
|
+
if (!generate.swiftSdk.some((s) => s.outputDir === swiftSdk.outputDir)) {
|
|
262
|
+
generate.swiftSdk.push(swiftSdk);
|
|
203
263
|
}
|
|
264
|
+
break;
|
|
204
265
|
}
|
|
266
|
+
default:
|
|
267
|
+
throw new error_1.FirebaseError(`Unsupported platform ${app.platform} for Data Connect SDK generation. Supported platforms are: ${Object.values(types_1.Platform)
|
|
268
|
+
.filter((p) => p !== types_1.Platform.NONE && p !== types_1.Platform.MULTIPLE)
|
|
269
|
+
.join(", ")}\n${JSON.stringify(app)}`);
|
|
205
270
|
}
|
|
206
271
|
}
|
|
207
|
-
exports.
|
|
208
|
-
function logInfoForFramework(framework) {
|
|
209
|
-
if (framework === "react") {
|
|
210
|
-
(0, utils_1.logBullet)("Visit https://firebase.google.com/docs/data-connect/web-sdk#react for more information on how to set up React Generated SDKs for Firebase Data Connect");
|
|
211
|
-
}
|
|
212
|
-
else if (framework === "angular") {
|
|
213
|
-
(0, utils_1.logBullet)("Run `npm i --save @angular/fire @tanstack-query-firebase/angular @tanstack/angular-query-experimental` to install angular sdk dependencies.\nVisit https://github.com/invertase/tanstack-query-firebase/tree/main/packages/angular for more information on how to set up Angular Generated SDKs for Firebase Data Connect");
|
|
214
|
-
}
|
|
215
|
-
}
|
|
272
|
+
exports.addSdkGenerateToConnectorYaml = addSdkGenerateToConnectorYaml;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.aitools = exports.apptestingAcutate = exports.apptestingAskQuestions = exports.genkit = exports.apphosting = exports.
|
|
3
|
+
exports.aitools = exports.apptestingAcutate = exports.apptestingAskQuestions = exports.genkit = exports.apphosting = exports.dataconnectSdkActuate = exports.dataconnectSdkAskQuestions = exports.dataconnectActuate = exports.dataconnectAskQuestions = exports.hostingGithub = exports.remoteconfig = exports.project = exports.extensions = exports.emulators = exports.storageActuate = exports.storageAskQuestions = exports.hosting = exports.functions = exports.firestoreActuate = exports.firestoreAskQuestions = exports.databaseActuate = exports.databaseAskQuestions = exports.account = void 0;
|
|
4
4
|
var account_1 = require("./account");
|
|
5
5
|
Object.defineProperty(exports, "account", { enumerable: true, get: function () { return account_1.doSetup; } });
|
|
6
6
|
var database_1 = require("./database");
|
|
@@ -29,9 +29,9 @@ Object.defineProperty(exports, "hostingGithub", { enumerable: true, get: functio
|
|
|
29
29
|
var dataconnect_1 = require("./dataconnect");
|
|
30
30
|
Object.defineProperty(exports, "dataconnectAskQuestions", { enumerable: true, get: function () { return dataconnect_1.askQuestions; } });
|
|
31
31
|
Object.defineProperty(exports, "dataconnectActuate", { enumerable: true, get: function () { return dataconnect_1.actuate; } });
|
|
32
|
-
Object.defineProperty(exports, "dataconnectPostSetup", { enumerable: true, get: function () { return dataconnect_1.postSetup; } });
|
|
33
32
|
var sdk_1 = require("./dataconnect/sdk");
|
|
34
|
-
Object.defineProperty(exports, "
|
|
33
|
+
Object.defineProperty(exports, "dataconnectSdkAskQuestions", { enumerable: true, get: function () { return sdk_1.askQuestions; } });
|
|
34
|
+
Object.defineProperty(exports, "dataconnectSdkActuate", { enumerable: true, get: function () { return sdk_1.actuate; } });
|
|
35
35
|
var apphosting_1 = require("./apphosting");
|
|
36
36
|
Object.defineProperty(exports, "apphosting", { enumerable: true, get: function () { return apphosting_1.doSetup; } });
|
|
37
37
|
var genkit_1 = require("./genkit");
|
package/lib/init/index.js
CHANGED
|
@@ -23,9 +23,12 @@ const featuresList = [
|
|
|
23
23
|
name: "dataconnect",
|
|
24
24
|
askQuestions: features.dataconnectAskQuestions,
|
|
25
25
|
actuate: features.dataconnectActuate,
|
|
26
|
-
postSetup: features.dataconnectPostSetup,
|
|
27
26
|
},
|
|
28
|
-
{
|
|
27
|
+
{
|
|
28
|
+
name: "dataconnect:sdk",
|
|
29
|
+
askQuestions: features.dataconnectSdkAskQuestions,
|
|
30
|
+
actuate: features.dataconnectSdkActuate,
|
|
31
|
+
},
|
|
29
32
|
{ name: "functions", doSetup: features.functions },
|
|
30
33
|
{ name: "hosting", doSetup: features.hosting },
|
|
31
34
|
{
|
package/lib/management/apps.js
CHANGED
|
@@ -13,7 +13,7 @@ const types_1 = require("../dataconnect/types");
|
|
|
13
13
|
const projectUtils_1 = require("../projectUtils");
|
|
14
14
|
const prompt = require("../prompt");
|
|
15
15
|
const projects_1 = require("./projects");
|
|
16
|
-
const
|
|
16
|
+
const appFinder_1 = require("../dataconnect/appFinder");
|
|
17
17
|
const utils_1 = require("../utils");
|
|
18
18
|
const TIMEOUT_MILLIS = 30000;
|
|
19
19
|
exports.APP_LIST_PAGE_SIZE = 100;
|
|
@@ -22,14 +22,14 @@ async function getDisplayName() {
|
|
|
22
22
|
return await prompt.input("What would you like to call your app?");
|
|
23
23
|
}
|
|
24
24
|
async function getPlatform(appDir, config) {
|
|
25
|
-
let targetPlatform = await (0,
|
|
25
|
+
let targetPlatform = await (0, appFinder_1.getPlatformFromFolder)(appDir);
|
|
26
26
|
if (targetPlatform === types_1.Platform.NONE) {
|
|
27
27
|
appDir = await (0, utils_1.promptForDirectory)({
|
|
28
28
|
config,
|
|
29
29
|
relativeTo: appDir,
|
|
30
30
|
message: "We couldn't determine what kind of app you're using. Where is your app directory?",
|
|
31
31
|
});
|
|
32
|
-
targetPlatform = await (0,
|
|
32
|
+
targetPlatform = await (0, appFinder_1.getPlatformFromFolder)(appDir);
|
|
33
33
|
}
|
|
34
34
|
if (targetPlatform === types_1.Platform.NONE || targetPlatform === types_1.Platform.MULTIPLE) {
|
|
35
35
|
if (targetPlatform === types_1.Platform.NONE) {
|