From 0ec2d7da89c7ab08af00944adfd1869a246f2045 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 22:21:59 +0000 Subject: [PATCH 01/16] feat(dataconnect): Update dataconnect:* commands to use flags Updated all `dataconnect:*` commands that previously used a positional argument for the service ID to use `--service` and `--location` flags instead. This change improves the usability and consistency of the CLI. The following commands were updated: - `dataconnect:sql:migrate` - `dataconnect:sql:diff` - `dataconnect:sdk:generate` - `dataconnect:sql:grant` - `dataconnect:sql:setup` - `dataconnect:sql:shell` The `pickService` and `loadAll` functions in `src/dataconnect/load.ts` were also updated to support filtering by location. --- npm-shrinkwrap.json | 15 ++++++++------- package.json | 2 +- src/commands/dataconnect-sdk-generate.ts | 14 +++++++++++--- src/commands/dataconnect-sql-diff.ts | 13 ++++++++++--- src/commands/dataconnect-sql-grant.ts | 13 ++++++++++--- src/commands/dataconnect-sql-migrate.ts | 13 ++++++++++--- src/commands/dataconnect-sql-setup.ts | 13 ++++++++++--- src/commands/dataconnect-sql-shell.ts | 13 ++++++++++--- src/dataconnect/load.ts | 15 ++++++++++++--- 9 files changed, 82 insertions(+), 29 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 9aa64561c77..3a43b4646a3 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -172,7 +172,7 @@ "source-map-support": "^0.5.9", "supertest": "^6.2.3", "swagger2openapi": "^7.0.8", - "ts-node": "^10.4.0", + "ts-node": "^10.9.2", "typescript": "^4.5.4", "typescript-json-schema": "^0.65.1", "vite": "^4.2.1" @@ -20361,9 +20361,10 @@ "dev": true }, "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -36920,9 +36921,9 @@ "dev": true }, "ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", diff --git a/package.json b/package.json index 3ab1a6b8f3e..bfc70e48eb8 100644 --- a/package.json +++ b/package.json @@ -262,7 +262,7 @@ "source-map-support": "^0.5.9", "supertest": "^6.2.3", "swagger2openapi": "^7.0.8", - "ts-node": "^10.4.0", + "ts-node": "^10.9.2", "typescript": "^4.5.4", "typescript-json-schema": "^0.65.1", "vite": "^4.2.1" diff --git a/src/commands/dataconnect-sdk-generate.ts b/src/commands/dataconnect-sdk-generate.ts index 741b74370b4..afcd9ca53c5 100644 --- a/src/commands/dataconnect-sdk-generate.ts +++ b/src/commands/dataconnect-sdk-generate.ts @@ -4,24 +4,32 @@ import { Command } from "../command"; import { Options } from "../options"; import { DataConnectEmulator } from "../emulator/dataconnectEmulator"; import { needProjectId } from "../projectUtils"; -import { loadAll } from "../dataconnect/load"; +import { loadAll, pickService } from "../dataconnect/load"; import { logger } from "../logger"; import { getProjectDefaultAccount } from "../auth"; import { logLabeledSuccess } from "../utils"; import { ServiceInfo } from "../dataconnect/types"; -type GenerateOptions = Options & { watch?: boolean }; +type GenerateOptions = Options & { watch?: boolean; service?: string }; export const command = new Command("dataconnect:sdk:generate") .description("generate typed SDKs for your Data Connect connectors") + .option( + "--service ", + "the serviceId of the Data Connect service. If not provided, generates SDKs for all services.", + ) + .option("--location ", "the location of the Data Connect service", "us-central1") .option( "--watch", "watch for changes to your connector GQL files and regenerate your SDKs when updates occur", ) .action(async (options: GenerateOptions) => { const projectId = needProjectId(options); + const location = options.location as string; - const serviceInfos = await loadAll(projectId, options.config); + const serviceInfos = options.service + ? [await pickService(projectId, options.config, options.service, location)] + : await loadAll(projectId, options.config, location); const serviceInfosWithSDKs = serviceInfos.filter((serviceInfo) => serviceInfo.connectorInfo.some((c) => { return ( diff --git a/src/commands/dataconnect-sql-diff.ts b/src/commands/dataconnect-sql-diff.ts index 73035ad52e8..c1851ce821c 100644 --- a/src/commands/dataconnect-sql-diff.ts +++ b/src/commands/dataconnect-sql-diff.ts @@ -7,20 +7,27 @@ import { pickService } from "../dataconnect/load"; import { diffSchema } from "../dataconnect/schemaMigration"; import { requireAuth } from "../requireAuth"; -export const command = new Command("dataconnect:sql:diff [serviceId]") +export const command = new Command("dataconnect:sql:diff") .description( "display the differences between a local Data Connect schema and your CloudSQL database's current schema", ) + .option("--service ", "the serviceId of the Data Connect service") + .option("--location ", "the location of the Data Connect service", "us-central1") .before(requirePermissions, [ "firebasedataconnect.services.list", "firebasedataconnect.schemas.list", "firebasedataconnect.schemas.update", ]) .before(requireAuth) - .action(async (serviceId: string, options: Options) => { + .action(async (options: Options) => { const projectId = needProjectId(options); + if (!options.service) { + throw new FirebaseError("Missing required flag --service"); + } + const serviceId = options.service as string; + const location = options.location as string; await ensureApis(projectId); - const serviceInfo = await pickService(projectId, options.config, serviceId); + const serviceInfo = await pickService(projectId, options.config, serviceId, location); const diffs = await diffSchema( options, diff --git a/src/commands/dataconnect-sql-grant.ts b/src/commands/dataconnect-sql-grant.ts index 30a25da9e9e..a5a73498640 100644 --- a/src/commands/dataconnect-sql-grant.ts +++ b/src/commands/dataconnect-sql-grant.ts @@ -12,8 +12,10 @@ import { iamUserIsCSQLAdmin } from "../gcp/cloudsql/cloudsqladmin"; const allowedRoles = Object.keys(fdcSqlRoleMap); -export const command = new Command("dataconnect:sql:grant [serviceId]") +export const command = new Command("dataconnect:sql:grant") .description("grants the SQL role to the provided user or service account ") + .option("--service ", "the serviceId of the Data Connect service") + .option("--location ", "the location of the Data Connect service", "us-central1") .option("-R, --role ", "The SQL role to grant. One of: owner, writer, or reader.") .option( "-E, --email ", @@ -21,7 +23,12 @@ export const command = new Command("dataconnect:sql:grant [serviceId]") ) .before(requirePermissions, ["firebasedataconnect.services.list"]) .before(requireAuth) - .action(async (serviceId: string, options: Options) => { + .action(async (options: Options) => { + if (!options.service) { + throw new FirebaseError("Missing required flag --service"); + } + const serviceId = options.service as string; + const location = options.location as string; const role = options.role as string; const email = options.email as string; if (!role) { @@ -49,7 +56,7 @@ export const command = new Command("dataconnect:sql:grant [serviceId]") const projectId = needProjectId(options); await ensureApis(projectId); - const serviceInfo = await pickService(projectId, options.config, serviceId); + const serviceInfo = await pickService(projectId, options.config, serviceId, location); await grantRoleToUserInSchema(options, serviceInfo.schema); return { projectId, serviceId }; diff --git a/src/commands/dataconnect-sql-migrate.ts b/src/commands/dataconnect-sql-migrate.ts index bcc72943327..d58f157728e 100644 --- a/src/commands/dataconnect-sql-migrate.ts +++ b/src/commands/dataconnect-sql-migrate.ts @@ -9,8 +9,10 @@ import { requirePermissions } from "../requirePermissions"; import { ensureApis } from "../dataconnect/ensureApis"; import { logLabeledSuccess } from "../utils"; -export const command = new Command("dataconnect:sql:migrate [serviceId]") +export const command = new Command("dataconnect:sql:migrate") .description("migrate your CloudSQL database's schema to match your local Data Connect schema") + .option("--service ", "the serviceId of the Data Connect service") + .option("--location ", "the location of the Data Connect service", "us-central1") .before(requirePermissions, [ "firebasedataconnect.services.list", "firebasedataconnect.schemas.list", @@ -19,10 +21,15 @@ export const command = new Command("dataconnect:sql:migrate [serviceId]") ]) .before(requireAuth) .withForce("execute any required database changes without prompting") - .action(async (serviceId: string, options: Options) => { + .action(async (options: Options) => { const projectId = needProjectId(options); + if (!options.service) { + throw new FirebaseError("Missing required flag --service"); + } + const serviceId = options.service as string; + const location = options.location as string; await ensureApis(projectId); - const serviceInfo = await pickService(projectId, options.config, serviceId); + const serviceInfo = await pickService(projectId, options.config, serviceId, location); const instanceId = serviceInfo.dataConnectYaml.schema.datasource.postgresql?.cloudSql.instanceId; if (!instanceId) { diff --git a/src/commands/dataconnect-sql-setup.ts b/src/commands/dataconnect-sql-setup.ts index 164a1d55d53..a739dc9fda8 100644 --- a/src/commands/dataconnect-sql-setup.ts +++ b/src/commands/dataconnect-sql-setup.ts @@ -11,8 +11,10 @@ import { DEFAULT_SCHEMA } from "../gcp/cloudsql/permissions"; import { getIdentifiers, ensureServiceIsConnectedToCloudSql } from "../dataconnect/schemaMigration"; import { setupIAMUsers } from "../gcp/cloudsql/connect"; -export const command = new Command("dataconnect:sql:setup [serviceId]") +export const command = new Command("dataconnect:sql:setup") .description("set up your CloudSQL database") + .option("--service ", "the serviceId of the Data Connect service") + .option("--location ", "the location of the Data Connect service", "us-central1") .before(requirePermissions, [ "firebasedataconnect.services.list", "firebasedataconnect.schemas.list", @@ -20,10 +22,15 @@ export const command = new Command("dataconnect:sql:setup [serviceId]") "cloudsql.instances.connect", ]) .before(requireAuth) - .action(async (serviceId: string, options: Options) => { + .action(async (options: Options) => { const projectId = needProjectId(options); + if (!options.service) { + throw new FirebaseError("Missing required flag --service"); + } + const serviceId = options.service as string; + const location = options.location as string; await ensureApis(projectId); - const serviceInfo = await pickService(projectId, options.config, serviceId); + const serviceInfo = await pickService(projectId, options.config, serviceId, location); const instanceId = serviceInfo.dataConnectYaml.schema.datasource.postgresql?.cloudSql.instanceId; if (!instanceId) { diff --git a/src/commands/dataconnect-sql-shell.ts b/src/commands/dataconnect-sql-shell.ts index 6023fb44905..ceb3b971f44 100644 --- a/src/commands/dataconnect-sql-shell.ts +++ b/src/commands/dataconnect-sql-shell.ts @@ -81,16 +81,23 @@ async function mainShellLoop(conn: pg.PoolClient) { } } -export const command = new Command("dataconnect:sql:shell [serviceId]") +export const command = new Command("dataconnect:sql:shell") .description( "start a shell connected directly to your Data Connect service's linked CloudSQL instance", ) + .option("--service ", "the serviceId of the Data Connect service") + .option("--location ", "the location of the Data Connect service", "us-central1") .before(requirePermissions, ["firebasedataconnect.services.list", "cloudsql.instances.connect"]) .before(requireAuth) - .action(async (serviceId: string, options: Options) => { + .action(async (options: Options) => { const projectId = needProjectId(options); + if (!options.service) { + throw new FirebaseError("Missing required flag --service"); + } + const serviceId = options.service as string; + const location = options.location as string; await ensureApis(projectId); - const serviceInfo = await pickService(projectId, options.config, serviceId); + const serviceInfo = await pickService(projectId, options.config, serviceId, location); const { instanceId, databaseId } = getIdentifiers(serviceInfo.schema); const { user: username } = await getIAMUser(options); const instance = await cloudSqlAdminClient.getInstance(projectId, instanceId); diff --git a/src/dataconnect/load.ts b/src/dataconnect/load.ts index 4f021746e22..5a5beaa4d1d 100644 --- a/src/dataconnect/load.ts +++ b/src/dataconnect/load.ts @@ -21,8 +21,9 @@ export async function pickService( projectId: string, config: Config, serviceId?: string, + location?: string, ): Promise { - const serviceInfos = await loadAll(projectId, config); + const serviceInfos = await loadAll(projectId, config, location); if (serviceInfos.length === 0) { throw new FirebaseError( "No Data Connect services found in firebase.json." + @@ -58,9 +59,17 @@ export async function pickService( /** * Loads all Data Connect service configurations from the firebase.json file. */ -export async function loadAll(projectId: string, config: Config): Promise { +export async function loadAll( + projectId: string, + config: Config, + location?: string, +): Promise { const serviceCfgs = readFirebaseJson(config); - return await Promise.all(serviceCfgs.map((c) => load(projectId, config, c.source))); + let serviceInfos = await Promise.all(serviceCfgs.map((c) => load(projectId, config, c.source))); + if (location) { + serviceInfos = serviceInfos.filter((si) => si.dataConnectYaml.location === location); + } + return serviceInfos; } /** From d6b9f3f53d70117bf4e9eabe16fd85aa31f71ffa Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Mon, 13 Oct 2025 15:27:44 -0700 Subject: [PATCH 02/16] --location is optional --- npm-shrinkwrap.json | 15 +++++++-------- package.json | 2 +- src/commands/dataconnect-sdk-generate.ts | 2 +- src/commands/dataconnect-sql-diff.ts | 3 ++- src/commands/dataconnect-sql-grant.ts | 2 +- src/commands/dataconnect-sql-migrate.ts | 2 +- src/commands/dataconnect-sql-setup.ts | 2 +- src/commands/dataconnect-sql-shell.ts | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 3a43b4646a3..9aa64561c77 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -172,7 +172,7 @@ "source-map-support": "^0.5.9", "supertest": "^6.2.3", "swagger2openapi": "^7.0.8", - "ts-node": "^10.9.2", + "ts-node": "^10.4.0", "typescript": "^4.5.4", "typescript-json-schema": "^0.65.1", "vite": "^4.2.1" @@ -20361,10 +20361,9 @@ "dev": true }, "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "license": "MIT", + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -36921,9 +36920,9 @@ "dev": true }, "ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", diff --git a/package.json b/package.json index bfc70e48eb8..3ab1a6b8f3e 100644 --- a/package.json +++ b/package.json @@ -262,7 +262,7 @@ "source-map-support": "^0.5.9", "supertest": "^6.2.3", "swagger2openapi": "^7.0.8", - "ts-node": "^10.9.2", + "ts-node": "^10.4.0", "typescript": "^4.5.4", "typescript-json-schema": "^0.65.1", "vite": "^4.2.1" diff --git a/src/commands/dataconnect-sdk-generate.ts b/src/commands/dataconnect-sdk-generate.ts index afcd9ca53c5..ec35c228a23 100644 --- a/src/commands/dataconnect-sdk-generate.ts +++ b/src/commands/dataconnect-sdk-generate.ts @@ -18,7 +18,7 @@ export const command = new Command("dataconnect:sdk:generate") "--service ", "the serviceId of the Data Connect service. If not provided, generates SDKs for all services.", ) - .option("--location ", "the location of the Data Connect service", "us-central1") + .option("--location ", "the location of the Data Connect service to disambiguate") .option( "--watch", "watch for changes to your connector GQL files and regenerate your SDKs when updates occur", diff --git a/src/commands/dataconnect-sql-diff.ts b/src/commands/dataconnect-sql-diff.ts index c1851ce821c..7954ee9bb1e 100644 --- a/src/commands/dataconnect-sql-diff.ts +++ b/src/commands/dataconnect-sql-diff.ts @@ -6,13 +6,14 @@ import { requirePermissions } from "../requirePermissions"; import { pickService } from "../dataconnect/load"; import { diffSchema } from "../dataconnect/schemaMigration"; import { requireAuth } from "../requireAuth"; +import { FirebaseError } from "../error"; export const command = new Command("dataconnect:sql:diff") .description( "display the differences between a local Data Connect schema and your CloudSQL database's current schema", ) .option("--service ", "the serviceId of the Data Connect service") - .option("--location ", "the location of the Data Connect service", "us-central1") + .option("--location ", "the location of the Data Connect service to disambiguate") .before(requirePermissions, [ "firebasedataconnect.services.list", "firebasedataconnect.schemas.list", diff --git a/src/commands/dataconnect-sql-grant.ts b/src/commands/dataconnect-sql-grant.ts index a5a73498640..3b504ad0b67 100644 --- a/src/commands/dataconnect-sql-grant.ts +++ b/src/commands/dataconnect-sql-grant.ts @@ -15,7 +15,7 @@ const allowedRoles = Object.keys(fdcSqlRoleMap); export const command = new Command("dataconnect:sql:grant") .description("grants the SQL role to the provided user or service account ") .option("--service ", "the serviceId of the Data Connect service") - .option("--location ", "the location of the Data Connect service", "us-central1") + .option("--location ", "the location of the Data Connect service to disambiguate") .option("-R, --role ", "The SQL role to grant. One of: owner, writer, or reader.") .option( "-E, --email ", diff --git a/src/commands/dataconnect-sql-migrate.ts b/src/commands/dataconnect-sql-migrate.ts index d58f157728e..7c8df5a46cc 100644 --- a/src/commands/dataconnect-sql-migrate.ts +++ b/src/commands/dataconnect-sql-migrate.ts @@ -12,7 +12,7 @@ import { logLabeledSuccess } from "../utils"; export const command = new Command("dataconnect:sql:migrate") .description("migrate your CloudSQL database's schema to match your local Data Connect schema") .option("--service ", "the serviceId of the Data Connect service") - .option("--location ", "the location of the Data Connect service", "us-central1") + .option("--location ", "the location of the Data Connect service to disambiguate") .before(requirePermissions, [ "firebasedataconnect.services.list", "firebasedataconnect.schemas.list", diff --git a/src/commands/dataconnect-sql-setup.ts b/src/commands/dataconnect-sql-setup.ts index a739dc9fda8..59d58cb94df 100644 --- a/src/commands/dataconnect-sql-setup.ts +++ b/src/commands/dataconnect-sql-setup.ts @@ -14,7 +14,7 @@ import { setupIAMUsers } from "../gcp/cloudsql/connect"; export const command = new Command("dataconnect:sql:setup") .description("set up your CloudSQL database") .option("--service ", "the serviceId of the Data Connect service") - .option("--location ", "the location of the Data Connect service", "us-central1") + .option("--location ", "the location of the Data Connect service to disambiguate") .before(requirePermissions, [ "firebasedataconnect.services.list", "firebasedataconnect.schemas.list", diff --git a/src/commands/dataconnect-sql-shell.ts b/src/commands/dataconnect-sql-shell.ts index ceb3b971f44..1a80b50b119 100644 --- a/src/commands/dataconnect-sql-shell.ts +++ b/src/commands/dataconnect-sql-shell.ts @@ -86,7 +86,7 @@ export const command = new Command("dataconnect:sql:shell") "start a shell connected directly to your Data Connect service's linked CloudSQL instance", ) .option("--service ", "the serviceId of the Data Connect service") - .option("--location ", "the location of the Data Connect service", "us-central1") + .option("--location ", "the location of the Data Connect service to disambiguate") .before(requirePermissions, ["firebasedataconnect.services.list", "cloudsql.instances.connect"]) .before(requireAuth) .action(async (options: Options) => { From fe9367a971eddf9d28013a284310ee2d6dd2a02d Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Mon, 13 Oct 2025 16:03:31 -0700 Subject: [PATCH 03/16] compile --- src/commands/dataconnect-sdk-generate.ts | 12 +-- src/commands/dataconnect-sql-diff.ts | 14 +--- src/commands/dataconnect-sql-grant.ts | 15 ++-- src/commands/dataconnect-sql-migrate.ts | 11 +-- src/commands/dataconnect-sql-setup.ts | 14 ++-- src/commands/dataconnect-sql-shell.ts | 11 +-- src/dataconnect/load.ts | 81 ++++++++++--------- src/mcp/tools/dataconnect/compile.ts | 18 +++-- src/mcp/tools/dataconnect/execute.ts | 23 ++++-- .../tools/dataconnect/generate_operation.ts | 19 +++-- 10 files changed, 111 insertions(+), 107 deletions(-) diff --git a/src/commands/dataconnect-sdk-generate.ts b/src/commands/dataconnect-sdk-generate.ts index ec35c228a23..e3cf03c309e 100644 --- a/src/commands/dataconnect-sdk-generate.ts +++ b/src/commands/dataconnect-sdk-generate.ts @@ -4,16 +4,16 @@ import { Command } from "../command"; import { Options } from "../options"; import { DataConnectEmulator } from "../emulator/dataconnectEmulator"; import { needProjectId } from "../projectUtils"; -import { loadAll, pickService } from "../dataconnect/load"; +import { pickServices } from "../dataconnect/load"; import { logger } from "../logger"; import { getProjectDefaultAccount } from "../auth"; import { logLabeledSuccess } from "../utils"; import { ServiceInfo } from "../dataconnect/types"; -type GenerateOptions = Options & { watch?: boolean; service?: string }; +type GenerateOptions = Options & { watch?: boolean; service?: string; location?: string }; export const command = new Command("dataconnect:sdk:generate") - .description("generate typed SDKs for your Data Connect connectors") + .description("generate typed SDKs to use Data Connect in your apps") .option( "--service ", "the serviceId of the Data Connect service. If not provided, generates SDKs for all services.", @@ -25,11 +25,7 @@ export const command = new Command("dataconnect:sdk:generate") ) .action(async (options: GenerateOptions) => { const projectId = needProjectId(options); - const location = options.location as string; - - const serviceInfos = options.service - ? [await pickService(projectId, options.config, options.service, location)] - : await loadAll(projectId, options.config, location); + const serviceInfos = await pickServices(projectId, options.config, options.service, options.location); const serviceInfosWithSDKs = serviceInfos.filter((serviceInfo) => serviceInfo.connectorInfo.some((c) => { return ( diff --git a/src/commands/dataconnect-sql-diff.ts b/src/commands/dataconnect-sql-diff.ts index 7954ee9bb1e..b603533033c 100644 --- a/src/commands/dataconnect-sql-diff.ts +++ b/src/commands/dataconnect-sql-diff.ts @@ -3,14 +3,13 @@ import { Options } from "../options"; import { needProjectId } from "../projectUtils"; import { ensureApis } from "../dataconnect/ensureApis"; import { requirePermissions } from "../requirePermissions"; -import { pickService } from "../dataconnect/load"; +import { pickOneService } from "../dataconnect/load"; import { diffSchema } from "../dataconnect/schemaMigration"; import { requireAuth } from "../requireAuth"; -import { FirebaseError } from "../error"; export const command = new Command("dataconnect:sql:diff") .description( - "display the differences between a local Data Connect schema and your CloudSQL database's current schema", + "display the differences between the local Data Connect schema and your CloudSQL database's schema", ) .option("--service ", "the serviceId of the Data Connect service") .option("--location ", "the location of the Data Connect service to disambiguate") @@ -22,18 +21,13 @@ export const command = new Command("dataconnect:sql:diff") .before(requireAuth) .action(async (options: Options) => { const projectId = needProjectId(options); - if (!options.service) { - throw new FirebaseError("Missing required flag --service"); - } - const serviceId = options.service as string; - const location = options.location as string; await ensureApis(projectId); - const serviceInfo = await pickService(projectId, options.config, serviceId, location); + const serviceInfo = await pickOneService(projectId, options.config, options.service, options.location); const diffs = await diffSchema( options, serviceInfo.schema, serviceInfo.dataConnectYaml.schema.datasource.postgresql?.schemaValidation, ); - return { projectId, serviceId, diffs }; + return {projectId, diffs }; }); diff --git a/src/commands/dataconnect-sql-grant.ts b/src/commands/dataconnect-sql-grant.ts index 3b504ad0b67..f80d8003ffa 100644 --- a/src/commands/dataconnect-sql-grant.ts +++ b/src/commands/dataconnect-sql-grant.ts @@ -3,7 +3,7 @@ import { Options } from "../options"; import { needProjectId } from "../projectUtils"; import { ensureApis } from "../dataconnect/ensureApis"; import { requirePermissions } from "../requirePermissions"; -import { pickService } from "../dataconnect/load"; +import { pickOneService } from "../dataconnect/load"; import { grantRoleToUserInSchema } from "../dataconnect/schemaMigration"; import { requireAuth } from "../requireAuth"; import { FirebaseError } from "../error"; @@ -14,21 +14,16 @@ const allowedRoles = Object.keys(fdcSqlRoleMap); export const command = new Command("dataconnect:sql:grant") .description("grants the SQL role to the provided user or service account ") - .option("--service ", "the serviceId of the Data Connect service") - .option("--location ", "the location of the Data Connect service to disambiguate") .option("-R, --role ", "The SQL role to grant. One of: owner, writer, or reader.") .option( "-E, --email ", "The email of the user or service account we would like to grant the role to.", ) + .option("--service ", "the serviceId of the Data Connect service") + .option("--location ", "the location of the Data Connect service to disambiguate") .before(requirePermissions, ["firebasedataconnect.services.list"]) .before(requireAuth) .action(async (options: Options) => { - if (!options.service) { - throw new FirebaseError("Missing required flag --service"); - } - const serviceId = options.service as string; - const location = options.location as string; const role = options.role as string; const email = options.email as string; if (!role) { @@ -56,8 +51,8 @@ export const command = new Command("dataconnect:sql:grant") const projectId = needProjectId(options); await ensureApis(projectId); - const serviceInfo = await pickService(projectId, options.config, serviceId, location); + const serviceInfo = await pickOneService(projectId, options.config, options.service, options.location); await grantRoleToUserInSchema(options, serviceInfo.schema); - return { projectId, serviceId }; + return { projectId }; }); diff --git a/src/commands/dataconnect-sql-migrate.ts b/src/commands/dataconnect-sql-migrate.ts index 7c8df5a46cc..133b729dcd2 100644 --- a/src/commands/dataconnect-sql-migrate.ts +++ b/src/commands/dataconnect-sql-migrate.ts @@ -1,7 +1,7 @@ import { Command } from "../command"; import { Options } from "../options"; import { needProjectId } from "../projectUtils"; -import { pickService } from "../dataconnect/load"; +import { pickOneService } from "../dataconnect/load"; import { FirebaseError } from "../error"; import { migrateSchema } from "../dataconnect/schemaMigration"; import { requireAuth } from "../requireAuth"; @@ -23,13 +23,8 @@ export const command = new Command("dataconnect:sql:migrate") .withForce("execute any required database changes without prompting") .action(async (options: Options) => { const projectId = needProjectId(options); - if (!options.service) { - throw new FirebaseError("Missing required flag --service"); - } - const serviceId = options.service as string; - const location = options.location as string; await ensureApis(projectId); - const serviceInfo = await pickService(projectId, options.config, serviceId, location); + const serviceInfo = await pickOneService(projectId, options.config, options.service, options.location); const instanceId = serviceInfo.dataConnectYaml.schema.datasource.postgresql?.cloudSql.instanceId; if (!instanceId) { @@ -51,5 +46,5 @@ export const command = new Command("dataconnect:sql:migrate") } else { logLabeledSuccess("dataconnect", "Database schema is already up to date!"); } - return { projectId, serviceId, diffs }; + return { projectId, diffs }; }); diff --git a/src/commands/dataconnect-sql-setup.ts b/src/commands/dataconnect-sql-setup.ts index 59d58cb94df..aed54ae1b38 100644 --- a/src/commands/dataconnect-sql-setup.ts +++ b/src/commands/dataconnect-sql-setup.ts @@ -1,7 +1,6 @@ import { Command } from "../command"; import { Options } from "../options"; import { needProjectId } from "../projectUtils"; -import { pickService } from "../dataconnect/load"; import { FirebaseError } from "../error"; import { requireAuth } from "../requireAuth"; import { requirePermissions } from "../requirePermissions"; @@ -10,6 +9,7 @@ import { setupSQLPermissions, getSchemaMetadata } from "../gcp/cloudsql/permissi import { DEFAULT_SCHEMA } from "../gcp/cloudsql/permissions"; import { getIdentifiers, ensureServiceIsConnectedToCloudSql } from "../dataconnect/schemaMigration"; import { setupIAMUsers } from "../gcp/cloudsql/connect"; +import { pickOneService } from "../dataconnect/load"; export const command = new Command("dataconnect:sql:setup") .description("set up your CloudSQL database") @@ -24,13 +24,13 @@ export const command = new Command("dataconnect:sql:setup") .before(requireAuth) .action(async (options: Options) => { const projectId = needProjectId(options); - if (!options.service) { - throw new FirebaseError("Missing required flag --service"); - } - const serviceId = options.service as string; - const location = options.location as string; await ensureApis(projectId); - const serviceInfo = await pickService(projectId, options.config, serviceId, location); + const serviceInfo = await pickOneService( + projectId, + options.config, + options.service, + options.location, + ); const instanceId = serviceInfo.dataConnectYaml.schema.datasource.postgresql?.cloudSql.instanceId; if (!instanceId) { diff --git a/src/commands/dataconnect-sql-shell.ts b/src/commands/dataconnect-sql-shell.ts index 1a80b50b119..cfc17349f00 100644 --- a/src/commands/dataconnect-sql-shell.ts +++ b/src/commands/dataconnect-sql-shell.ts @@ -7,7 +7,7 @@ import { Options } from "../options"; import { needProjectId } from "../projectUtils"; import { ensureApis } from "../dataconnect/ensureApis"; import { requirePermissions } from "../requirePermissions"; -import { pickService } from "../dataconnect/load"; +import { pickOneService } from "../dataconnect/load"; import { getIdentifiers } from "../dataconnect/schemaMigration"; import { requireAuth } from "../requireAuth"; import { getIAMUser } from "../gcp/cloudsql/connect"; @@ -91,13 +91,8 @@ export const command = new Command("dataconnect:sql:shell") .before(requireAuth) .action(async (options: Options) => { const projectId = needProjectId(options); - if (!options.service) { - throw new FirebaseError("Missing required flag --service"); - } - const serviceId = options.service as string; - const location = options.location as string; await ensureApis(projectId); - const serviceInfo = await pickService(projectId, options.config, serviceId, location); + const serviceInfo = await pickOneService(projectId, options.config, options.service, options.location); const { instanceId, databaseId } = getIdentifiers(serviceInfo.schema); const { user: username } = await getIAMUser(options); const instance = await cloudSqlAdminClient.getInstance(projectId, instanceId); @@ -141,5 +136,5 @@ export const command = new Command("dataconnect:sql:shell") await pool.end(); connector.close(); - return { projectId, serviceId }; + return { projectId }; }); diff --git a/src/dataconnect/load.ts b/src/dataconnect/load.ts index 5a5beaa4d1d..605b08f5739 100644 --- a/src/dataconnect/load.ts +++ b/src/dataconnect/load.ts @@ -15,61 +15,64 @@ import { import { readFileFromDirectory, wrappedSafeLoad } from "../utils"; import { DataConnectMultiple } from "../firebaseConfig"; -// pickService reads firebase.json and returns all services with a given serviceId. +export async function pickOneService(projectId: string, config: Config, service?: string, location?: string): Promise { + const services = await pickServices(projectId, config, service, location); + if (services.length > 1) { + const serviceIds = services.map( + (i) => `${i.dataConnectYaml.location}:${i.dataConnectYaml.serviceId}`, + ); + throw new FirebaseError( + `Multiple services matched. Please specify a service and location. Matched services: ${serviceIds.join( + ", ", + )}`, + ); + } + return services[0]; +} + +// pickService reads firebase.json and returns a service with a given serviceId and location. // If serviceID is not provided and there is a single service, return that. -export async function pickService( +export async function pickServices( projectId: string, config: Config, serviceId?: string, location?: string, -): Promise { - const serviceInfos = await loadAll(projectId, config, location); +): Promise { + const serviceInfos = await loadAll(projectId, config); if (serviceInfos.length === 0) { + let message = "No Data Connect services found in firebase.json."; + if (location) { + message = `No Data Connect services for location ${location} found in firebase.json.`; + } throw new FirebaseError( - "No Data Connect services found in firebase.json." + + message + `\nYou can run ${clc.bold("firebase init dataconnect")} to add a Data Connect service.`, ); - } else if (serviceInfos.length === 1) { - if (serviceId && serviceId !== serviceInfos[0].dataConnectYaml.serviceId) { - throw new FirebaseError( - `No service named ${serviceId} declared in firebase.json. Found ${serviceInfos[0].dataConnectYaml.serviceId}.` + - `\nYou can run ${clc.bold("firebase init dataconnect")} to add this Data Connect service.`, - ); - } - return serviceInfos[0]; - } else { - if (!serviceId) { - throw new FirebaseError( - "Multiple Data Connect services found in firebase.json. Please specify a service ID to use.", - ); - } - // TODO: handle cases where there are services with the same ID in 2 locations. - const maybe = serviceInfos.find((i) => i.dataConnectYaml.serviceId === serviceId); - if (!maybe) { - const serviceIds = serviceInfos.map((i) => i.dataConnectYaml.serviceId); - throw new FirebaseError( - `No service named ${serviceId} declared in firebase.json. Found ${serviceIds.join(", ")}.` + - `\nYou can run ${clc.bold("firebase init dataconnect")} to add this Data Connect service.`, - ); - } - return maybe; } + + const matchingServices = serviceInfos.filter( + (i) => + (!serviceId || i.dataConnectYaml.serviceId === serviceId) && + (!location || i.dataConnectYaml.location === location), + ); + if (matchingServices.length === 0) { + const serviceIds = serviceInfos.map( + (i) => `${i.dataConnectYaml.location}:${i.dataConnectYaml.serviceId}`, + ); + throw new FirebaseError( + `No service matched service in firebase.json. Available services: ${serviceIds.join(", ")}` + + `\nYou can run ${clc.bold("firebase init dataconnect")} to add this Data Connect service.`, + ); + } + return matchingServices; } /** * Loads all Data Connect service configurations from the firebase.json file. */ -export async function loadAll( - projectId: string, - config: Config, - location?: string, -): Promise { +export async function loadAll(projectId: string, config: Config): Promise { const serviceCfgs = readFirebaseJson(config); - let serviceInfos = await Promise.all(serviceCfgs.map((c) => load(projectId, config, c.source))); - if (location) { - serviceInfos = serviceInfos.filter((si) => si.dataConnectYaml.location === location); - } - return serviceInfos; + return await Promise.all(serviceCfgs.map((c) => load(projectId, config, c.source))); } /** diff --git a/src/mcp/tools/dataconnect/compile.ts b/src/mcp/tools/dataconnect/compile.ts index e5904385051..d5d02440eed 100644 --- a/src/mcp/tools/dataconnect/compile.ts +++ b/src/mcp/tools/dataconnect/compile.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { tool } from "../../tool"; -import { pickService } from "../../../dataconnect/load"; import { compileErrors } from "../../util/dataconnect/compile"; +import { pickOneService, pickServices } from "../../../dataconnect/load"; export const compile = tool( { @@ -13,11 +13,15 @@ export const compile = tool( .enum(["all", "schema", "operations"]) .describe("filter errors to a specific type only. defaults to `all` if omitted.") .optional(), - service_id: z + service_id: z.string().optional() + .describe( + `Data Connect Service ID to dis-ambulate if there are multiple Data Connect services.`, + ), + location_id: z .string() .optional() .describe( - "The Firebase Data Connect service ID to look for. If omitted, builds all services defined in `firebase.json`.", + `Data Connect Service location ID to dis-ambulate among multiple Data Connect services.`, ), }), annotations: { @@ -29,9 +33,11 @@ export const compile = tool( requiresAuth: false, }, }, - async ({ service_id, error_filter }, { projectId, config }) => { - const serviceInfo = await pickService(projectId, config, service_id || undefined); - const errors = await compileErrors(serviceInfo.sourceDirectory, error_filter); + async ({ service_id, location_id, error_filter }, { projectId, config }) => { + const serviceInfos = await pickServices(projectId, config, service_id || undefined, location_id || undefined); + const errors = await Promise.all(serviceInfos.map(async (serviceInfo) => { + return await compileErrors(serviceInfo.sourceDirectory, error_filter); + })); if (errors) return { content: [ diff --git a/src/mcp/tools/dataconnect/execute.ts b/src/mcp/tools/dataconnect/execute.ts index 6143c7df01f..07789dd7e70 100644 --- a/src/mcp/tools/dataconnect/execute.ts +++ b/src/mcp/tools/dataconnect/execute.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import { tool } from "../../tool"; import * as dataplane from "../../../dataconnect/dataplaneClient"; -import { pickService } from "../../../dataconnect/load"; +import { pickOneService, pickServices } from "../../../dataconnect/load"; import { graphqlResponseToToolResponse, parseVariables } from "../../util/dataconnect/converter"; import { getDataConnectEmulatorClient } from "../../util/dataconnect/emulator"; import { Client } from "../../../apiv2"; @@ -18,10 +18,15 @@ You can use the \`dataconnect_generate_operation\` tool to generate a query. Example Data Connect schema and example queries can be found in files ending in \`.graphql\` or \`.gql\`. `), service_id: z.string().optional() - .describe(`Data Connect Service ID to dis-ambulate if there are multiple. -It's only necessary if there are multiple dataconnect sources in \`firebase.json\`. -You can find candidate service_id in \`dataconnect.yaml\` -`), + .describe( + `Data Connect Service ID to dis-ambulate if there are multiple Data Connect services.`, + ), + location_id: z + .string() + .optional() + .describe( + `Data Connect Service location ID to dis-ambulate among multiple Data Connect services.`, + ), variables_json: z .string() .optional() @@ -55,13 +60,19 @@ You can find candidate service_id in \`dataconnect.yaml\` { query, service_id, + location_id, variables_json: unparsedVariables, use_emulator, auth_token_json: unparsedAuthToken, }, { projectId, config, host }, ) => { - const serviceInfo = await pickService(projectId, config, service_id || undefined); + const serviceInfo = await pickOneService( + projectId, + config, + service_id || undefined, + location_id || undefined, + ); let apiClient: Client; if (use_emulator) { apiClient = await getDataConnectEmulatorClient(host); diff --git a/src/mcp/tools/dataconnect/generate_operation.ts b/src/mcp/tools/dataconnect/generate_operation.ts index 731afc7adc7..d39e573b9d9 100644 --- a/src/mcp/tools/dataconnect/generate_operation.ts +++ b/src/mcp/tools/dataconnect/generate_operation.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import { tool } from "../../tool"; import { toContent } from "../../util"; import { generateOperation } from "../../../gemini/fdcExperience"; -import { pickService } from "../../../dataconnect/load"; +import { pickOneService } from "../../../dataconnect/load"; export const generate_operation = tool( { @@ -16,11 +16,15 @@ export const generate_operation = tool( .describe( "Write the prompt like you're talking to a person, describe the task you're trying to accomplish and give details that are specific to the users request", ), - service_id: z + service_id: z.string().optional() + .describe( + `Data Connect Service ID to dis-ambulate if there are multiple Data Connect services.`, + ), + location_id: z .string() .optional() .describe( - "Optional: Uses the service ID from the firebase.json file if nothing provided. The service ID of the deployed Firebase resource.", + `Data Connect Service location ID to dis-ambulate among multiple Data Connect services.`, ), }), annotations: { @@ -33,8 +37,13 @@ export const generate_operation = tool( requiresGemini: true, }, }, - async ({ prompt, service_id }, { projectId, config }) => { - const serviceInfo = await pickService(projectId, config, service_id || undefined); + async ({ prompt, service_id, location_id }, { projectId, config }) => { + const serviceInfo = await pickOneService( + projectId, + config, + service_id || undefined, + location_id || undefined, + ); const schema = await generateOperation(prompt, serviceInfo.serviceName, projectId); return toContent(schema); }, From 3d80375c4d08ac970cb08d335e35e5f1b2449833 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Mon, 13 Oct 2025 16:10:44 -0700 Subject: [PATCH 04/16] format --- src/commands/dataconnect-sdk-generate.ts | 7 ++++- src/commands/dataconnect-sql-diff.ts | 9 +++++-- src/commands/dataconnect-sql-grant.ts | 7 ++++- src/commands/dataconnect-sql-migrate.ts | 7 ++++- src/commands/dataconnect-sql-setup.ts | 4 +-- src/commands/dataconnect-sql-shell.ts | 9 +++++-- src/dataconnect/load.ts | 7 ++++- src/mcp/tools/dataconnect/compile.ts | 27 +++++++++++++------ src/mcp/tools/dataconnect/execute.ts | 6 +++-- .../tools/dataconnect/generate_operation.ts | 4 ++- 10 files changed, 66 insertions(+), 21 deletions(-) diff --git a/src/commands/dataconnect-sdk-generate.ts b/src/commands/dataconnect-sdk-generate.ts index e3cf03c309e..204f25c0f09 100644 --- a/src/commands/dataconnect-sdk-generate.ts +++ b/src/commands/dataconnect-sdk-generate.ts @@ -25,7 +25,12 @@ export const command = new Command("dataconnect:sdk:generate") ) .action(async (options: GenerateOptions) => { const projectId = needProjectId(options); - const serviceInfos = await pickServices(projectId, options.config, options.service, options.location); + const serviceInfos = await pickServices( + projectId, + options.config, + options.service, + options.location, + ); const serviceInfosWithSDKs = serviceInfos.filter((serviceInfo) => serviceInfo.connectorInfo.some((c) => { return ( diff --git a/src/commands/dataconnect-sql-diff.ts b/src/commands/dataconnect-sql-diff.ts index b603533033c..fe8b0b66e4b 100644 --- a/src/commands/dataconnect-sql-diff.ts +++ b/src/commands/dataconnect-sql-diff.ts @@ -22,12 +22,17 @@ export const command = new Command("dataconnect:sql:diff") .action(async (options: Options) => { const projectId = needProjectId(options); await ensureApis(projectId); - const serviceInfo = await pickOneService(projectId, options.config, options.service, options.location); + const serviceInfo = await pickOneService( + projectId, + options.config, + options.service as string | undefined, + options.location as string | undefined, + ); const diffs = await diffSchema( options, serviceInfo.schema, serviceInfo.dataConnectYaml.schema.datasource.postgresql?.schemaValidation, ); - return {projectId, diffs }; + return { projectId, diffs }; }); diff --git a/src/commands/dataconnect-sql-grant.ts b/src/commands/dataconnect-sql-grant.ts index f80d8003ffa..9ba2cacbcfc 100644 --- a/src/commands/dataconnect-sql-grant.ts +++ b/src/commands/dataconnect-sql-grant.ts @@ -51,7 +51,12 @@ export const command = new Command("dataconnect:sql:grant") const projectId = needProjectId(options); await ensureApis(projectId); - const serviceInfo = await pickOneService(projectId, options.config, options.service, options.location); + const serviceInfo = await pickOneService( + projectId, + options.config, + options.service as string | undefined, + options.location as string | undefined, + ); await grantRoleToUserInSchema(options, serviceInfo.schema); return { projectId }; diff --git a/src/commands/dataconnect-sql-migrate.ts b/src/commands/dataconnect-sql-migrate.ts index 133b729dcd2..31f3c68c019 100644 --- a/src/commands/dataconnect-sql-migrate.ts +++ b/src/commands/dataconnect-sql-migrate.ts @@ -24,7 +24,12 @@ export const command = new Command("dataconnect:sql:migrate") .action(async (options: Options) => { const projectId = needProjectId(options); await ensureApis(projectId); - const serviceInfo = await pickOneService(projectId, options.config, options.service, options.location); + const serviceInfo = await pickOneService( + projectId, + options.config, + options.service as string | undefined, + options.location as string | undefined, + ); const instanceId = serviceInfo.dataConnectYaml.schema.datasource.postgresql?.cloudSql.instanceId; if (!instanceId) { diff --git a/src/commands/dataconnect-sql-setup.ts b/src/commands/dataconnect-sql-setup.ts index aed54ae1b38..2134b6bb702 100644 --- a/src/commands/dataconnect-sql-setup.ts +++ b/src/commands/dataconnect-sql-setup.ts @@ -28,8 +28,8 @@ export const command = new Command("dataconnect:sql:setup") const serviceInfo = await pickOneService( projectId, options.config, - options.service, - options.location, + options.service as string | undefined, + options.location as string | undefined, ); const instanceId = serviceInfo.dataConnectYaml.schema.datasource.postgresql?.cloudSql.instanceId; diff --git a/src/commands/dataconnect-sql-shell.ts b/src/commands/dataconnect-sql-shell.ts index cfc17349f00..1e9880785ae 100644 --- a/src/commands/dataconnect-sql-shell.ts +++ b/src/commands/dataconnect-sql-shell.ts @@ -7,7 +7,7 @@ import { Options } from "../options"; import { needProjectId } from "../projectUtils"; import { ensureApis } from "../dataconnect/ensureApis"; import { requirePermissions } from "../requirePermissions"; -import { pickOneService } from "../dataconnect/load"; +import { pickOneService } from "../dataconnect/load"; import { getIdentifiers } from "../dataconnect/schemaMigration"; import { requireAuth } from "../requireAuth"; import { getIAMUser } from "../gcp/cloudsql/connect"; @@ -92,7 +92,12 @@ export const command = new Command("dataconnect:sql:shell") .action(async (options: Options) => { const projectId = needProjectId(options); await ensureApis(projectId); - const serviceInfo = await pickOneService(projectId, options.config, options.service, options.location); + const serviceInfo = await pickOneService( + projectId, + options.config, + options.service as string | undefined, + options.location as string | undefined, + ); const { instanceId, databaseId } = getIdentifiers(serviceInfo.schema); const { user: username } = await getIAMUser(options); const instance = await cloudSqlAdminClient.getInstance(projectId, instanceId); diff --git a/src/dataconnect/load.ts b/src/dataconnect/load.ts index 605b08f5739..3b0fce825cc 100644 --- a/src/dataconnect/load.ts +++ b/src/dataconnect/load.ts @@ -15,7 +15,12 @@ import { import { readFileFromDirectory, wrappedSafeLoad } from "../utils"; import { DataConnectMultiple } from "../firebaseConfig"; -export async function pickOneService(projectId: string, config: Config, service?: string, location?: string): Promise { +export async function pickOneService( + projectId: string, + config: Config, + service?: string, + location?: string, +): Promise { const services = await pickServices(projectId, config, service, location); if (services.length > 1) { const serviceIds = services.map( diff --git a/src/mcp/tools/dataconnect/compile.ts b/src/mcp/tools/dataconnect/compile.ts index d5d02440eed..bb8a8fddee3 100644 --- a/src/mcp/tools/dataconnect/compile.ts +++ b/src/mcp/tools/dataconnect/compile.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { tool } from "../../tool"; import { compileErrors } from "../../util/dataconnect/compile"; -import { pickOneService, pickServices } from "../../../dataconnect/load"; +import { pickServices } from "../../../dataconnect/load"; export const compile = tool( { @@ -13,7 +13,9 @@ export const compile = tool( .enum(["all", "schema", "operations"]) .describe("filter errors to a specific type only. defaults to `all` if omitted.") .optional(), - service_id: z.string().optional() + service_id: z + .string() + .optional() .describe( `Data Connect Service ID to dis-ambulate if there are multiple Data Connect services.`, ), @@ -34,16 +36,25 @@ export const compile = tool( }, }, async ({ service_id, location_id, error_filter }, { projectId, config }) => { - const serviceInfos = await pickServices(projectId, config, service_id || undefined, location_id || undefined); - const errors = await Promise.all(serviceInfos.map(async (serviceInfo) => { - return await compileErrors(serviceInfo.sourceDirectory, error_filter); - })); - if (errors) + const serviceInfos = await pickServices( + projectId, + config, + service_id || undefined, + location_id || undefined, + ); + const errors = ( + await Promise.all( + serviceInfos.map(async (serviceInfo) => { + return await compileErrors(serviceInfo.sourceDirectory, error_filter); + }), + ) + ).flat(); + if (errors.length > 0) return { content: [ { type: "text", - text: `The following errors were encountered while compiling Data Connect from directory \`${serviceInfo.sourceDirectory}\`:\n\n${errors}`, + text: `The following errors were encountered while compiling Data Connect:\n\n${errors.join("\n")}`, }, ], isError: true, diff --git a/src/mcp/tools/dataconnect/execute.ts b/src/mcp/tools/dataconnect/execute.ts index 07789dd7e70..8d84b277f78 100644 --- a/src/mcp/tools/dataconnect/execute.ts +++ b/src/mcp/tools/dataconnect/execute.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import { tool } from "../../tool"; import * as dataplane from "../../../dataconnect/dataplaneClient"; -import { pickOneService, pickServices } from "../../../dataconnect/load"; +import { pickOneService } from "../../../dataconnect/load"; import { graphqlResponseToToolResponse, parseVariables } from "../../util/dataconnect/converter"; import { getDataConnectEmulatorClient } from "../../util/dataconnect/emulator"; import { Client } from "../../../apiv2"; @@ -17,7 +17,9 @@ export const execute = tool( You can use the \`dataconnect_generate_operation\` tool to generate a query. Example Data Connect schema and example queries can be found in files ending in \`.graphql\` or \`.gql\`. `), - service_id: z.string().optional() + service_id: z + .string() + .optional() .describe( `Data Connect Service ID to dis-ambulate if there are multiple Data Connect services.`, ), diff --git a/src/mcp/tools/dataconnect/generate_operation.ts b/src/mcp/tools/dataconnect/generate_operation.ts index d39e573b9d9..2a9fcdbb61c 100644 --- a/src/mcp/tools/dataconnect/generate_operation.ts +++ b/src/mcp/tools/dataconnect/generate_operation.ts @@ -16,7 +16,9 @@ export const generate_operation = tool( .describe( "Write the prompt like you're talking to a person, describe the task you're trying to accomplish and give details that are specific to the users request", ), - service_id: z.string().optional() + service_id: z + .string() + .optional() .describe( `Data Connect Service ID to dis-ambulate if there are multiple Data Connect services.`, ), From 4590b18ef762cef32dd3f5ee412048d057cfc034 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Mon, 13 Oct 2025 16:13:53 -0700 Subject: [PATCH 05/16] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ffa9bd0696..e9ba0ca7610 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Add a confirmation in `firebase init dataconnect` before asking for app idea description. (#9282) +- Update dataconnect:* commands to use flags for --service & --location (#9312) From 12e4127f57fedfc1401d2dcc794b9a6c5185f541 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Mon, 13 Oct 2025 16:57:57 -0700 Subject: [PATCH 06/16] feedbacks --- src/commands/dataconnect-sql-diff.ts | 8 +++-- src/commands/dataconnect-sql-grant.ts | 33 +++++++++++-------- src/commands/dataconnect-sql-migrate.ts | 8 +++-- src/commands/dataconnect-sql-setup.ts | 8 +++-- src/commands/dataconnect-sql-shell.ts | 8 +++-- src/dataconnect/load.ts | 13 +++----- src/mcp/tools/dataconnect/compile.ts | 4 +-- src/mcp/tools/dataconnect/execute.ts | 4 +-- .../tools/dataconnect/generate_operation.ts | 4 +-- 9 files changed, 49 insertions(+), 41 deletions(-) diff --git a/src/commands/dataconnect-sql-diff.ts b/src/commands/dataconnect-sql-diff.ts index fe8b0b66e4b..de12631d884 100644 --- a/src/commands/dataconnect-sql-diff.ts +++ b/src/commands/dataconnect-sql-diff.ts @@ -7,6 +7,8 @@ import { pickOneService } from "../dataconnect/load"; import { diffSchema } from "../dataconnect/schemaMigration"; import { requireAuth } from "../requireAuth"; +type DiffOptions = Options & { service?: string; location?: string }; + export const command = new Command("dataconnect:sql:diff") .description( "display the differences between the local Data Connect schema and your CloudSQL database's schema", @@ -19,14 +21,14 @@ export const command = new Command("dataconnect:sql:diff") "firebasedataconnect.schemas.update", ]) .before(requireAuth) - .action(async (options: Options) => { + .action(async (options: DiffOptions) => { const projectId = needProjectId(options); await ensureApis(projectId); const serviceInfo = await pickOneService( projectId, options.config, - options.service as string | undefined, - options.location as string | undefined, + options.service, + options.location, ); const diffs = await diffSchema( diff --git a/src/commands/dataconnect-sql-grant.ts b/src/commands/dataconnect-sql-grant.ts index 9ba2cacbcfc..808fcd1ca9e 100644 --- a/src/commands/dataconnect-sql-grant.ts +++ b/src/commands/dataconnect-sql-grant.ts @@ -12,6 +12,13 @@ import { iamUserIsCSQLAdmin } from "../gcp/cloudsql/cloudsqladmin"; const allowedRoles = Object.keys(fdcSqlRoleMap); +type GrantOptions = Options & { + role?: string; + email?: string; + service?: string; + location?: string; +}; + export const command = new Command("dataconnect:sql:grant") .description("grants the SQL role to the provided user or service account ") .option("-R, --role ", "The SQL role to grant. One of: owner, writer, or reader.") @@ -23,32 +30,22 @@ export const command = new Command("dataconnect:sql:grant") .option("--location ", "the location of the Data Connect service to disambiguate") .before(requirePermissions, ["firebasedataconnect.services.list"]) .before(requireAuth) - .action(async (options: Options) => { - const role = options.role as string; - const email = options.email as string; - if (!role) { + .action(async (options: GrantOptions) => { + if (!options.role) { throw new FirebaseError( "-R, --role is required. Run the command with -h for more info.", ); } - if (!email) { + if (!options.email) { throw new FirebaseError( "-E, --email is required. Run the command with -h for more info.", ); } - if (!allowedRoles.includes(role.toLowerCase())) { + if (!allowedRoles.includes(options.role.toLowerCase())) { throw new FirebaseError(`Role should be one of ${allowedRoles.join(" | ")}.`); } - // Make sure current user can perform this action. - const userIsCSQLAdmin = await iamUserIsCSQLAdmin(options); - if (!userIsCSQLAdmin) { - throw new FirebaseError( - `Only users with 'roles/cloudsql.admin' can grant SQL roles. If you do not have this role, ask your database administrator to run this command or manually grant ${role} to ${email}`, - ); - } - const projectId = needProjectId(options); await ensureApis(projectId); const serviceInfo = await pickOneService( @@ -58,6 +55,14 @@ export const command = new Command("dataconnect:sql:grant") options.location as string | undefined, ); + // Make sure current user can perform this action. + const userIsCSQLAdmin = await iamUserIsCSQLAdmin(options); + if (!userIsCSQLAdmin) { + throw new FirebaseError( + `Only users with 'roles/cloudsql.admin' can grant SQL roles. If you do not have this role, ask your database administrator to run this command or manually grant ${options.role} to ${options.email}`, + ); + } + await grantRoleToUserInSchema(options, serviceInfo.schema); return { projectId }; }); diff --git a/src/commands/dataconnect-sql-migrate.ts b/src/commands/dataconnect-sql-migrate.ts index 31f3c68c019..636b89d4988 100644 --- a/src/commands/dataconnect-sql-migrate.ts +++ b/src/commands/dataconnect-sql-migrate.ts @@ -9,6 +9,8 @@ import { requirePermissions } from "../requirePermissions"; import { ensureApis } from "../dataconnect/ensureApis"; import { logLabeledSuccess } from "../utils"; +type MigrateOptions = Options & { service?: string; location?: string }; + export const command = new Command("dataconnect:sql:migrate") .description("migrate your CloudSQL database's schema to match your local Data Connect schema") .option("--service ", "the serviceId of the Data Connect service") @@ -21,14 +23,14 @@ export const command = new Command("dataconnect:sql:migrate") ]) .before(requireAuth) .withForce("execute any required database changes without prompting") - .action(async (options: Options) => { + .action(async (options: MigrateOptions) => { const projectId = needProjectId(options); await ensureApis(projectId); const serviceInfo = await pickOneService( projectId, options.config, - options.service as string | undefined, - options.location as string | undefined, + options.service, + options.location, ); const instanceId = serviceInfo.dataConnectYaml.schema.datasource.postgresql?.cloudSql.instanceId; diff --git a/src/commands/dataconnect-sql-setup.ts b/src/commands/dataconnect-sql-setup.ts index 2134b6bb702..4627e997b9c 100644 --- a/src/commands/dataconnect-sql-setup.ts +++ b/src/commands/dataconnect-sql-setup.ts @@ -11,6 +11,8 @@ import { getIdentifiers, ensureServiceIsConnectedToCloudSql } from "../dataconne import { setupIAMUsers } from "../gcp/cloudsql/connect"; import { pickOneService } from "../dataconnect/load"; +type SetupOptions = Options & { service?: string; location?: string }; + export const command = new Command("dataconnect:sql:setup") .description("set up your CloudSQL database") .option("--service ", "the serviceId of the Data Connect service") @@ -22,14 +24,14 @@ export const command = new Command("dataconnect:sql:setup") "cloudsql.instances.connect", ]) .before(requireAuth) - .action(async (options: Options) => { + .action(async (options: SetupOptions) => { const projectId = needProjectId(options); await ensureApis(projectId); const serviceInfo = await pickOneService( projectId, options.config, - options.service as string | undefined, - options.location as string | undefined, + options.service, + options.location, ); const instanceId = serviceInfo.dataConnectYaml.schema.datasource.postgresql?.cloudSql.instanceId; diff --git a/src/commands/dataconnect-sql-shell.ts b/src/commands/dataconnect-sql-shell.ts index 1e9880785ae..85eb716ed21 100644 --- a/src/commands/dataconnect-sql-shell.ts +++ b/src/commands/dataconnect-sql-shell.ts @@ -81,6 +81,8 @@ async function mainShellLoop(conn: pg.PoolClient) { } } +type ShellOptions = Options & { service?: string; location?: string }; + export const command = new Command("dataconnect:sql:shell") .description( "start a shell connected directly to your Data Connect service's linked CloudSQL instance", @@ -89,14 +91,14 @@ export const command = new Command("dataconnect:sql:shell") .option("--location ", "the location of the Data Connect service to disambiguate") .before(requirePermissions, ["firebasedataconnect.services.list", "cloudsql.instances.connect"]) .before(requireAuth) - .action(async (options: Options) => { + .action(async (options: ShellOptions) => { const projectId = needProjectId(options); await ensureApis(projectId); const serviceInfo = await pickOneService( projectId, options.config, - options.service as string | undefined, - options.location as string | undefined, + options.service, + options.location, ); const { instanceId, databaseId } = getIdentifiers(serviceInfo.schema); const { user: username } = await getIAMUser(options); diff --git a/src/dataconnect/load.ts b/src/dataconnect/load.ts index 3b0fce825cc..33671e79caa 100644 --- a/src/dataconnect/load.ts +++ b/src/dataconnect/load.ts @@ -15,6 +15,7 @@ import { import { readFileFromDirectory, wrappedSafeLoad } from "../utils"; import { DataConnectMultiple } from "../firebaseConfig"; +/** Picks exactly one Data Connect service based on flags. */ export async function pickOneService( projectId: string, config: Config, @@ -35,8 +36,7 @@ export async function pickOneService( return services[0]; } -// pickService reads firebase.json and returns a service with a given serviceId and location. -// If serviceID is not provided and there is a single service, return that. +/** Picks Data Connect services based on flags. */ export async function pickServices( projectId: string, config: Config, @@ -45,12 +45,8 @@ export async function pickServices( ): Promise { const serviceInfos = await loadAll(projectId, config); if (serviceInfos.length === 0) { - let message = "No Data Connect services found in firebase.json."; - if (location) { - message = `No Data Connect services for location ${location} found in firebase.json.`; - } throw new FirebaseError( - message + + "No Data Connect services found in firebase.json." + `\nYou can run ${clc.bold("firebase init dataconnect")} to add a Data Connect service.`, ); } @@ -65,8 +61,7 @@ export async function pickServices( (i) => `${i.dataConnectYaml.location}:${i.dataConnectYaml.serviceId}`, ); throw new FirebaseError( - `No service matched service in firebase.json. Available services: ${serviceIds.join(", ")}` + - `\nYou can run ${clc.bold("firebase init dataconnect")} to add this Data Connect service.`, + `No service matched service in firebase.json. Available services: ${serviceIds.join(", ")}`, ); } return matchingServices; diff --git a/src/mcp/tools/dataconnect/compile.ts b/src/mcp/tools/dataconnect/compile.ts index bb8a8fddee3..bec43f68357 100644 --- a/src/mcp/tools/dataconnect/compile.ts +++ b/src/mcp/tools/dataconnect/compile.ts @@ -17,13 +17,13 @@ export const compile = tool( .string() .optional() .describe( - `Data Connect Service ID to dis-ambulate if there are multiple Data Connect services.`, + `Data Connect Service ID to disambiguate if there are multiple Data Connect services.`, ), location_id: z .string() .optional() .describe( - `Data Connect Service location ID to dis-ambulate among multiple Data Connect services.`, + `Data Connect Service location ID to disambiguate among multiple Data Connect services.`, ), }), annotations: { diff --git a/src/mcp/tools/dataconnect/execute.ts b/src/mcp/tools/dataconnect/execute.ts index 8d84b277f78..b541a0b64f9 100644 --- a/src/mcp/tools/dataconnect/execute.ts +++ b/src/mcp/tools/dataconnect/execute.ts @@ -21,13 +21,13 @@ Example Data Connect schema and example queries can be found in files ending in .string() .optional() .describe( - `Data Connect Service ID to dis-ambulate if there are multiple Data Connect services.`, + `Data Connect Service ID to disambiguate if there are multiple Data Connect services.`, ), location_id: z .string() .optional() .describe( - `Data Connect Service location ID to dis-ambulate among multiple Data Connect services.`, + `Data Connect Service location ID to disambiguate among multiple Data Connect services.`, ), variables_json: z .string() diff --git a/src/mcp/tools/dataconnect/generate_operation.ts b/src/mcp/tools/dataconnect/generate_operation.ts index 2a9fcdbb61c..309907e83a3 100644 --- a/src/mcp/tools/dataconnect/generate_operation.ts +++ b/src/mcp/tools/dataconnect/generate_operation.ts @@ -20,13 +20,13 @@ export const generate_operation = tool( .string() .optional() .describe( - `Data Connect Service ID to dis-ambulate if there are multiple Data Connect services.`, + `Data Connect Service ID to disambiguate if there are multiple Data Connect services.`, ), location_id: z .string() .optional() .describe( - `Data Connect Service location ID to dis-ambulate among multiple Data Connect services.`, + `Data Connect Service location ID to disambiguate among multiple Data Connect services.`, ), }), annotations: { From 13eebfc11810dd1476cd7be41179664e24fef4ac Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Mon, 13 Oct 2025 17:13:34 -0700 Subject: [PATCH 07/16] m --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9ba0ca7610..0dd9db9ca89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,2 @@ - Add a confirmation in `firebase init dataconnect` before asking for app idea description. (#9282) -- Update dataconnect:* commands to use flags for --service & --location (#9312) +- Update dataconnect:\* commands to use flags for --service & --location (#9312) From 7173d9104ebdd89a57d693b9e7663585dc5e86e4 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Mon, 1 Dec 2025 15:22:40 -0800 Subject: [PATCH 08/16] Fix merge conflicts and run build --- firebase-vscode/package-lock.json | 143 ------------------------------ 1 file changed, 143 deletions(-) diff --git a/firebase-vscode/package-lock.json b/firebase-vscode/package-lock.json index 515b478a397..9223814cc2b 100644 --- a/firebase-vscode/package-lock.json +++ b/firebase-vscode/package-lock.json @@ -3290,30 +3290,6 @@ "node": ">=18.20.0" } }, - "node_modules/@wdio/cli/node_modules/@puppeteer/browsers": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", - "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "debug": "^4.3.5", - "extract-zip": "^2.0.1", - "progress": "^2.0.3", - "proxy-agent": "^6.4.0", - "semver": "^7.6.3", - "tar-fs": "^3.0.6", - "unbzip2-stream": "^1.4.3", - "yargs": "^17.7.2" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@wdio/cli/node_modules/@wdio/repl": { "version": "9.0.8", "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.0.8.tgz", @@ -3347,30 +3323,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/cli/node_modules/chromium-bidi": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.3.tgz", - "integrity": "sha512-qXlsCmpCZJAnoTYI83Iu6EdYQpMYdVkCfq08KDh2pmlVqK5t5IA9mGs4/LwCwp4fqisSOMXZxP3HIh8w8aRn0A==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "mitt": "3.0.1", - "urlpattern-polyfill": "10.0.0", - "zod": "3.23.8" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/@wdio/cli/node_modules/devtools-protocol": { - "version": "0.0.1312386", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", - "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/@wdio/cli/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -3386,24 +3338,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/cli/node_modules/puppeteer-core": { - "version": "22.15.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", - "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@puppeteer/browsers": "2.3.0", - "chromium-bidi": "0.6.3", - "debug": "^4.3.6", - "devtools-protocol": "0.0.1312386", - "ws": "^8.18.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@wdio/cli/node_modules/webdriver": { "version": "9.2.5", "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.2.5.tgz", @@ -3545,30 +3479,6 @@ "webdriverio": "9.2.6" } }, - "node_modules/@wdio/globals/node_modules/@puppeteer/browsers": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", - "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "debug": "^4.3.5", - "extract-zip": "^2.0.1", - "progress": "^2.0.3", - "proxy-agent": "^6.4.0", - "semver": "^7.6.3", - "tar-fs": "^3.0.6", - "unbzip2-stream": "^1.4.3", - "yargs": "^17.7.2" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@wdio/globals/node_modules/@wdio/repl": { "version": "9.0.8", "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.0.8.tgz", @@ -3592,30 +3502,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/@wdio/globals/node_modules/chromium-bidi": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.3.tgz", - "integrity": "sha512-qXlsCmpCZJAnoTYI83Iu6EdYQpMYdVkCfq08KDh2pmlVqK5t5IA9mGs4/LwCwp4fqisSOMXZxP3HIh8w8aRn0A==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "mitt": "3.0.1", - "urlpattern-polyfill": "10.0.0", - "zod": "3.23.8" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/@wdio/globals/node_modules/devtools-protocol": { - "version": "0.0.1312386", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", - "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/@wdio/globals/node_modules/expect-webdriverio": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.0.3.tgz", @@ -3664,24 +3550,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/globals/node_modules/puppeteer-core": { - "version": "22.15.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", - "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@puppeteer/browsers": "2.3.0", - "chromium-bidi": "0.6.3", - "debug": "^4.3.6", - "devtools-protocol": "0.0.1312386", - "ws": "^8.18.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@wdio/globals/node_modules/webdriver": { "version": "9.2.5", "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.2.5.tgz", @@ -17727,17 +17595,6 @@ "dependencies": { "safe-buffer": "~5.2.0" } - }, - "node_modules/zod": { - "version": "3.23.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", - "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", - "dev": true, - "optional": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } } } } From 2d31e3f904ce7bbd57eb238824e2c45d838cdc4d Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Mon, 1 Dec 2025 16:33:42 -0800 Subject: [PATCH 09/16] m --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c712066ed0..c381ec3a12b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,4 +4,4 @@ - [BREAKING] Removed support for running emulators with Java versions prior to 21. - Add a confirmation in `firebase init dataconnect` before asking for app idea description. (#9282) - [BREAKING] Removed deprecated `firebase --open-sesame` and `firebase --close-sesame` commands. Use `firebase experiments:enable` and `firebase experiments:disable` instead. -- Update dataconnect:* commands to use flags for --service & --location (#9312) +- Update dataconnect:\* commands to use flags for --service & --location (#9312) From 99c130a1b3a73fb5e0999a27d888577d8c78867d Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Mon, 1 Dec 2025 16:57:44 -0800 Subject: [PATCH 10/16] sdk-gen --- src/commands/dataconnect-sdk-generate.ts | 67 ++++++++++-------------- 1 file changed, 27 insertions(+), 40 deletions(-) diff --git a/src/commands/dataconnect-sdk-generate.ts b/src/commands/dataconnect-sdk-generate.ts index cc18d8c17c6..c9d7e00d787 100644 --- a/src/commands/dataconnect-sdk-generate.ts +++ b/src/commands/dataconnect-sdk-generate.ts @@ -3,7 +3,7 @@ import * as clc from "colorette"; import { Command } from "../command"; import { Options } from "../options"; import { DataConnectEmulator } from "../emulator/dataconnectEmulator"; -import { getProjectId, needProjectId } from "../projectUtils"; +import { getProjectId } from "../projectUtils"; import { pickServices } from "../dataconnect/load"; import { getProjectDefaultAccount } from "../auth"; import { logBullet, logLabeledSuccess, logWarning } from "../utils"; @@ -14,6 +14,7 @@ import * as dataconnectInit from "../init/features/dataconnect"; import * as dataconnectSdkInit from "../init/features/dataconnect/sdk"; import { FirebaseError } from "../error"; import { postInitSaves } from "./init"; +import { EmulatorHub } from "../emulator/hub"; type GenerateOptions = Options & { watch?: boolean; service?: string; location?: string }; @@ -36,9 +37,7 @@ export const command = new Command("dataconnect:sdk:generate") if (!config || !config.has("dataconnect")) { if (options.nonInteractive) { throw new FirebaseError( - `No dataconnect project directory found. Please run ${clc.bold( - "firebase init dataconnect", - )} to set it up first.`, + `No dataconnect project directory found. Please run ${clc.bold("firebase init dataconnect")} to set it up first.`, ); } logWarning("No dataconnect project directory found."); @@ -65,29 +64,11 @@ export const command = new Command("dataconnect:sdk:generate") options.config = config; } - const serviceInfos = await pickServices( - needProjectId(options), - options.config, - options.service, - options.location, - ); - let serviceInfosWithSDKs = serviceInfos.filter((serviceInfo) => - serviceInfo.connectorInfo.some((c) => { - return ( - c.connectorYaml.generate?.javascriptSdk || - c.connectorYaml.generate?.kotlinSdk || - c.connectorYaml.generate?.swiftSdk || - c.connectorYaml.generate?.dartSdk - ); - }), - ); - + let serviceInfosWithSDKs = await loadAllWithSDKs(projectId, config); if (!serviceInfosWithSDKs.length) { if (justRanInit || options.nonInteractive) { throw new FirebaseError( - `No generated SDKs are configured during init. Please run ${clc.bold( - "firebase init dataconnect:sdk", - )} to configure a generated SDK.`, + `No generated SDKs are configured during init. Please run ${clc.bold("firebase init dataconnect:sdk")} to configure a generated SDK.`, ); } logWarning("No generated SDKs have been configured."); @@ -106,27 +87,33 @@ export const command = new Command("dataconnect:sdk:generate") await dataconnectSdkInit.askQuestions(setup); await dataconnectSdkInit.actuate(setup, config); justRanInit = true; - const newServiceInfos = await pickServices( - needProjectId(options), - options.config, - options.service, - options.location, - ); - serviceInfosWithSDKs = newServiceInfos.filter((serviceInfo) => - serviceInfo.connectorInfo.some((c) => { - return ( - c.connectorYaml.generate?.javascriptSdk || - c.connectorYaml.generate?.kotlinSdk || - c.connectorYaml.generate?.swiftSdk || - c.connectorYaml.generate?.dartSdk - ); - }), - ); + serviceInfosWithSDKs = await loadAllWithSDKs(projectId, config); } await generateSDKsInAll(options, serviceInfosWithSDKs, justRanInit); }); +async function loadAllWithSDKs( + projectId: string | undefined, + config: Config, +): Promise { + const serviceInfos = await pickServices( + projectId || EmulatorHub.MISSING_PROJECT_PLACEHOLDER, + config, + ); + return serviceInfos.filter((serviceInfo) => + serviceInfo.connectorInfo.some((c) => { + return ( + c.connectorYaml.generate?.javascriptSdk || + c.connectorYaml.generate?.kotlinSdk || + c.connectorYaml.generate?.swiftSdk || + c.connectorYaml.generate?.dartSdk || + c.connectorYaml.generate?.adminNodeSdk + ); + }), + ); +} + async function generateSDKsInAll( options: GenerateOptions, serviceInfosWithSDKs: ServiceInfo[], From 5d593e638e6121674f4bae53faae58c4ef61414b Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Mon, 1 Dec 2025 16:58:32 -0800 Subject: [PATCH 11/16] m --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c381ec3a12b..4a859edf055 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,4 +4,4 @@ - [BREAKING] Removed support for running emulators with Java versions prior to 21. - Add a confirmation in `firebase init dataconnect` before asking for app idea description. (#9282) - [BREAKING] Removed deprecated `firebase --open-sesame` and `firebase --close-sesame` commands. Use `firebase experiments:enable` and `firebase experiments:disable` instead. -- Update dataconnect:\* commands to use flags for --service & --location (#9312) +- [BREAKING] Update dataconnect:\* commands to use flags instead of positional arguments for --service & --location (#9312) From 16448b58d4e2a4f5f39773aa7e495f148499b808 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Tue, 2 Dec 2025 11:58:29 -0800 Subject: [PATCH 12/16] Update src/commands/dataconnect-execute.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/commands/dataconnect-execute.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/dataconnect-execute.ts b/src/commands/dataconnect-execute.ts index 21844f29da0..91bd01aab9f 100644 --- a/src/commands/dataconnect-execute.ts +++ b/src/commands/dataconnect-execute.ts @@ -212,7 +212,7 @@ export const command = new Command("dataconnect:execute [file] [operationName]") options.config, serviceId || undefined, locationId || undefined, - ).catch((e: any) => { + ).catch((e: unknown) => { if (!(e instanceof FirebaseError)) { return Promise.reject(e); } From ef679c582f802f83440ca15a2991d9b5c9f8c912 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Tue, 2 Dec 2025 11:59:57 -0800 Subject: [PATCH 13/16] Update src/commands/dataconnect-sql-grant.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/commands/dataconnect-sql-grant.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/dataconnect-sql-grant.ts b/src/commands/dataconnect-sql-grant.ts index 76c790d55ff..4216c317807 100644 --- a/src/commands/dataconnect-sql-grant.ts +++ b/src/commands/dataconnect-sql-grant.ts @@ -52,8 +52,8 @@ export const command = new Command("dataconnect:sql:grant") const serviceInfo = await pickOneService( projectId, options.config, - options.service as string | undefined, - options.location as string | undefined, + options.service, + options.location, ); // Make sure current user can perform this action. From 37fcdc4f3be784e226a2dee4c4278c2dcce9f9bc Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Tue, 2 Dec 2025 12:00:09 -0800 Subject: [PATCH 14/16] Update CHANGELOG.md Co-authored-by: Joe Hanley --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a859edf055..56c476bfc67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,4 +4,4 @@ - [BREAKING] Removed support for running emulators with Java versions prior to 21. - Add a confirmation in `firebase init dataconnect` before asking for app idea description. (#9282) - [BREAKING] Removed deprecated `firebase --open-sesame` and `firebase --close-sesame` commands. Use `firebase experiments:enable` and `firebase experiments:disable` instead. -- [BREAKING] Update dataconnect:\* commands to use flags instead of positional arguments for --service & --location (#9312) +- [BREAKING] Update `dataconnect:\*` commands to use flags instead of positional arguments for `--service` & `--location`. Changed output type of `dataconnect:sql:migrate --json` (#9312) From dd618a7f0f37b09fc9350c0b76f837e17365d9f2 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Tue, 2 Dec 2025 12:00:23 -0800 Subject: [PATCH 15/16] Update src/mcp/tools/dataconnect/compile.ts Co-authored-by: Joe Hanley --- src/mcp/tools/dataconnect/compile.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mcp/tools/dataconnect/compile.ts b/src/mcp/tools/dataconnect/compile.ts index 8b45d035794..d0d9d8a1ed6 100644 --- a/src/mcp/tools/dataconnect/compile.ts +++ b/src/mcp/tools/dataconnect/compile.ts @@ -18,7 +18,7 @@ export const compile = tool( .string() .optional() .describe( - `Data Connect Service ID to disambiguate if there are multiple Data Connect services.`, + `Service ID of the Data Connect service to compile. Used to disambiguate when there are multiple Data Connect services in firebase.json.`, ), location_id: z .string() From 00e2832f209b566f52832e48c190113fd8f7412d Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Tue, 2 Dec 2025 12:05:35 -0800 Subject: [PATCH 16/16] all comments --- src/commands/dataconnect-sdk-generate.ts | 12 +++++++++--- src/commands/dataconnect-sql-diff.ts | 5 ++++- src/commands/dataconnect-sql-grant.ts | 5 ++++- src/commands/dataconnect-sql-migrate.ts | 5 ++++- src/commands/dataconnect-sql-setup.ts | 5 ++++- src/commands/dataconnect-sql-shell.ts | 5 ++++- src/mcp/tools/dataconnect/execute.ts | 2 +- src/mcp/tools/dataconnect/generate_operation.ts | 2 +- 8 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/commands/dataconnect-sdk-generate.ts b/src/commands/dataconnect-sdk-generate.ts index c9d7e00d787..4dfe9e02ef8 100644 --- a/src/commands/dataconnect-sdk-generate.ts +++ b/src/commands/dataconnect-sdk-generate.ts @@ -24,7 +24,10 @@ export const command = new Command("dataconnect:sdk:generate") "--service ", "the serviceId of the Data Connect service. If not provided, generates SDKs for all services.", ) - .option("--location ", "the location of the Data Connect service to disambiguate") + .option( + "--location ", + "the location of the Data Connect service. Only needed if service ID is used in multiple locations.", + ) .option( "--watch", "watch for changes to your connector GQL files and regenerate your SDKs when updates occur", @@ -64,7 +67,7 @@ export const command = new Command("dataconnect:sdk:generate") options.config = config; } - let serviceInfosWithSDKs = await loadAllWithSDKs(projectId, config); + let serviceInfosWithSDKs = await loadAllWithSDKs(projectId, config, options); if (!serviceInfosWithSDKs.length) { if (justRanInit || options.nonInteractive) { throw new FirebaseError( @@ -87,7 +90,7 @@ export const command = new Command("dataconnect:sdk:generate") await dataconnectSdkInit.askQuestions(setup); await dataconnectSdkInit.actuate(setup, config); justRanInit = true; - serviceInfosWithSDKs = await loadAllWithSDKs(projectId, config); + serviceInfosWithSDKs = await loadAllWithSDKs(projectId, config, options); } await generateSDKsInAll(options, serviceInfosWithSDKs, justRanInit); @@ -96,10 +99,13 @@ export const command = new Command("dataconnect:sdk:generate") async function loadAllWithSDKs( projectId: string | undefined, config: Config, + options: GenerateOptions, ): Promise { const serviceInfos = await pickServices( projectId || EmulatorHub.MISSING_PROJECT_PLACEHOLDER, config, + options.service, + options.location, ); return serviceInfos.filter((serviceInfo) => serviceInfo.connectorInfo.some((c) => { diff --git a/src/commands/dataconnect-sql-diff.ts b/src/commands/dataconnect-sql-diff.ts index 796efd8c098..87a9e2eeda7 100644 --- a/src/commands/dataconnect-sql-diff.ts +++ b/src/commands/dataconnect-sql-diff.ts @@ -15,7 +15,10 @@ export const command = new Command("dataconnect:sql:diff") "display the differences between the local Data Connect schema and your CloudSQL database's schema", ) .option("--service ", "the serviceId of the Data Connect service") - .option("--location ", "the location of the Data Connect service to disambiguate") + .option( + "--location ", + "the location of the Data Connect service. Only needed if service ID is used in multiple locations.", + ) .before(requirePermissions, [ "firebasedataconnect.services.list", "firebasedataconnect.schemas.list", diff --git a/src/commands/dataconnect-sql-grant.ts b/src/commands/dataconnect-sql-grant.ts index 4216c317807..e5f8bb9b143 100644 --- a/src/commands/dataconnect-sql-grant.ts +++ b/src/commands/dataconnect-sql-grant.ts @@ -28,7 +28,10 @@ export const command = new Command("dataconnect:sql:grant") "The email of the user or service account we would like to grant the role to.", ) .option("--service ", "the serviceId of the Data Connect service") - .option("--location ", "the location of the Data Connect service to disambiguate") + .option( + "--location ", + "the location of the Data Connect service. Only needed if service ID is used in multiple locations.", + ) .before(requirePermissions, ["firebasedataconnect.services.list"]) .before(requireAuth) .action(async (options: GrantOptions) => { diff --git a/src/commands/dataconnect-sql-migrate.ts b/src/commands/dataconnect-sql-migrate.ts index cb5f01cf561..f249e95c5fc 100644 --- a/src/commands/dataconnect-sql-migrate.ts +++ b/src/commands/dataconnect-sql-migrate.ts @@ -15,7 +15,10 @@ type MigrateOptions = Options & { service?: string; location?: string }; export const command = new Command("dataconnect:sql:migrate") .description("migrate your CloudSQL database's schema to match your local Data Connect schema") .option("--service ", "the serviceId of the Data Connect service") - .option("--location ", "the location of the Data Connect service to disambiguate") + .option( + "--location ", + "the location of the Data Connect service. Only needed if service ID is used in multiple locations.", + ) .before(requirePermissions, [ "firebasedataconnect.services.list", "firebasedataconnect.schemas.list", diff --git a/src/commands/dataconnect-sql-setup.ts b/src/commands/dataconnect-sql-setup.ts index 566b1dd6142..7548e90bd37 100644 --- a/src/commands/dataconnect-sql-setup.ts +++ b/src/commands/dataconnect-sql-setup.ts @@ -17,7 +17,10 @@ type SetupOptions = Options & { service?: string; location?: string }; export const command = new Command("dataconnect:sql:setup") .description("set up your CloudSQL database") .option("--service ", "the serviceId of the Data Connect service") - .option("--location ", "the location of the Data Connect service to disambiguate") + .option( + "--location ", + "the location of the Data Connect service. Only needed if service ID is used in multiple locations.", + ) .before(requirePermissions, [ "firebasedataconnect.services.list", "firebasedataconnect.schemas.list", diff --git a/src/commands/dataconnect-sql-shell.ts b/src/commands/dataconnect-sql-shell.ts index 358a00a436e..ff7516529ce 100644 --- a/src/commands/dataconnect-sql-shell.ts +++ b/src/commands/dataconnect-sql-shell.ts @@ -89,7 +89,10 @@ export const command = new Command("dataconnect:sql:shell") "start a shell connected directly to your Data Connect service's linked CloudSQL instance", ) .option("--service ", "the serviceId of the Data Connect service") - .option("--location ", "the location of the Data Connect service to disambiguate") + .option( + "--location ", + "the location of the Data Connect service. Only needed if service ID is used in multiple locations.", + ) .before(requirePermissions, ["firebasedataconnect.services.list", "cloudsql.instances.connect"]) .before(requireAuth) .action(async (options: ShellOptions) => { diff --git a/src/mcp/tools/dataconnect/execute.ts b/src/mcp/tools/dataconnect/execute.ts index a757f117f5c..1be67d502df 100644 --- a/src/mcp/tools/dataconnect/execute.ts +++ b/src/mcp/tools/dataconnect/execute.ts @@ -22,7 +22,7 @@ Example Data Connect schema and example queries can be found in files ending in .string() .optional() .describe( - `Data Connect Service ID to disambiguate if there are multiple Data Connect services.`, + `Service ID of the Data Connect service to compile. Used to disambiguate when there are multiple Data Connect services in firebase.json.`, ), location_id: z .string() diff --git a/src/mcp/tools/dataconnect/generate_operation.ts b/src/mcp/tools/dataconnect/generate_operation.ts index 602810521b6..00002b82a9d 100644 --- a/src/mcp/tools/dataconnect/generate_operation.ts +++ b/src/mcp/tools/dataconnect/generate_operation.ts @@ -21,7 +21,7 @@ export const generate_operation = tool( .string() .optional() .describe( - `Data Connect Service ID to disambiguate if there are multiple Data Connect services.`, + `Service ID of the Data Connect service to compile. Used to disambiguate when there are multiple Data Connect services in firebase.json.`, ), location_id: z .string()