Skip to content
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +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)
1 change: 0 additions & 1 deletion firebase-vscode/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ const defaultOptions: Readonly<VsCodeOptions> = {
projectNumber: "",
projectRoot: "",
account: "",
json: true,
nonInteractive: true,
interactive: false,
debug: false,
Expand Down
1 change: 0 additions & 1 deletion src/checkValidTargetFilters.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const SAMPLE_OPTIONS: Options = {
only: "",
except: "",
nonInteractive: false,
json: false,
interactive: false,
debug: false,
force: false,
Expand Down
1 change: 1 addition & 0 deletions src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ export class Command {
}

if (getInheritedOption(options, "json")) {
options.interactive = false;
options.nonInteractive = true;
} else if (!options.isMCP) {
useConsoleLoggers();
Expand Down
19 changes: 14 additions & 5 deletions src/commands/dataconnect-sdk-generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,33 @@ import { Command } from "../command";
import { Options } from "../options";
import { DataConnectEmulator } from "../emulator/dataconnectEmulator";
import { needProjectId } from "../projectUtils";
import { loadAll } 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 };
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 <serviceId>",
"the serviceId of the Data Connect service. If not provided, generates SDKs for all services.",
)
.option("--location <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",
)
.action(async (options: GenerateOptions) => {
const projectId = needProjectId(options);

const serviceInfos = await loadAll(projectId, options.config);
const serviceInfos = await pickServices(
projectId,
options.config,
options.service,
options.location,
);
const serviceInfosWithSDKs = serviceInfos.filter((serviceInfo) =>
serviceInfo.connectorInfo.some((c) => {
return (
Expand Down
19 changes: 13 additions & 6 deletions src/commands/dataconnect-sql-diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,36 @@ 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";

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",
"display the differences between the local Data Connect schema and your CloudSQL database's schema",
)
.option("--service <serviceId>", "the serviceId of the Data Connect service")
.option("--location <location>", "the location of the Data Connect service to disambiguate")
.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);
await ensureApis(projectId);
const serviceInfo = await pickService(projectId, options.config, serviceId);
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, serviceId, diffs };
return { projectId, diffs };
});
17 changes: 12 additions & 5 deletions src/commands/dataconnect-sql-grant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -12,16 +12,18 @@ 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 <role> to the provided user or service account <email>")
.option("-R, --role <role>", "The SQL role to grant. One of: owner, writer, or reader.")
.option(
"-E, --email <email>",
"The email of the user or service account we would like to grant the role to.",
)
.option("--service <serviceId>", "the serviceId of the Data Connect service")
.option("--location <location>", "the location of the Data Connect service to disambiguate")
.before(requirePermissions, ["firebasedataconnect.services.list"])
.before(requireAuth)
.action(async (serviceId: string, options: Options) => {
.action(async (options: Options) => {
const role = options.role as string;
const email = options.email as string;
if (!role) {
Expand Down Expand Up @@ -49,8 +51,13 @@ 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 pickOneService(
projectId,
options.config,
options.service as string | undefined,
options.location as string | undefined,
);

await grantRoleToUserInSchema(options, serviceInfo.schema);
return { projectId, serviceId };
return { projectId };
});
17 changes: 12 additions & 5 deletions src/commands/dataconnect-sql-migrate.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
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";
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 <serviceId>", "the serviceId of the Data Connect service")
.option("--location <location>", "the location of the Data Connect service to disambiguate")
.before(requirePermissions, [
"firebasedataconnect.services.list",
"firebasedataconnect.schemas.list",
Expand All @@ -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);
await ensureApis(projectId);
const serviceInfo = await pickService(projectId, options.config, serviceId);
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) {
Expand All @@ -44,5 +51,5 @@ export const command = new Command("dataconnect:sql:migrate [serviceId]")
} else {
logLabeledSuccess("dataconnect", "Database schema is already up to date!");
}
return { projectId, serviceId, diffs };
return { projectId, diffs };
});
15 changes: 11 additions & 4 deletions src/commands/dataconnect-sql-setup.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -10,20 +9,28 @@ 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 [serviceId]")
export const command = new Command("dataconnect:sql:setup")
.description("set up your CloudSQL database")
.option("--service <serviceId>", "the serviceId of the Data Connect service")
.option("--location <location>", "the location of the Data Connect service to disambiguate")
.before(requirePermissions, [
"firebasedataconnect.services.list",
"firebasedataconnect.schemas.list",
"firebasedataconnect.schemas.update",
"cloudsql.instances.connect",
])
.before(requireAuth)
.action(async (serviceId: string, options: Options) => {
.action(async (options: Options) => {
const projectId = needProjectId(options);
await ensureApis(projectId);
const serviceInfo = await pickService(projectId, options.config, serviceId);
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) {
Expand Down
17 changes: 12 additions & 5 deletions src/commands/dataconnect-sql-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
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";
Expand Down Expand Up @@ -62,8 +62,8 @@
return query;
}

async function mainShellLoop(conn: pg.PoolClient) {

Check warning on line 65 in src/commands/dataconnect-sql-shell.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
while (true) {

Check warning on line 66 in src/commands/dataconnect-sql-shell.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected constant condition
const query = await promptForQuery();
if (query.toLowerCase() === ".exit") {
break;
Expand All @@ -81,16 +81,23 @@
}
}

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 <serviceId>", "the serviceId of the Data Connect service")
.option("--location <location>", "the location of the Data Connect service to disambiguate")
.before(requirePermissions, ["firebasedataconnect.services.list", "cloudsql.instances.connect"])
.before(requireAuth)
.action(async (serviceId: string, options: Options) => {
.action(async (options: Options) => {
const projectId = needProjectId(options);
await ensureApis(projectId);
const serviceInfo = await pickService(projectId, options.config, serviceId);
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);
Expand All @@ -99,7 +106,7 @@
const connectionName = instance.connectionName;
if (!connectionName) {
throw new FirebaseError(
`Could not get instance connection string for ${options.instanceId}:${options.databaseId}`,

Check warning on line 109 in src/commands/dataconnect-sql-shell.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Invalid type "unknown" of template literal expression

Check warning on line 109 in src/commands/dataconnect-sql-shell.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Invalid type "unknown" of template literal expression
);
}
const connector: Connector = new Connector({
Expand Down Expand Up @@ -134,5 +141,5 @@
await pool.end();
connector.close();

return { projectId, serviceId };
return { projectId };
});
6 changes: 1 addition & 5 deletions src/commands/firestore-backups-delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,7 @@ export const command = new Command("firestore:backups:delete <backup>")
throw new FirebaseError(`Failed to delete the backup ${backupName}`, { original: err });
}

if (options.json) {
logger.info(JSON.stringify(backup, undefined, 2));
} else {
logger.info(clc.bold(`Successfully deleted ${clc.yellow(backupName)}`));
}
logger.info(clc.bold(`Successfully deleted ${clc.yellow(backupName)}`));

return backup;
});
10 changes: 2 additions & 8 deletions src/commands/firestore-backups-get.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
import { Command } from "../command";
import { logger } from "../logger";
import { requirePermissions } from "../requirePermissions";
import { Emulators } from "../emulator/types";
import { warnEmulatorNotSupported } from "../emulator/commandUtils";
import { FirestoreOptions } from "../firestore/options";
import { Backup, getBackup } from "../gcp/firestore";
import { PrettyPrint } from "../firestore/pretty-print";

export const command = new Command("firestore:backups:get <backup>")
.description("get a Cloud Firestore database backup")
.before(requirePermissions, ["datastore.backups.get"])
.before(warnEmulatorNotSupported, Emulators.FIRESTORE)
.action(async (backupName: string, options: FirestoreOptions) => {
.action(async (backupName: string) => {
const backup: Backup = await getBackup(backupName);
const printer = new PrettyPrint();

if (options.json) {
logger.info(JSON.stringify(backup, undefined, 2));
} else {
printer.prettyPrintBackup(backup);
}
printer.prettyPrintBackup(backup);

return backup;
});
19 changes: 8 additions & 11 deletions src/commands/firestore-backups-list.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Command } from "../command";
import { logger } from "../logger";
import { requirePermissions } from "../requirePermissions";
import { Emulators } from "../emulator/types";
import { warnEmulatorNotSupported } from "../emulator/commandUtils";
Expand All @@ -23,17 +22,15 @@ export const command = new Command("firestore:backups:list")
const listBackupsResponse: ListBackupsResponse = await listBackups(options.project, location);
const backups: Backup[] = listBackupsResponse.backups || [];

if (options.json) {
logger.info(JSON.stringify(listBackupsResponse, undefined, 2));
} else {
printer.prettyPrintBackups(backups);
if (listBackupsResponse.unreachable && listBackupsResponse.unreachable.length > 0) {
logWarning(
"We were not able to reach the following locations: " +
listBackupsResponse.unreachable.join(", "),
);
}
printer.prettyPrintBackups(backups);
if (listBackupsResponse.unreachable && listBackupsResponse.unreachable.length > 0) {
logWarning(
"We were not able to reach the following locations: " +
listBackupsResponse.unreachable.join(", "),
);
}

// TODO: Consider returning listBackupResponse instead for --json. This will
// be a breaking change but exposes .unreachable, not just .backups.
return backups;
});
10 changes: 3 additions & 7 deletions src/commands/firestore-backups-schedules-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,9 @@ export const command = new Command("firestore:backups:schedules:create")
weeklyRecurrence,
);

if (options.json) {
logger.info(JSON.stringify(backupSchedule, undefined, 2));
} else {
logger.info(
clc.bold(`Successfully created ${printer.prettyBackupScheduleString(backupSchedule)}`),
);
}
logger.info(
clc.bold(`Successfully created ${printer.prettyBackupScheduleString(backupSchedule)}`),
);

return backupSchedule;
});
6 changes: 1 addition & 5 deletions src/commands/firestore-backups-schedules-delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,7 @@ export const command = new Command("firestore:backups:schedules:delete <backupSc
});
}

if (options.json) {
logger.info(JSON.stringify(backupSchedule, undefined, 2));
} else {
logger.info(clc.bold(`Successfully deleted ${clc.yellow(backupScheduleName)}`));
}
logger.info(clc.bold(`Successfully deleted ${clc.yellow(backupScheduleName)}`));

return backupSchedule;
});
Loading
Loading