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
|
@@ -5,7 +5,7 @@ const zod_1 = require("zod");
|
|
|
5
5
|
const tool_1 = require("../../tool");
|
|
6
6
|
const util_1 = require("../../util");
|
|
7
7
|
const dataplane = require("../../../dataconnect/dataplaneClient");
|
|
8
|
-
const
|
|
8
|
+
const load_1 = require("../../../dataconnect/load");
|
|
9
9
|
const converter_1 = require("./converter");
|
|
10
10
|
const emulator_1 = require("./emulator");
|
|
11
11
|
exports.execute_mutation = (0, tool_1.tool)({
|
|
@@ -36,7 +36,7 @@ exports.execute_mutation = (0, tool_1.tool)({
|
|
|
36
36
|
requiresAuth: true,
|
|
37
37
|
},
|
|
38
38
|
}, async ({ operationName, service_id, connector_id, variables: unparsedVariables, use_emulator }, { projectId, config, host }) => {
|
|
39
|
-
const serviceInfo = await (0,
|
|
39
|
+
const serviceInfo = await (0, load_1.pickService)(projectId, config, service_id || undefined);
|
|
40
40
|
let apiClient;
|
|
41
41
|
if (!connector_id) {
|
|
42
42
|
if (serviceInfo.connectorInfo.length === 0) {
|
|
@@ -5,7 +5,7 @@ const zod_1 = require("zod");
|
|
|
5
5
|
const tool_1 = require("../../tool");
|
|
6
6
|
const util_1 = require("../../util");
|
|
7
7
|
const dataplane = require("../../../dataconnect/dataplaneClient");
|
|
8
|
-
const
|
|
8
|
+
const load_1 = require("../../../dataconnect/load");
|
|
9
9
|
const converter_1 = require("./converter");
|
|
10
10
|
const emulator_1 = require("./emulator");
|
|
11
11
|
exports.execute_query = (0, tool_1.tool)({
|
|
@@ -36,7 +36,7 @@ exports.execute_query = (0, tool_1.tool)({
|
|
|
36
36
|
requiresAuth: true,
|
|
37
37
|
},
|
|
38
38
|
}, async ({ operationName, service_id, connector_id, variables: unparsedVariables, use_emulator }, { projectId, config, host }) => {
|
|
39
|
-
const serviceInfo = await (0,
|
|
39
|
+
const serviceInfo = await (0, load_1.pickService)(projectId, config, service_id || undefined);
|
|
40
40
|
let apiClient;
|
|
41
41
|
if (!connector_id) {
|
|
42
42
|
if (serviceInfo.connectorInfo.length === 0) {
|
|
@@ -5,7 +5,7 @@ const zod_1 = require("zod");
|
|
|
5
5
|
const tool_1 = require("../../tool");
|
|
6
6
|
const util_1 = require("../../util");
|
|
7
7
|
const fdcExperience_1 = require("../../../gemini/fdcExperience");
|
|
8
|
-
const
|
|
8
|
+
const load_1 = require("../../../dataconnect/load");
|
|
9
9
|
exports.generate_operation = (0, tool_1.tool)({
|
|
10
10
|
name: "generate_operation",
|
|
11
11
|
description: "Generates a single Firebase Data Connect query or mutation based on the currently deployed schema and the provided prompt.",
|
|
@@ -28,7 +28,7 @@ exports.generate_operation = (0, tool_1.tool)({
|
|
|
28
28
|
requiresGemini: true,
|
|
29
29
|
},
|
|
30
30
|
}, async ({ prompt, service_id }, { projectId, config }) => {
|
|
31
|
-
const serviceInfo = await (0,
|
|
31
|
+
const serviceInfo = await (0, load_1.pickService)(projectId, config, service_id || undefined);
|
|
32
32
|
const schema = await (0, fdcExperience_1.generateOperation)(prompt, serviceInfo.serviceName, projectId);
|
|
33
33
|
return (0, util_1.toContent)(schema);
|
|
34
34
|
});
|
|
@@ -5,7 +5,7 @@ const zod_1 = require("zod");
|
|
|
5
5
|
const tool_1 = require("../../tool");
|
|
6
6
|
const util_1 = require("../../util");
|
|
7
7
|
const client = require("../../../dataconnect/client");
|
|
8
|
-
const
|
|
8
|
+
const load_1 = require("../../../dataconnect/load");
|
|
9
9
|
const converter_1 = require("./converter");
|
|
10
10
|
exports.get_connectors = (0, tool_1.tool)({
|
|
11
11
|
name: "get_connectors",
|
|
@@ -25,7 +25,7 @@ exports.get_connectors = (0, tool_1.tool)({
|
|
|
25
25
|
requiresAuth: true,
|
|
26
26
|
},
|
|
27
27
|
}, async ({ service_id }, { projectId, config }) => {
|
|
28
|
-
const serviceInfo = await (0,
|
|
28
|
+
const serviceInfo = await (0, load_1.pickService)(projectId, config, service_id || undefined);
|
|
29
29
|
const connectors = await client.listConnectors(serviceInfo.serviceName, ["*"]);
|
|
30
30
|
return (0, util_1.toContent)(connectors.map(converter_1.connectorToText).join("\n\n"));
|
|
31
31
|
});
|
|
@@ -5,7 +5,7 @@ const zod_1 = require("zod");
|
|
|
5
5
|
const tool_1 = require("../../tool");
|
|
6
6
|
const util_1 = require("../../util");
|
|
7
7
|
const client = require("../../../dataconnect/client");
|
|
8
|
-
const
|
|
8
|
+
const load_1 = require("../../../dataconnect/load");
|
|
9
9
|
const converter_1 = require("./converter");
|
|
10
10
|
exports.get_schema = (0, tool_1.tool)({
|
|
11
11
|
name: "get_schema",
|
|
@@ -25,7 +25,7 @@ exports.get_schema = (0, tool_1.tool)({
|
|
|
25
25
|
requiresAuth: true,
|
|
26
26
|
},
|
|
27
27
|
}, async ({ service_id }, { projectId, config }) => {
|
|
28
|
-
const serviceInfo = await (0,
|
|
28
|
+
const serviceInfo = await (0, load_1.pickService)(projectId, config, service_id || undefined);
|
|
29
29
|
const schemas = await client.listSchemas(serviceInfo.serviceName, ["*"]);
|
|
30
30
|
return (0, util_1.toContent)(schemas === null || schemas === void 0 ? void 0 : schemas.map(converter_1.schemaToText).join("\n\n"));
|
|
31
31
|
});
|
package/lib/utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.openInBrowserPopup = exports.openInBrowser = exports.connectableHostname = exports.randomInt = exports.debounce = exports.last = exports.cloneDeep = exports.groupBy = exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.sleep = exports.promiseWithSpinner = exports.tryParse = exports.promiseProps = exports.withTimeout = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledError = exports.logLabeledWarning = exports.logWarningToStderr = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.setVSCodeEnvVars = exports.getInheritedOption = exports.consoleUrl = exports.vscodeEnvVars = exports.envOverrides = exports.IS_WINDOWS = void 0;
|
|
4
|
-
exports.deepEqual = exports.promptForDirectory = exports.updateOrCreateGitignore = exports.readSecretValue = exports.generatePassword = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = exports.getHostnameFromUrl = void 0;
|
|
4
|
+
exports.newUniqueId = exports.deepEqual = exports.promptForDirectory = exports.updateOrCreateGitignore = exports.readSecretValue = exports.generatePassword = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = exports.getHostnameFromUrl = void 0;
|
|
5
5
|
const fs = require("fs-extra");
|
|
6
6
|
const tty = require("tty");
|
|
7
7
|
const path = require("node:path");
|
|
@@ -647,3 +647,13 @@ function deepEqual(a, b) {
|
|
|
647
647
|
return true;
|
|
648
648
|
}
|
|
649
649
|
exports.deepEqual = deepEqual;
|
|
650
|
+
function newUniqueId(recommended, existingIDs) {
|
|
651
|
+
let id = recommended;
|
|
652
|
+
let i = 1;
|
|
653
|
+
while (existingIDs.includes(id)) {
|
|
654
|
+
id = `${recommended}-${i}`;
|
|
655
|
+
i++;
|
|
656
|
+
}
|
|
657
|
+
return id;
|
|
658
|
+
}
|
|
659
|
+
exports.newUniqueId = newUniqueId;
|
package/package.json
CHANGED
|
@@ -1,17 +1 @@
|
|
|
1
1
|
connectorId: __connectorId__
|
|
2
|
-
## ## Here's an example of how to add generated SDKs.
|
|
3
|
-
## ## You'll need to replace the outputDirs with ones pointing to where you want the generated code in your app.
|
|
4
|
-
# generate:
|
|
5
|
-
# javascriptSdk:
|
|
6
|
-
# outputDir: <Path where you want the generated SDK to be written to, relative to this file>
|
|
7
|
-
# package: "@firebasegen/my-connector"
|
|
8
|
-
# packageJsonDir: < Optional. Path to your Javascript app's package.json>
|
|
9
|
-
# swiftSdk:
|
|
10
|
-
# outputDir: <Path where you want the generated SDK to be written to, relative to this file>
|
|
11
|
-
# package: DefaultConnector
|
|
12
|
-
# kotlinSdk:
|
|
13
|
-
# outputDir: <Path where you want the generated SDK to be written to, relative to this file>
|
|
14
|
-
# package: connectors.default
|
|
15
|
-
# dartSdk:
|
|
16
|
-
# outputDir: <Path where you want the generated SDK to be written to, relative to this file>
|
|
17
|
-
# package: default_connector
|
|
@@ -8,5 +8,6 @@ schema:
|
|
|
8
8
|
database: __cloudSqlDatabase__
|
|
9
9
|
cloudSql:
|
|
10
10
|
instanceId: __cloudSqlInstanceId__
|
|
11
|
-
|
|
11
|
+
# schemaValidation: "STRICT" # STRICT mode makes Postgres schema match Data Connect exactly.
|
|
12
|
+
# schemaValidation: "COMPATIBLE" # COMPATIBLE mode makes Postgres schema compatible with Data Connect.
|
|
12
13
|
connectorDirs: __connectorDirs__
|
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Example mutations for a simple movie app
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
# Create a movie based on user input
|
|
4
|
+
mutation CreateMovie($title: String!, $genre: String!, $imageUrl: String!)
|
|
5
|
+
@auth(level: USER_EMAIL_VERIFIED, insecureReason: "Any email verified users can create a new movie.") {
|
|
6
|
+
movie_insert(data: { title: $title, genre: $genre, imageUrl: $imageUrl })
|
|
7
|
+
}
|
|
8
8
|
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
# Upsert (update or insert) a user's username based on their auth.uid
|
|
10
|
+
mutation UpsertUser($username: String!) @auth(level: USER) {
|
|
11
|
+
# The "auth.uid" server value ensures that users can only register their own user.
|
|
12
|
+
user_upsert(data: { id_expr: "auth.uid", username: $username })
|
|
13
|
+
}
|
|
14
14
|
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
15
|
+
# Add a review for a movie
|
|
16
|
+
mutation AddReview($movieId: UUID!, $rating: Int!, $reviewText: String!)
|
|
17
|
+
@auth(level: USER) {
|
|
18
|
+
review_upsert(
|
|
19
|
+
data: {
|
|
20
|
+
userId_expr: "auth.uid"
|
|
21
|
+
movieId: $movieId
|
|
22
|
+
rating: $rating
|
|
23
|
+
reviewText: $reviewText
|
|
24
|
+
# reviewDate defaults to today in the schema. No need to set it manually.
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
28
|
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
# Logged in user can delete their review for a movie
|
|
30
|
+
mutation DeleteReview($movieId: UUID!) @auth(level: USER) {
|
|
31
|
+
# The "auth.uid" server value ensures that users can only delete their own reviews.
|
|
32
|
+
review_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
|
|
33
|
+
}
|
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Example queries for a simple movie app.
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
# @auth() directives control who can call each operation.
|
|
4
|
+
# Anyone should be able to list all movies, so the auth level is set to PUBLIC
|
|
5
|
+
query ListMovies @auth(level: PUBLIC, insecureReason: "Anyone can list all movies.") {
|
|
6
|
+
movies {
|
|
7
|
+
id
|
|
8
|
+
title
|
|
9
|
+
imageUrl
|
|
10
|
+
genre
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
13
|
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
# List all users, only admins should be able to list all users, so we use NO_ACCESS
|
|
15
|
+
query ListUsers @auth(level: NO_ACCESS) {
|
|
16
|
+
users {
|
|
17
|
+
id
|
|
18
|
+
username
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
21
|
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
22
|
+
# Logged in users can list all their reviews and movie titles associated with the review
|
|
23
|
+
# Since the query uses the uid of the current authenticated user, we set auth level to USER
|
|
24
|
+
query ListUserReviews @auth(level: USER) {
|
|
25
|
+
user(key: { id_expr: "auth.uid" }) {
|
|
26
|
+
id
|
|
27
|
+
username
|
|
28
|
+
# <field>_on_<foreign_key_field> makes it easy to grab info from another table
|
|
29
|
+
# Here, we use it to grab all the reviews written by the user.
|
|
30
|
+
reviews: reviews_on_user {
|
|
31
|
+
rating
|
|
32
|
+
reviewDate
|
|
33
|
+
reviewText
|
|
34
|
+
movie {
|
|
35
|
+
id
|
|
36
|
+
title
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
41
|
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
42
|
+
# Get movie by id
|
|
43
|
+
query GetMovieById($id: UUID!) @auth(level: PUBLIC, insecureReason: "Anyone can get a movie by id.") {
|
|
44
|
+
movie(id: $id) {
|
|
45
|
+
id
|
|
46
|
+
title
|
|
47
|
+
imageUrl
|
|
48
|
+
genre
|
|
49
|
+
metadata: movieMetadata_on_movie {
|
|
50
|
+
rating
|
|
51
|
+
releaseYear
|
|
52
|
+
description
|
|
53
|
+
}
|
|
54
|
+
reviews: reviews_on_movie {
|
|
55
|
+
reviewText
|
|
56
|
+
reviewDate
|
|
57
|
+
rating
|
|
58
|
+
user {
|
|
59
|
+
id
|
|
60
|
+
username
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
65
|
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
# Search for movies, actors, and reviews
|
|
67
|
+
query SearchMovie($titleInput: String, $genre: String) @auth(level: PUBLIC, insecureReason: "Anyone can search for movies.") {
|
|
68
|
+
movies(
|
|
69
|
+
where: {
|
|
70
|
+
_and: [{ genre: { eq: $genre } }, { title: { contains: $titleInput } }]
|
|
71
|
+
}
|
|
72
|
+
) {
|
|
73
|
+
id
|
|
74
|
+
title
|
|
75
|
+
genre
|
|
76
|
+
imageUrl
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Example schema for simple movie review app
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
|
|
3
|
+
# User table is keyed by Firebase Auth UID.
|
|
4
|
+
type User @table {
|
|
5
|
+
# `@default(expr: "auth.uid")` sets it to Firebase Auth UID during insert and upsert.
|
|
6
|
+
id: String! @default(expr: "auth.uid")
|
|
7
|
+
username: String! @col(dataType: "varchar(50)")
|
|
8
|
+
# The `user: User!` field in the Review table generates the following one-to-many query field.
|
|
9
|
+
# reviews_on_user: [Review!]!
|
|
10
|
+
# The `Review` join table the following many-to-many query field.
|
|
11
|
+
# movies_via_Review: [Movie!]!
|
|
12
|
+
}
|
|
13
13
|
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
# Movie is keyed by a randomly generated UUID.
|
|
15
|
+
type Movie @table {
|
|
16
|
+
# If you do not pass a 'key' to `@table`, Data Connect automatically adds the following 'id' column.
|
|
17
|
+
# Feel free to uncomment and customize it.
|
|
18
|
+
# id: UUID! @default(expr: "uuidV4()")
|
|
19
|
+
title: String!
|
|
20
|
+
imageUrl: String!
|
|
21
|
+
genre: String
|
|
22
|
+
}
|
|
23
23
|
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
# MovieMetadata is a metadata attached to a Movie.
|
|
25
|
+
# Movie <-> MovieMetadata is a one-to-one relationship
|
|
26
|
+
type MovieMetadata @table {
|
|
27
|
+
# @unique ensures each Movie can only one MovieMetadata.
|
|
28
|
+
movie: Movie! @unique
|
|
29
|
+
# The movie field adds the following foreign key field. Feel free to uncomment and customize it.
|
|
30
|
+
# movieId: UUID!
|
|
31
|
+
rating: Float
|
|
32
|
+
releaseYear: Int
|
|
33
|
+
description: String
|
|
34
|
+
}
|
|
35
35
|
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
36
|
+
# Reviews is a join table between User and Movie.
|
|
37
|
+
# It has a composite primary keys `userUid` and `movieId`.
|
|
38
|
+
# A user can leave reviews for many movies. A movie can have reviews from many users.
|
|
39
|
+
# User <-> Review is a one-to-many relationship
|
|
40
|
+
# Movie <-> Review is a one-to-many relationship
|
|
41
|
+
# Movie <-> User is a many-to-many relationship
|
|
42
|
+
type Review @table(name: "Reviews", key: ["movie", "user"]) {
|
|
43
|
+
user: User!
|
|
44
|
+
# The user field adds the following foreign key field. Feel free to uncomment and customize it.
|
|
45
|
+
# userUid: String!
|
|
46
|
+
movie: Movie!
|
|
47
|
+
# The movie field adds the following foreign key field. Feel free to uncomment and customize it.
|
|
48
|
+
# movieId: UUID!
|
|
49
|
+
rating: Int
|
|
50
|
+
reviewText: String
|
|
51
|
+
reviewDate: Date! @default(expr: "request.time")
|
|
52
|
+
}
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getFrameworksFromPackageJson = exports.frameworksMap = exports.SUPPORTED_FRAMEWORKS = exports.resolvePackageJson = exports.getPlatformFromFolder = exports.pickService = exports.readGQLFiles = exports.readConnectorYaml = exports.readDataConnectYaml = exports.readFirebaseJson = void 0;
|
|
4
|
-
const fs = require("fs-extra");
|
|
5
|
-
const path = require("path");
|
|
6
|
-
const clc = require("colorette");
|
|
7
|
-
const glob_1 = require("glob");
|
|
8
|
-
const error_1 = require("../error");
|
|
9
|
-
const types_1 = require("./types");
|
|
10
|
-
const utils_1 = require("../utils");
|
|
11
|
-
const load_1 = require("./load");
|
|
12
|
-
function readFirebaseJson(config) {
|
|
13
|
-
if (!(config === null || config === void 0 ? void 0 : config.has("dataconnect"))) {
|
|
14
|
-
return [];
|
|
15
|
-
}
|
|
16
|
-
const validator = (cfg) => {
|
|
17
|
-
if (!cfg["source"]) {
|
|
18
|
-
throw new error_1.FirebaseError("Invalid firebase.json: DataConnect requires `source`");
|
|
19
|
-
}
|
|
20
|
-
return {
|
|
21
|
-
source: cfg["source"],
|
|
22
|
-
};
|
|
23
|
-
};
|
|
24
|
-
const configs = config.get("dataconnect");
|
|
25
|
-
if (typeof configs === "object" && !Array.isArray(configs)) {
|
|
26
|
-
return [validator(configs)];
|
|
27
|
-
}
|
|
28
|
-
else if (Array.isArray(configs)) {
|
|
29
|
-
return configs.map(validator);
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
throw new error_1.FirebaseError("Invalid firebase.json: dataconnect should be of the form { source: string }");
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
exports.readFirebaseJson = readFirebaseJson;
|
|
36
|
-
async function readDataConnectYaml(sourceDirectory) {
|
|
37
|
-
const file = await (0, utils_1.readFileFromDirectory)(sourceDirectory, "dataconnect.yaml");
|
|
38
|
-
const dataconnectYaml = await (0, utils_1.wrappedSafeLoad)(file.source);
|
|
39
|
-
return validateDataConnectYaml(dataconnectYaml);
|
|
40
|
-
}
|
|
41
|
-
exports.readDataConnectYaml = readDataConnectYaml;
|
|
42
|
-
function validateDataConnectYaml(unvalidated) {
|
|
43
|
-
if (!unvalidated["location"]) {
|
|
44
|
-
throw new error_1.FirebaseError("Missing required field 'location' in dataconnect.yaml");
|
|
45
|
-
}
|
|
46
|
-
return unvalidated;
|
|
47
|
-
}
|
|
48
|
-
async function readConnectorYaml(sourceDirectory) {
|
|
49
|
-
const file = await (0, utils_1.readFileFromDirectory)(sourceDirectory, "connector.yaml");
|
|
50
|
-
const connectorYaml = await (0, utils_1.wrappedSafeLoad)(file.source);
|
|
51
|
-
return validateConnectorYaml(connectorYaml);
|
|
52
|
-
}
|
|
53
|
-
exports.readConnectorYaml = readConnectorYaml;
|
|
54
|
-
function validateConnectorYaml(unvalidated) {
|
|
55
|
-
return unvalidated;
|
|
56
|
-
}
|
|
57
|
-
async function readGQLFiles(sourceDir) {
|
|
58
|
-
if (!fs.existsSync(sourceDir)) {
|
|
59
|
-
return [];
|
|
60
|
-
}
|
|
61
|
-
const files = await (0, glob_1.glob)("**/*.{gql,graphql}", { cwd: sourceDir, absolute: true, nodir: true });
|
|
62
|
-
return files.map((f) => toFile(sourceDir, f));
|
|
63
|
-
}
|
|
64
|
-
exports.readGQLFiles = readGQLFiles;
|
|
65
|
-
function toFile(sourceDir, fullPath) {
|
|
66
|
-
const relPath = path.relative(sourceDir, fullPath);
|
|
67
|
-
if (!fs.existsSync(fullPath)) {
|
|
68
|
-
throw new error_1.FirebaseError(`file ${fullPath} not found`);
|
|
69
|
-
}
|
|
70
|
-
const content = fs.readFileSync(fullPath).toString();
|
|
71
|
-
return {
|
|
72
|
-
path: relPath,
|
|
73
|
-
content,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
async function pickService(projectId, config, serviceId) {
|
|
77
|
-
const serviceCfgs = readFirebaseJson(config);
|
|
78
|
-
let serviceInfo;
|
|
79
|
-
if (serviceCfgs.length === 0) {
|
|
80
|
-
throw new error_1.FirebaseError("No Data Connect services found in firebase.json." +
|
|
81
|
-
`\nYou can run ${clc.bold("firebase init dataconnect")} to add a Data Connect service.`);
|
|
82
|
-
}
|
|
83
|
-
else if (serviceCfgs.length === 1) {
|
|
84
|
-
serviceInfo = await (0, load_1.load)(projectId, config, serviceCfgs[0].source);
|
|
85
|
-
if (serviceId && serviceId !== serviceInfo.dataConnectYaml.serviceId) {
|
|
86
|
-
throw new error_1.FirebaseError(`No service named ${serviceId} declared in firebase.json. Found ${serviceInfo.dataConnectYaml.serviceId}.` +
|
|
87
|
-
`\nYou can run ${clc.bold("firebase init dataconnect")} to add this Data Connect service.`);
|
|
88
|
-
}
|
|
89
|
-
return serviceInfo;
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
if (!serviceId) {
|
|
93
|
-
throw new error_1.FirebaseError("Multiple Data Connect services found in firebase.json. Please specify a service ID to use.");
|
|
94
|
-
}
|
|
95
|
-
const infos = await Promise.all(serviceCfgs.map((c) => (0, load_1.load)(projectId, config, c.source)));
|
|
96
|
-
const maybe = infos.find((i) => i.dataConnectYaml.serviceId === serviceId);
|
|
97
|
-
if (!maybe) {
|
|
98
|
-
throw new error_1.FirebaseError(`No service named ${serviceId} declared in firebase.json. Found ${infos.map((i) => i.dataConnectYaml.serviceId).join(", ")}.` +
|
|
99
|
-
`\nYou can run ${clc.bold("firebase init dataconnect")} to add this Data Connect service.`);
|
|
100
|
-
}
|
|
101
|
-
return maybe;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
exports.pickService = pickService;
|
|
105
|
-
const WEB_INDICATORS = ["package.json", "package-lock.json", "node_modules"];
|
|
106
|
-
const IOS_INDICATORS = ["info.plist", "podfile", "package.swift", ".xcodeproj"];
|
|
107
|
-
const ANDROID_INDICATORS = ["androidmanifest.xml", "build.gradle", "build.gradle.kts"];
|
|
108
|
-
const DART_INDICATORS = ["pubspec.yaml", "pubspec.lock"];
|
|
109
|
-
const IOS_POSTFIX_INDICATORS = [".xcworkspace", ".xcodeproj"];
|
|
110
|
-
async function getPlatformFromFolder(dirPath) {
|
|
111
|
-
const fileNames = await fs.readdir(dirPath);
|
|
112
|
-
let hasWeb = false;
|
|
113
|
-
let hasAndroid = false;
|
|
114
|
-
let hasIOS = false;
|
|
115
|
-
let hasDart = false;
|
|
116
|
-
for (const fileName of fileNames) {
|
|
117
|
-
const cleanedFileName = fileName.toLowerCase();
|
|
118
|
-
hasWeb || (hasWeb = WEB_INDICATORS.some((indicator) => indicator === cleanedFileName));
|
|
119
|
-
hasAndroid || (hasAndroid = ANDROID_INDICATORS.some((indicator) => indicator === cleanedFileName));
|
|
120
|
-
hasIOS || (hasIOS = IOS_INDICATORS.some((indicator) => indicator === cleanedFileName) ||
|
|
121
|
-
IOS_POSTFIX_INDICATORS.some((indicator) => cleanedFileName.endsWith(indicator)));
|
|
122
|
-
hasDart || (hasDart = DART_INDICATORS.some((indicator) => indicator === cleanedFileName));
|
|
123
|
-
}
|
|
124
|
-
if (!hasWeb && !hasAndroid && !hasIOS && !hasDart) {
|
|
125
|
-
return types_1.Platform.NONE;
|
|
126
|
-
}
|
|
127
|
-
else if (hasWeb && !hasAndroid && !hasIOS && !hasDart) {
|
|
128
|
-
return types_1.Platform.WEB;
|
|
129
|
-
}
|
|
130
|
-
else if (hasAndroid && !hasWeb && !hasIOS && !hasDart) {
|
|
131
|
-
return types_1.Platform.ANDROID;
|
|
132
|
-
}
|
|
133
|
-
else if (hasIOS && !hasWeb && !hasAndroid && !hasDart) {
|
|
134
|
-
return types_1.Platform.IOS;
|
|
135
|
-
}
|
|
136
|
-
else if (hasDart && !hasWeb && !hasIOS && !hasAndroid) {
|
|
137
|
-
return types_1.Platform.FLUTTER;
|
|
138
|
-
}
|
|
139
|
-
return types_1.Platform.MULTIPLE;
|
|
140
|
-
}
|
|
141
|
-
exports.getPlatformFromFolder = getPlatformFromFolder;
|
|
142
|
-
async function resolvePackageJson(packageJsonPath) {
|
|
143
|
-
let validPackageJsonPath = packageJsonPath;
|
|
144
|
-
if (!packageJsonPath.endsWith("package.json")) {
|
|
145
|
-
validPackageJsonPath = path.join(packageJsonPath, "package.json");
|
|
146
|
-
}
|
|
147
|
-
validPackageJsonPath = path.resolve(validPackageJsonPath);
|
|
148
|
-
try {
|
|
149
|
-
return JSON.parse((await fs.readFile(validPackageJsonPath)).toString());
|
|
150
|
-
}
|
|
151
|
-
catch (_a) {
|
|
152
|
-
return undefined;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
exports.resolvePackageJson = resolvePackageJson;
|
|
156
|
-
exports.SUPPORTED_FRAMEWORKS = ["react", "angular"];
|
|
157
|
-
exports.frameworksMap = {
|
|
158
|
-
react: ["react", "next"],
|
|
159
|
-
angular: ["@angular/core"],
|
|
160
|
-
};
|
|
161
|
-
function getFrameworksFromPackageJson(packageJson) {
|
|
162
|
-
var _a, _b;
|
|
163
|
-
const devDependencies = Object.keys((_a = packageJson.devDependencies) !== null && _a !== void 0 ? _a : {});
|
|
164
|
-
const dependencies = Object.keys((_b = packageJson.dependencies) !== null && _b !== void 0 ? _b : {});
|
|
165
|
-
const allDeps = Array.from(new Set([...devDependencies, ...dependencies]));
|
|
166
|
-
return exports.SUPPORTED_FRAMEWORKS.filter((framework) => exports.frameworksMap[framework].find((dep) => allDeps.includes(dep)));
|
|
167
|
-
}
|
|
168
|
-
exports.getFrameworksFromPackageJson = getFrameworksFromPackageJson;
|