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
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listTopVersions = void 0;
|
|
4
|
+
const logger_1 = require("../logger");
|
|
5
|
+
const error_1 = require("../error");
|
|
6
|
+
const utils_1 = require("./utils");
|
|
7
|
+
async function listTopVersions(appId, versionCount, issueId) {
|
|
8
|
+
const requestProjectNumber = (0, utils_1.parseProjectNumber)(appId);
|
|
9
|
+
try {
|
|
10
|
+
const queryParams = new URLSearchParams();
|
|
11
|
+
queryParams.set("page_size", `${versionCount}`);
|
|
12
|
+
if (issueId) {
|
|
13
|
+
queryParams.set("filter.issue.id", issueId);
|
|
14
|
+
}
|
|
15
|
+
logger_1.logger.debug(`[mcp][crashlytics] listTopVersions called with appId: ${appId}, versionCount: ${versionCount}, issueId: ${issueId}`);
|
|
16
|
+
const response = await utils_1.CRASHLYTICS_API_CLIENT.request({
|
|
17
|
+
method: "GET",
|
|
18
|
+
headers: {
|
|
19
|
+
"Content-Type": "application/json",
|
|
20
|
+
},
|
|
21
|
+
path: `/projects/${requestProjectNumber}/apps/${appId}/reports/topVersions`,
|
|
22
|
+
queryParams: queryParams,
|
|
23
|
+
timeout: utils_1.TIMEOUT,
|
|
24
|
+
});
|
|
25
|
+
return response.body;
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
logger_1.logger.debug(err.message);
|
|
29
|
+
throw new error_1.FirebaseError(`Failed to fetch the top versions for the Firebase app id: ${appId}. Error: ${err}.`, { original: err });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.listTopVersions = listTopVersions;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.updateIssue = exports.IssueState = void 0;
|
|
4
|
+
const logger_1 = require("../logger");
|
|
5
|
+
const error_1 = require("../error");
|
|
6
|
+
const utils_1 = require("./utils");
|
|
7
|
+
var IssueState;
|
|
8
|
+
(function (IssueState) {
|
|
9
|
+
IssueState["OPEN"] = "OPEN";
|
|
10
|
+
IssueState["CLOSED"] = "CLOSED";
|
|
11
|
+
})(IssueState = exports.IssueState || (exports.IssueState = {}));
|
|
12
|
+
async function updateIssue(appId, issueId, state) {
|
|
13
|
+
const requestProjectNumber = (0, utils_1.parseProjectNumber)(appId);
|
|
14
|
+
try {
|
|
15
|
+
logger_1.logger.debug(`[mcp][crashlytics] updateIssue called with appId: ${appId}, issueId: ${issueId}, state: ${state}`);
|
|
16
|
+
const response = await utils_1.CRASHLYTICS_API_CLIENT.request({
|
|
17
|
+
method: "PATCH",
|
|
18
|
+
headers: {
|
|
19
|
+
"Content-Type": "application/json",
|
|
20
|
+
},
|
|
21
|
+
path: `/projects/${requestProjectNumber}/apps/${appId}/issues/${issueId}`,
|
|
22
|
+
queryParams: { updateMask: "state" },
|
|
23
|
+
body: { state },
|
|
24
|
+
timeout: utils_1.TIMEOUT,
|
|
25
|
+
});
|
|
26
|
+
return response.body;
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
logger_1.logger.debug(err.message);
|
|
30
|
+
throw new error_1.FirebaseError(`Failed to update issue ${issueId} for app ${appId}. Error: ${err}.`, {
|
|
31
|
+
original: err,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.updateIssue = updateIssue;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parsePlatform = exports.parseProjectNumber = exports.PLATFORM_PATH = exports.CRASHLYTICS_API_CLIENT = exports.TIMEOUT = void 0;
|
|
4
|
+
const error_1 = require("../error");
|
|
5
|
+
const apiv2_1 = require("../apiv2");
|
|
6
|
+
const api_1 = require("../api");
|
|
7
|
+
exports.TIMEOUT = 10000;
|
|
8
|
+
exports.CRASHLYTICS_API_CLIENT = new apiv2_1.Client({
|
|
9
|
+
urlPrefix: (0, api_1.crashlyticsApiOrigin)(),
|
|
10
|
+
apiVersion: "v1alpha",
|
|
11
|
+
});
|
|
12
|
+
var PLATFORM_PATH;
|
|
13
|
+
(function (PLATFORM_PATH) {
|
|
14
|
+
PLATFORM_PATH["ANDROID"] = "topAndroidDevices";
|
|
15
|
+
PLATFORM_PATH["IOS"] = "topAppleDevices";
|
|
16
|
+
})(PLATFORM_PATH = exports.PLATFORM_PATH || (exports.PLATFORM_PATH = {}));
|
|
17
|
+
function parseProjectNumber(appId) {
|
|
18
|
+
const appIdParts = appId.split(":");
|
|
19
|
+
if (appIdParts.length > 1) {
|
|
20
|
+
return appIdParts[1];
|
|
21
|
+
}
|
|
22
|
+
throw new error_1.FirebaseError("Unable to get the projectId from the AppId.");
|
|
23
|
+
}
|
|
24
|
+
exports.parseProjectNumber = parseProjectNumber;
|
|
25
|
+
function parsePlatform(appId) {
|
|
26
|
+
const appIdParts = appId.split(":");
|
|
27
|
+
if (appIdParts.length < 3) {
|
|
28
|
+
throw new error_1.FirebaseError("Unable to get the platform from the AppId.");
|
|
29
|
+
}
|
|
30
|
+
if (appIdParts[2] === "android") {
|
|
31
|
+
return PLATFORM_PATH.ANDROID;
|
|
32
|
+
}
|
|
33
|
+
else if (appIdParts[2] === "ios") {
|
|
34
|
+
return PLATFORM_PATH.IOS;
|
|
35
|
+
}
|
|
36
|
+
throw new error_1.FirebaseError(`Only android or ios apps are supported.`);
|
|
37
|
+
}
|
|
38
|
+
exports.parsePlatform = parsePlatform;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getFrameworksFromPackageJson = exports.WEB_FRAMEWORKS_SIGNALS = exports.WEB_FRAMEWORKS = exports.isPathInside = exports.detectApps = exports.getPlatformFromFolder = exports.appDescription = void 0;
|
|
4
|
+
const fs = require("fs-extra");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const glob_1 = require("glob");
|
|
7
|
+
const types_1 = require("./types");
|
|
8
|
+
function appDescription(a) {
|
|
9
|
+
return `${a.directory} (${a.platform.toLowerCase()})`;
|
|
10
|
+
}
|
|
11
|
+
exports.appDescription = appDescription;
|
|
12
|
+
async function getPlatformFromFolder(dirPath) {
|
|
13
|
+
const apps = await detectApps(dirPath);
|
|
14
|
+
const hasWeb = apps.some((app) => app.platform === types_1.Platform.WEB);
|
|
15
|
+
const hasAndroid = apps.some((app) => app.platform === types_1.Platform.ANDROID);
|
|
16
|
+
const hasIOS = apps.some((app) => app.platform === types_1.Platform.IOS);
|
|
17
|
+
const hasDart = apps.some((app) => app.platform === types_1.Platform.FLUTTER);
|
|
18
|
+
if (!hasWeb && !hasAndroid && !hasIOS && !hasDart) {
|
|
19
|
+
return types_1.Platform.NONE;
|
|
20
|
+
}
|
|
21
|
+
else if (hasWeb && !hasAndroid && !hasIOS && !hasDart) {
|
|
22
|
+
return types_1.Platform.WEB;
|
|
23
|
+
}
|
|
24
|
+
else if (hasAndroid && !hasWeb && !hasIOS && !hasDart) {
|
|
25
|
+
return types_1.Platform.ANDROID;
|
|
26
|
+
}
|
|
27
|
+
else if (hasIOS && !hasWeb && !hasAndroid && !hasDart) {
|
|
28
|
+
return types_1.Platform.IOS;
|
|
29
|
+
}
|
|
30
|
+
else if (hasDart && !hasWeb && !hasIOS && !hasAndroid) {
|
|
31
|
+
return types_1.Platform.FLUTTER;
|
|
32
|
+
}
|
|
33
|
+
return types_1.Platform.MULTIPLE;
|
|
34
|
+
}
|
|
35
|
+
exports.getPlatformFromFolder = getPlatformFromFolder;
|
|
36
|
+
async function detectApps(dirPath) {
|
|
37
|
+
const packageJsonFiles = await detectFiles(dirPath, "package.json");
|
|
38
|
+
const pubSpecYamlFiles = await detectFiles(dirPath, "pubspec.yaml");
|
|
39
|
+
const srcMainFolders = await detectFiles(dirPath, "src/main/");
|
|
40
|
+
const xCodeProjects = await detectFiles(dirPath, "*.xcodeproj/");
|
|
41
|
+
const webApps = await Promise.all(packageJsonFiles.map((p) => packageJsonToWebApp(dirPath, p)));
|
|
42
|
+
const flutterApps = pubSpecYamlFiles.map((f) => ({
|
|
43
|
+
platform: types_1.Platform.FLUTTER,
|
|
44
|
+
directory: path.dirname(f),
|
|
45
|
+
}));
|
|
46
|
+
const androidApps = srcMainFolders
|
|
47
|
+
.map((f) => ({
|
|
48
|
+
platform: types_1.Platform.ANDROID,
|
|
49
|
+
directory: path.dirname(path.dirname(f)),
|
|
50
|
+
}))
|
|
51
|
+
.filter((a) => !flutterApps.some((f) => isPathInside(f.directory, a.directory)));
|
|
52
|
+
const iosApps = xCodeProjects
|
|
53
|
+
.map((f) => ({
|
|
54
|
+
platform: types_1.Platform.IOS,
|
|
55
|
+
directory: path.dirname(f),
|
|
56
|
+
}))
|
|
57
|
+
.filter((a) => !flutterApps.some((f) => isPathInside(f.directory, a.directory)));
|
|
58
|
+
return [...webApps, ...flutterApps, ...androidApps, ...iosApps];
|
|
59
|
+
}
|
|
60
|
+
exports.detectApps = detectApps;
|
|
61
|
+
function isPathInside(parent, child) {
|
|
62
|
+
const relativePath = path.relative(parent, child);
|
|
63
|
+
return !relativePath.startsWith(`..`);
|
|
64
|
+
}
|
|
65
|
+
exports.isPathInside = isPathInside;
|
|
66
|
+
async function packageJsonToWebApp(dirPath, packageJsonFile) {
|
|
67
|
+
const fullPath = path.join(dirPath, packageJsonFile);
|
|
68
|
+
const packageJson = JSON.parse((await fs.readFile(fullPath)).toString());
|
|
69
|
+
return {
|
|
70
|
+
platform: types_1.Platform.WEB,
|
|
71
|
+
directory: path.dirname(packageJsonFile),
|
|
72
|
+
frameworks: getFrameworksFromPackageJson(packageJson),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
exports.WEB_FRAMEWORKS = ["react", "angular"];
|
|
76
|
+
exports.WEB_FRAMEWORKS_SIGNALS = {
|
|
77
|
+
react: ["react", "next"],
|
|
78
|
+
angular: ["@angular/core"],
|
|
79
|
+
};
|
|
80
|
+
function getFrameworksFromPackageJson(packageJson) {
|
|
81
|
+
var _a, _b;
|
|
82
|
+
const devDependencies = Object.keys((_a = packageJson.devDependencies) !== null && _a !== void 0 ? _a : {});
|
|
83
|
+
const dependencies = Object.keys((_b = packageJson.dependencies) !== null && _b !== void 0 ? _b : {});
|
|
84
|
+
const allDeps = Array.from(new Set([...devDependencies, ...dependencies]));
|
|
85
|
+
return exports.WEB_FRAMEWORKS.filter((framework) => exports.WEB_FRAMEWORKS_SIGNALS[framework].find((dep) => allDeps.includes(dep)));
|
|
86
|
+
}
|
|
87
|
+
exports.getFrameworksFromPackageJson = getFrameworksFromPackageJson;
|
|
88
|
+
async function detectFiles(dirPath, filePattern) {
|
|
89
|
+
const options = {
|
|
90
|
+
cwd: dirPath,
|
|
91
|
+
ignore: [
|
|
92
|
+
"**/dataconnect*/**",
|
|
93
|
+
"**/node_modules/**",
|
|
94
|
+
"**/dist/**",
|
|
95
|
+
"**/build/**",
|
|
96
|
+
"**/out/**",
|
|
97
|
+
"**/.next/**",
|
|
98
|
+
"**/coverage/**",
|
|
99
|
+
],
|
|
100
|
+
absolute: false,
|
|
101
|
+
};
|
|
102
|
+
return (0, glob_1.glob)(`**/${filePattern}`, options);
|
|
103
|
+
}
|
package/lib/dataconnect/load.js
CHANGED
|
@@ -1,19 +1,55 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.load = void 0;
|
|
3
|
+
exports.readConnectorYaml = exports.readDataConnectYaml = exports.readFirebaseJson = exports.load = exports.loadAll = exports.pickService = void 0;
|
|
4
4
|
const path = require("path");
|
|
5
|
-
const
|
|
5
|
+
const fs = require("fs-extra");
|
|
6
|
+
const clc = require("colorette");
|
|
7
|
+
const glob_1 = require("glob");
|
|
8
|
+
const error_1 = require("../error");
|
|
6
9
|
const types_1 = require("./types");
|
|
10
|
+
const utils_1 = require("../utils");
|
|
11
|
+
async function pickService(projectId, config, serviceId) {
|
|
12
|
+
const serviceInfos = await loadAll(projectId, config);
|
|
13
|
+
if (serviceInfos.length === 0) {
|
|
14
|
+
throw new error_1.FirebaseError("No Data Connect services found in firebase.json." +
|
|
15
|
+
`\nYou can run ${clc.bold("firebase init dataconnect")} to add a Data Connect service.`);
|
|
16
|
+
}
|
|
17
|
+
else if (serviceInfos.length === 1) {
|
|
18
|
+
if (serviceId && serviceId !== serviceInfos[0].dataConnectYaml.serviceId) {
|
|
19
|
+
throw new error_1.FirebaseError(`No service named ${serviceId} declared in firebase.json. Found ${serviceInfos[0].dataConnectYaml.serviceId}.` +
|
|
20
|
+
`\nYou can run ${clc.bold("firebase init dataconnect")} to add this Data Connect service.`);
|
|
21
|
+
}
|
|
22
|
+
return serviceInfos[0];
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
if (!serviceId) {
|
|
26
|
+
throw new error_1.FirebaseError("Multiple Data Connect services found in firebase.json. Please specify a service ID to use.");
|
|
27
|
+
}
|
|
28
|
+
const maybe = serviceInfos.find((i) => i.dataConnectYaml.serviceId === serviceId);
|
|
29
|
+
if (!maybe) {
|
|
30
|
+
const serviceIds = serviceInfos.map((i) => i.dataConnectYaml.serviceId);
|
|
31
|
+
throw new error_1.FirebaseError(`No service named ${serviceId} declared in firebase.json. Found ${serviceIds.join(", ")}.` +
|
|
32
|
+
`\nYou can run ${clc.bold("firebase init dataconnect")} to add this Data Connect service.`);
|
|
33
|
+
}
|
|
34
|
+
return maybe;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.pickService = pickService;
|
|
38
|
+
async function loadAll(projectId, config) {
|
|
39
|
+
const serviceCfgs = readFirebaseJson(config);
|
|
40
|
+
return await Promise.all(serviceCfgs.map((c) => load(projectId, config, c.source)));
|
|
41
|
+
}
|
|
42
|
+
exports.loadAll = loadAll;
|
|
7
43
|
async function load(projectId, config, sourceDirectory) {
|
|
8
44
|
const resolvedDir = config.path(sourceDirectory);
|
|
9
|
-
const dataConnectYaml = await
|
|
45
|
+
const dataConnectYaml = await readDataConnectYaml(resolvedDir);
|
|
10
46
|
const serviceName = `projects/${projectId}/locations/${dataConnectYaml.location}/services/${dataConnectYaml.serviceId}`;
|
|
11
47
|
const schemaDir = path.join(resolvedDir, dataConnectYaml.schema.source);
|
|
12
|
-
const schemaGQLs = await
|
|
48
|
+
const schemaGQLs = await readGQLFiles(schemaDir);
|
|
13
49
|
const connectorInfo = await Promise.all(dataConnectYaml.connectorDirs.map(async (dir) => {
|
|
14
50
|
const connectorDir = path.join(resolvedDir, dir);
|
|
15
|
-
const connectorYaml = await
|
|
16
|
-
const connectorGqls = await
|
|
51
|
+
const connectorYaml = await readConnectorYaml(connectorDir);
|
|
52
|
+
const connectorGqls = await readGQLFiles(connectorDir);
|
|
17
53
|
return {
|
|
18
54
|
directory: connectorDir,
|
|
19
55
|
connectorYaml,
|
|
@@ -42,3 +78,66 @@ async function load(projectId, config, sourceDirectory) {
|
|
|
42
78
|
};
|
|
43
79
|
}
|
|
44
80
|
exports.load = load;
|
|
81
|
+
function readFirebaseJson(config) {
|
|
82
|
+
if (!(config === null || config === void 0 ? void 0 : config.has("dataconnect"))) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
const validator = (cfg) => {
|
|
86
|
+
if (!cfg["source"]) {
|
|
87
|
+
throw new error_1.FirebaseError("Invalid firebase.json: DataConnect requires `source`");
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
source: cfg["source"],
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
const configs = config.get("dataconnect");
|
|
94
|
+
if (typeof configs === "object" && !Array.isArray(configs)) {
|
|
95
|
+
return [validator(configs)];
|
|
96
|
+
}
|
|
97
|
+
else if (Array.isArray(configs)) {
|
|
98
|
+
return configs.map(validator);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
throw new error_1.FirebaseError("Invalid firebase.json: dataconnect should be of the form { source: string }");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.readFirebaseJson = readFirebaseJson;
|
|
105
|
+
async function readDataConnectYaml(sourceDirectory) {
|
|
106
|
+
const file = await (0, utils_1.readFileFromDirectory)(sourceDirectory, "dataconnect.yaml");
|
|
107
|
+
const dataconnectYaml = await (0, utils_1.wrappedSafeLoad)(file.source);
|
|
108
|
+
return validateDataConnectYaml(dataconnectYaml);
|
|
109
|
+
}
|
|
110
|
+
exports.readDataConnectYaml = readDataConnectYaml;
|
|
111
|
+
function validateDataConnectYaml(unvalidated) {
|
|
112
|
+
if (!unvalidated["location"]) {
|
|
113
|
+
throw new error_1.FirebaseError("Missing required field 'location' in dataconnect.yaml");
|
|
114
|
+
}
|
|
115
|
+
return unvalidated;
|
|
116
|
+
}
|
|
117
|
+
async function readConnectorYaml(sourceDirectory) {
|
|
118
|
+
const file = await (0, utils_1.readFileFromDirectory)(sourceDirectory, "connector.yaml");
|
|
119
|
+
const connectorYaml = await (0, utils_1.wrappedSafeLoad)(file.source);
|
|
120
|
+
return validateConnectorYaml(connectorYaml);
|
|
121
|
+
}
|
|
122
|
+
exports.readConnectorYaml = readConnectorYaml;
|
|
123
|
+
function validateConnectorYaml(unvalidated) {
|
|
124
|
+
return unvalidated;
|
|
125
|
+
}
|
|
126
|
+
async function readGQLFiles(sourceDir) {
|
|
127
|
+
if (!fs.existsSync(sourceDir)) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
const files = await (0, glob_1.glob)("**/*.{gql,graphql}", { cwd: sourceDir, absolute: true, nodir: true });
|
|
131
|
+
return files.map((f) => toFile(sourceDir, f));
|
|
132
|
+
}
|
|
133
|
+
function toFile(sourceDir, fullPath) {
|
|
134
|
+
const relPath = path.relative(sourceDir, fullPath);
|
|
135
|
+
if (!fs.existsSync(fullPath)) {
|
|
136
|
+
throw new error_1.FirebaseError(`file ${fullPath} not found`);
|
|
137
|
+
}
|
|
138
|
+
const content = fs.readFileSync(fullPath).toString();
|
|
139
|
+
return {
|
|
140
|
+
path: relPath,
|
|
141
|
+
content,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const clc = require("colorette");
|
|
4
4
|
const load_1 = require("../../dataconnect/load");
|
|
5
|
-
const fileUtils_1 = require("../../dataconnect/fileUtils");
|
|
6
5
|
const logger_1 = require("../../logger");
|
|
7
6
|
const utils = require("../../utils");
|
|
8
7
|
const projectUtils_1 = require("../../projectUtils");
|
|
@@ -26,9 +25,8 @@ async function default_1(context, options) {
|
|
|
26
25
|
}
|
|
27
26
|
await (0, ensureApis_1.ensureApis)(projectId);
|
|
28
27
|
await (0, requireTosAcceptance_1.requireTosAcceptance)(firedata_1.DATA_CONNECT_TOS_ID)(options);
|
|
29
|
-
const serviceCfgs = (0, fileUtils_1.readFirebaseJson)(options.config);
|
|
30
28
|
const filters = (0, filters_1.getResourceFilters)(options);
|
|
31
|
-
const serviceInfos = await
|
|
29
|
+
const serviceInfos = await (0, load_1.loadAll)(projectId, options.config);
|
|
32
30
|
for (const si of serviceInfos) {
|
|
33
31
|
si.deploymentMetadata = await (0, build_1.build)(options, si.sourceDirectory, options.dryRun);
|
|
34
32
|
}
|
|
@@ -44,7 +44,7 @@ const firestoreEmulator_1 = require("./firestoreEmulator");
|
|
|
44
44
|
const hostingEmulator_1 = require("./hostingEmulator");
|
|
45
45
|
const pubsubEmulator_1 = require("./pubsubEmulator");
|
|
46
46
|
const storage_1 = require("./storage");
|
|
47
|
-
const
|
|
47
|
+
const load_1 = require("../dataconnect/load");
|
|
48
48
|
const tasksEmulator_1 = require("./tasksEmulator");
|
|
49
49
|
const apphosting_1 = require("./apphosting");
|
|
50
50
|
const webhook_1 = require("../dataconnect/webhook");
|
|
@@ -559,7 +559,7 @@ async function startAll(options, showUI = true, runningTestScript = false) {
|
|
|
559
559
|
await startEmulator(pubsubEmulator);
|
|
560
560
|
}
|
|
561
561
|
if (listenForEmulator.dataconnect) {
|
|
562
|
-
const config = (0,
|
|
562
|
+
const config = (0, load_1.readFirebaseJson)(options.config);
|
|
563
563
|
if (!config.length) {
|
|
564
564
|
throw new error_1.FirebaseError("No Data Connect service found in firebase.json");
|
|
565
565
|
}
|
|
@@ -54,28 +54,28 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dataconnect": {
|
|
56
56
|
"darwin": {
|
|
57
|
-
"version": "2.
|
|
57
|
+
"version": "2.12.0",
|
|
58
58
|
"expectedSize": 29447008,
|
|
59
|
-
"expectedChecksum": "
|
|
60
|
-
"expectedChecksumSHA256": "
|
|
61
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.
|
|
62
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.
|
|
59
|
+
"expectedChecksum": "73a09e7f54b642c0232ce89e9a4dd14a",
|
|
60
|
+
"expectedChecksumSHA256": "5464803fec776eac553e1431474f3d4fcc7cc2f4fe43a58e8c1ce9d7ede28acd",
|
|
61
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.12.0",
|
|
62
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.12.0"
|
|
63
63
|
},
|
|
64
64
|
"win32": {
|
|
65
|
-
"version": "2.
|
|
66
|
-
"expectedSize":
|
|
67
|
-
"expectedChecksum": "
|
|
68
|
-
"expectedChecksumSHA256": "
|
|
69
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.
|
|
70
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.
|
|
65
|
+
"version": "2.12.0",
|
|
66
|
+
"expectedSize": 29936128,
|
|
67
|
+
"expectedChecksum": "b4077d6d298024c9c454b8eb9ad9bf69",
|
|
68
|
+
"expectedChecksumSHA256": "37be096ab8d4d29fa902ca12df97327997b1dc06d4b5026e11aa11d65f183a71",
|
|
69
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.12.0",
|
|
70
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.12.0.exe"
|
|
71
71
|
},
|
|
72
72
|
"linux": {
|
|
73
|
-
"version": "2.
|
|
74
|
-
"expectedSize":
|
|
75
|
-
"expectedChecksum": "
|
|
76
|
-
"expectedChecksumSHA256": "
|
|
77
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.
|
|
78
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.
|
|
73
|
+
"version": "2.12.0",
|
|
74
|
+
"expectedSize": 29372600,
|
|
75
|
+
"expectedChecksum": "04fb5bc9ef194170a5ba0966247eaa79",
|
|
76
|
+
"expectedChecksumSHA256": "42397dd1b23094944aaf20044bc1ec0a5b400c79b536d15803eb4fa6b2bba025",
|
|
77
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.12.0",
|
|
78
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.12.0"
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createNextApp = exports.createReactApp = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const clc = require("colorette");
|
|
6
|
+
const utils_1 = require("../../../utils");
|
|
7
|
+
async function createReactApp(webAppId) {
|
|
8
|
+
const args = ["create", "vite@latest", webAppId, "--", "--template", "react"];
|
|
9
|
+
await executeCommand("npm", args);
|
|
10
|
+
}
|
|
11
|
+
exports.createReactApp = createReactApp;
|
|
12
|
+
async function createNextApp(webAppId) {
|
|
13
|
+
const args = [
|
|
14
|
+
"create-next-app@latest",
|
|
15
|
+
webAppId,
|
|
16
|
+
"--ts",
|
|
17
|
+
"--eslint",
|
|
18
|
+
"--tailwind",
|
|
19
|
+
"--src-dir",
|
|
20
|
+
"--app",
|
|
21
|
+
"--turbopack",
|
|
22
|
+
"--import-alias",
|
|
23
|
+
'"@/*"',
|
|
24
|
+
"--skip-install",
|
|
25
|
+
];
|
|
26
|
+
await executeCommand("npx", args);
|
|
27
|
+
}
|
|
28
|
+
exports.createNextApp = createNextApp;
|
|
29
|
+
async function executeCommand(command, args) {
|
|
30
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `Running ${clc.bold(`${command} ${args.join(" ")}`)}`);
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const childProcess = (0, child_process_1.spawn)(command, args, {
|
|
33
|
+
stdio: "inherit",
|
|
34
|
+
shell: true,
|
|
35
|
+
});
|
|
36
|
+
childProcess.on("close", (code) => {
|
|
37
|
+
if (code === 0) {
|
|
38
|
+
resolve();
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
reject(new Error(`Command failed with exit code ${code}`));
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
childProcess.on("error", (err) => {
|
|
45
|
+
reject(err);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toDNSCompatibleId = exports.postSetup = exports.actuate = exports.askQuestions = void 0;
|
|
3
|
+
exports.newUniqueId = exports.toDNSCompatibleId = exports.postSetup = 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", {
|
|
@@ -220,31 +222,27 @@ function schemasDeploySequence(projectId, info, schemaFiles, linkToCloudSql) {
|
|
|
220
222
|
},
|
|
221
223
|
];
|
|
222
224
|
}
|
|
223
|
-
async function postSetup(setup
|
|
225
|
+
async function postSetup(setup) {
|
|
224
226
|
var _a;
|
|
225
227
|
const info = (_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.dataconnect;
|
|
226
228
|
if (!info) {
|
|
227
229
|
throw new Error("Data Connect feature RequiredInfo is not provided");
|
|
228
230
|
}
|
|
229
231
|
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
232
|
if (info.appDescription) {
|
|
238
233
|
instructions.push(`You can visualize the Data Connect Schema in Firebase Console:
|
|
239
234
|
|
|
240
235
|
https://console.firebase.google.com/project/${setup.projectId}/dataconnect/locations/${info.locationId}/services/${info.serviceId}/schema`);
|
|
241
236
|
}
|
|
242
|
-
if (
|
|
243
|
-
instructions.push((0, freeTrial_1.upgradeInstructions)(setup.projectId));
|
|
237
|
+
if (!setup.isBillingEnabled) {
|
|
238
|
+
instructions.push((0, freeTrial_1.upgradeInstructions)(setup.projectId || "your-firebase-project"));
|
|
244
239
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
(
|
|
240
|
+
instructions.push(`Install the Data Connect VS Code Extensions. You can explore Data Connect Query on local pgLite and Cloud SQL Postgres Instance.`);
|
|
241
|
+
if (instructions.length) {
|
|
242
|
+
logger_1.logger.info(`\n${clc.bold("To get started with Firebase Data Connect:")}`);
|
|
243
|
+
for (const i of instructions) {
|
|
244
|
+
(0, utils_1.logBullet)(i + "\n");
|
|
245
|
+
}
|
|
248
246
|
}
|
|
249
247
|
}
|
|
250
248
|
exports.postSetup = postSetup;
|
|
@@ -271,7 +269,7 @@ async function writeFiles(config, info, serviceGql, options) {
|
|
|
271
269
|
async function writeConnectorFiles(config, connectorInfo, options) {
|
|
272
270
|
const subbedConnectorYaml = subConnectorYamlValues({ connectorId: connectorInfo.id });
|
|
273
271
|
const dir = config.get("dataconnect.source") || "dataconnect";
|
|
274
|
-
await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, "connector.yaml"), subbedConnectorYaml, !!options.force);
|
|
272
|
+
await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, "connector.yaml"), subbedConnectorYaml, !!options.force, true);
|
|
275
273
|
for (const f of connectorInfo.files) {
|
|
276
274
|
await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, f.path), f.content, !!options.force);
|
|
277
275
|
}
|
|
@@ -315,7 +313,7 @@ async function promptForExistingServices(setup, info) {
|
|
|
315
313
|
const choice = await chooseExistingService(existingServicesAndSchemas);
|
|
316
314
|
if (!choice) {
|
|
317
315
|
const existingServiceIds = existingServices.map((s) => s.name.split("/").pop());
|
|
318
|
-
info.serviceId = newUniqueId(defaultServiceId(), existingServiceIds);
|
|
316
|
+
info.serviceId = (0, utils_1.newUniqueId)(defaultServiceId(), existingServiceIds);
|
|
319
317
|
info.analyticsFlow += "_pick_new_service";
|
|
320
318
|
return;
|
|
321
319
|
}
|
|
@@ -346,7 +344,7 @@ async function promptForExistingServices(setup, info) {
|
|
|
346
344
|
const id = c.name.split("/").pop();
|
|
347
345
|
return {
|
|
348
346
|
id,
|
|
349
|
-
path: connectors.length === 1 ? "./
|
|
347
|
+
path: connectors.length === 1 ? "./example" : `./${id}`,
|
|
350
348
|
files: c.source.files || [],
|
|
351
349
|
};
|
|
352
350
|
});
|
|
@@ -419,7 +417,7 @@ async function promptForCloudSQL(setup, info) {
|
|
|
419
417
|
info.analyticsFlow += "_pick_new_csql";
|
|
420
418
|
info.cloudSqlInstanceId = await (0, prompt_1.input)({
|
|
421
419
|
message: `What ID would you like to use for your new CloudSQL instance?`,
|
|
422
|
-
default: newUniqueId(`${defaultServiceId().toLowerCase()}-fdc`, instances.map((i) => i.name)),
|
|
420
|
+
default: (0, utils_1.newUniqueId)(`${defaultServiceId().toLowerCase()}-fdc`, instances.map((i) => i.name)),
|
|
423
421
|
});
|
|
424
422
|
}
|
|
425
423
|
}
|
|
@@ -436,7 +434,7 @@ async function promptForCloudSQL(setup, info) {
|
|
|
436
434
|
try {
|
|
437
435
|
const dbs = await cloudsql.listDatabases(setup.projectId, info.cloudSqlInstanceId);
|
|
438
436
|
const existing = dbs.map((d) => d.name);
|
|
439
|
-
info.cloudSqlDatabase = newUniqueId("fdcdb", existing);
|
|
437
|
+
info.cloudSqlDatabase = (0, utils_1.newUniqueId)("fdcdb", existing);
|
|
440
438
|
}
|
|
441
439
|
catch (err) {
|
|
442
440
|
logger_1.logger.debug(`[dataconnect] Cannot list databases during init: ${err}`);
|
|
@@ -464,15 +462,6 @@ async function locationChoices(setup) {
|
|
|
464
462
|
];
|
|
465
463
|
}
|
|
466
464
|
}
|
|
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
465
|
function defaultServiceId() {
|
|
477
466
|
return toDNSCompatibleId((0, path_1.basename)(process.cwd()));
|
|
478
467
|
}
|