firebase-tools 14.14.0 → 14.15.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/lib/apphosting/backend.js +40 -15
- package/lib/auth.js +37 -2
- 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 +3 -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/init/features/dataconnect/create_app.js +48 -0
- package/lib/init/features/dataconnect/index.js +19 -30
- package/lib/init/features/dataconnect/sdk.js +218 -161
- package/lib/init/features/index.js +3 -2
- package/lib/init/index.js +5 -1
- 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/index.js +3 -2
- package/lib/mcp/tools/core/init.js +5 -1
- 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/execute_graphql.js +2 -2
- package/lib/mcp/tools/dataconnect/execute_graphql_read.js +2 -2
- package/lib/mcp/tools/dataconnect/execute_mutation.js +2 -2
- package/lib/mcp/tools/dataconnect/execute_query.js +2 -2
- package/lib/mcp/tools/dataconnect/generate_operation.js +2 -2
- package/lib/mcp/tools/dataconnect/get_connector.js +2 -2
- package/lib/mcp/tools/dataconnect/get_schema.js +2 -2
- 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
|
@@ -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.dataconnectPostSetup = 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");
|
|
@@ -31,7 +31,8 @@ Object.defineProperty(exports, "dataconnectAskQuestions", { enumerable: true, ge
|
|
|
31
31
|
Object.defineProperty(exports, "dataconnectActuate", { enumerable: true, get: function () { return dataconnect_1.actuate; } });
|
|
32
32
|
Object.defineProperty(exports, "dataconnectPostSetup", { enumerable: true, get: function () { return dataconnect_1.postSetup; } });
|
|
33
33
|
var sdk_1 = require("./dataconnect/sdk");
|
|
34
|
-
Object.defineProperty(exports, "
|
|
34
|
+
Object.defineProperty(exports, "dataconnectSdkAskQuestions", { enumerable: true, get: function () { return sdk_1.askQuestions; } });
|
|
35
|
+
Object.defineProperty(exports, "dataconnectSdkActuate", { enumerable: true, get: function () { return sdk_1.actuate; } });
|
|
35
36
|
var apphosting_1 = require("./apphosting");
|
|
36
37
|
Object.defineProperty(exports, "apphosting", { enumerable: true, get: function () { return apphosting_1.doSetup; } });
|
|
37
38
|
var genkit_1 = require("./genkit");
|
package/lib/init/index.js
CHANGED
|
@@ -25,7 +25,11 @@ const featuresList = [
|
|
|
25
25
|
actuate: features.dataconnectActuate,
|
|
26
26
|
postSetup: features.dataconnectPostSetup,
|
|
27
27
|
},
|
|
28
|
-
{
|
|
28
|
+
{
|
|
29
|
+
name: "dataconnect:sdk",
|
|
30
|
+
askQuestions: features.dataconnectSdkAskQuestions,
|
|
31
|
+
actuate: features.dataconnectSdkActuate,
|
|
32
|
+
},
|
|
29
33
|
{ name: "functions", doSetup: features.functions },
|
|
30
34
|
{ name: "hosting", doSetup: features.hosting },
|
|
31
35
|
{
|
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) {
|
|
@@ -43,14 +43,57 @@ ${prompt || "<the user didn't supply specific instructions>"}
|
|
|
43
43
|
Follow the steps below taking note of any user instructions provided above.
|
|
44
44
|
|
|
45
45
|
1. If there is no active user, prompt the user to run \`firebase login\` in an interactive terminal before continuing.
|
|
46
|
-
2.
|
|
47
|
-
3.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
2. Analyze the source code in the current working directory to determine if this is a web app. If it isn't, end this process and tell the user "The /deploy command only works with web apps."
|
|
47
|
+
3. Analyze the source code in the current working directory to determine if the app requires a server for Server-Side Rendering (SSR). This will determine whether or not to use Firebase App Hosting. Here are instructions to determine if the app needs a server:
|
|
48
|
+
Objective: Analyze the provided codebase files to determine if the web application requires a backend for Server-Side Rendering (SSR). Your final output must be a clear "Yes" or "No" followed by a brief justification.
|
|
49
|
+
Primary Analysis: package.json
|
|
50
|
+
This is the most critical step. If the package.json file is present, perform the following checks in order.
|
|
51
|
+
Parse package.json: Locate and read the contents of the package.json file.
|
|
52
|
+
Check Dependencies:
|
|
53
|
+
Examine the dependencies and devDependencies objects.
|
|
54
|
+
If any of the following packages are listed as keys, you can conclude the app uses SSR.
|
|
55
|
+
next
|
|
56
|
+
nuxt
|
|
57
|
+
@sveltejs/kit
|
|
58
|
+
@angular/ssr
|
|
59
|
+
remix
|
|
60
|
+
If a match is found, proceed directly to the Final Determination step.
|
|
61
|
+
Check Scripts: If no framework dependency was found, examine the scripts object.
|
|
62
|
+
Look for scripts (commonly start or serve) that execute a server process.
|
|
63
|
+
Examples include: "start": "next start", "start": "nuxt start", or "dev": "ng serve --ssr".
|
|
64
|
+
If such a script is found, conclude the app uses SSR and proceed to the Final Determination step.
|
|
65
|
+
Secondary Analysis: Project File Structure
|
|
66
|
+
Perform this analysis only if package.json is missing or inconclusive.
|
|
67
|
+
Scan for Framework-Specific Files and Directories: Search the codebase for the following patterns:
|
|
68
|
+
Next.js: A directory named app/ or pages/. Inside these, check for files containing the function name getServerSideProps.
|
|
69
|
+
Nuxt.js: A directory named server/.
|
|
70
|
+
SvelteKit: Any file ending with the .server.js suffix (e.g., +page.server.js, +layout.server.js).
|
|
71
|
+
Angular: A file named server.ts.
|
|
72
|
+
If any of these patterns are found, conclude the app uses SSR.
|
|
73
|
+
Final Determination
|
|
74
|
+
State Your Conclusion: Begin your response with a definitive "Yes" or "No".
|
|
75
|
+
Yes: The application requires a backend for SSR.
|
|
76
|
+
No: The application does not appear to require a backend for SSR and is likely a static or client-side rendered app.
|
|
77
|
+
Provide Justification: Follow your conclusion with a single sentence explaining the evidence.
|
|
78
|
+
Example (Yes): "Yes, the project requires SSR, as evidenced by the next dependency in package.json."
|
|
79
|
+
Example (Yes): "Yes, the project requires SSR, as evidenced by the presence of a +page.server.js file."
|
|
80
|
+
Example (No): "No, there are no dependencies or file structures that indicate the use of a server-side rendering framework."
|
|
81
|
+
4. If there is no \`firebase.json\` file, manually create one based on whether the app requires SSR:
|
|
82
|
+
4a. If the app requires SSR, configure Firebase App Hosting:
|
|
83
|
+
Create \`firebase.json\ with an "apphosting" configuration, setting backendId to the app's name in package.json: \`{"apphosting": {"backendId": "<backendId>"}}\
|
|
84
|
+
4b. If the app does NOT require SSR, configure Firebase Hosting:
|
|
85
|
+
Create \`firebase.json\ with a "hosting" configuration. Add a \`{"hosting": {"predeploy": "<build_script>"}}\` config to build before deploying.
|
|
86
|
+
5. Check if there is an active Firebase project for this environment (the \`firebase_get_environment\` tool may be helpful). If there is, proceed using that project. If there is not an active project, give the user two options: Use an existing Firebase project or Create a new one. Wait for their response before proceeding.
|
|
87
|
+
5a. If the user chooses to use an existing Firebase project, the \`firebase_list_projects\` tool may be helpful. Set the selected project as the active project (the \`firebase_update_environment\` tool may be helpful).
|
|
88
|
+
5b. If the user chooses to create a new project, use the \`firebase_create_project \` tool. Then set the new project as the active project (the \`firebase_update_environment\` tool may be helpful).
|
|
89
|
+
6. If firebase.json contains an "apphosting" configuration, check if a backend exists matching the provided backendId (the \`apphosting_list_backends\` tool may be helpful).
|
|
90
|
+
If it doesn't exist, create one by running the \`firebase apphosting:backends:create --backend <backendId> --primary-region us-central1 --root-dir .\` shell.
|
|
91
|
+
7. Only after making sure Firebase has been initialized, run the \`firebase deploy\` shell command to perform the deploy. This may take a few minutes.
|
|
92
|
+
7a. If deploying to apphosting, tell the user the deployment will take a few minutes, and they can monitor deployment progress in the Firebase console: \`https://console.firebase.google.com/project/<projectId>/apphosting\`
|
|
93
|
+
8. If the deploy has errors, attempt to fix them and ask the user clarifying questions as needed.
|
|
94
|
+
9. If the deploy needs \`--force\` to run successfully, ALWAYS prompt the user before running \`firebase deploy --force\`.
|
|
95
|
+
10. If only one specific feature is failing, use command \`firebase deploy --only <feature>\` as you debug.
|
|
96
|
+
11. If the deploy succeeds, your job is finished.
|
|
54
97
|
`.trim(),
|
|
55
98
|
},
|
|
56
99
|
},
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getAppIdInstruction = exports.ACTIVE_USER_INSTRUCTION = void 0;
|
|
4
|
+
exports.ACTIVE_USER_INSTRUCTION = "If the Firbase 'Active user' is set to <NONE>, instruct the user to run `firebase login` before continuing. You will know that the user is not logged in if we have nothing in the 'Active user' field.";
|
|
5
|
+
const getAppIdInstruction = (index) => `If there is no active app id, then do the following:
|
|
6
|
+
${index}a. If this is an Android app, read the mobilesdk_app_id value specified in the google-services.json file. If there are multiple files or multiple app ids in single file. Ask the user to disambiguate.
|
|
7
|
+
${index}b. If this is an iOS app, read the GOOGLE_APP_ID from GoogleService-Info.plist file. If there are multiple files or multiple app ids in single file. Ask the user to disambiguate.
|
|
8
|
+
${index}c. If you can't find either of the above, ask the user for the app id.
|
|
9
|
+
`;
|
|
10
|
+
exports.getAppIdInstruction = getAppIdInstruction;
|