Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding better error messaging when using non Firebase projects #8021

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
- Added default value for `emulators.dataconnect.dataDir` to `init dataconnect`.
- Fixed an issue where `firebase` would error out instead of displaying help text.
- Improved error messaging when using a project that does not have Firebase enabled.
26 changes: 22 additions & 4 deletions src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
import { detectProjectRoot } from "./detectProjectRoot";
import { trackEmulator, trackGA4 } from "./track";
import { selectAccount, setActiveAccount } from "./auth";
import { getProject } from "./management/projects";
import {
getProject,
isFirebaseProject,
printMigrateToFirebaseMessage,
} from "./management/projects";
import { requireAuth } from "./requireAuth";
import { Options } from "./options";

Expand Down Expand Up @@ -38,6 +42,7 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private options: any[][] = [];
private aliases: string[] = [];
private firebaseRequired = true;
private actionFn: ActionFunction = (): void => {
// noop by default, unless overwritten by `.action(fn)`.
};
Expand Down Expand Up @@ -99,6 +104,15 @@
return this;
}

/**
* This command is safe to run on Cloud projects that do not have Firebase enabled.
* @returns
*/
firebaseNotRequired(): Command {
this.firebaseRequired = false;
return this;
}

/**
* Attaches a function to run before the command's action function.
* @param fn the function to run.
Expand Down Expand Up @@ -291,7 +305,7 @@
* @param options the command options object.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public async prepare(options: any): Promise<void> {
public async prepare(options: any): Promise<Options> {
options = options || {};
options.project = getInheritedOption(options, "project");

Expand Down Expand Up @@ -342,6 +356,7 @@
await this.resolveProjectIdentifiers(options);
validateProjectId(options.projectId);
}
return options as Options;
}

/**
Expand Down Expand Up @@ -415,12 +430,15 @@
args.splice(args.length - 1, 0, "");
}

const options = last(args);
await this.prepare(options);
const options = await this.prepare(last(args));

for (const before of this.befores) {
await before.fn(options, ...before.args);
}
if (this.firebaseRequired && options.project && !(await isFirebaseProject(options.project))) {
printMigrateToFirebaseMessage(options.project);
throw new FirebaseError("This command requires that your Google Cloud project has Firebase added to it.");

Check failure on line 440 in src/command.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Replace `"This·command·requires·that·your·Google·Cloud·project·has·Firebase·added·to·it."` with `⏎··········"This·command·requires·that·your·Google·Cloud·project·has·Firebase·added·to·it.",⏎········`
}
return this.actionFn(...args);
};
}
Expand Down
1 change: 1 addition & 0 deletions src/commands/apphosting-backends-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { requireTosAcceptance } from "../requireTosAcceptance";

export const command = new Command("apphosting:backends:create")
.description("create a Firebase App Hosting backend")
.firebaseNotRequired()
.option(
"-a, --app <webAppId>",
"specify an existing Firebase web app's ID to associate your App Hosting backend with",
Expand Down
1 change: 1 addition & 0 deletions src/commands/apphosting-backends-delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import * as ora from "ora";

export const command = new Command("apphosting:backends:delete <backend>")
.description("delete a Firebase App Hosting backend")
.firebaseNotRequired()
.option("-l, --location <location>", "specify the location of the backend", "-")
.withForce()
.before(apphosting.ensureApiEnabled)
Expand Down
1 change: 1 addition & 0 deletions src/commands/apphosting-backends-get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { printBackendsTable } from "./apphosting-backends-list";

export const command = new Command("apphosting:backends:get <backend>")
.description("print info about a Firebase App Hosting backend")
.firebaseNotRequired()
.option("-l, --location <location>", "backend location", "-")
.before(apphosting.ensureApiEnabled)
.action(async (backend: string, options: Options) => {
Expand Down
1 change: 1 addition & 0 deletions src/commands/apphosting-backends-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const TABLE_HEAD = ["Backend", "Repository", "URL", "Location", "Updated Date"];

export const command = new Command("apphosting:backends:list")
.description("list Firebase App Hosting backends")
.firebaseNotRequired()
.option("-l, --location <location>", "list backends in the specified location", "-")
.before(apphosting.ensureApiEnabled)
.action(async (options: Options) => {
Expand Down
1 change: 1 addition & 0 deletions src/commands/apphosting-builds-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { needProjectId } from "../projectUtils";

export const command = new Command("apphosting:builds:create <backendId>")
.description("create a build for an App Hosting backend")
.firebaseNotRequired()
.option("-l, --location <location>", "specify the region of the backend", "us-central1")
.option("-i, --id <buildId>", "id of the build (defaults to autogenerating a random id)", "")
.option("-b, --branch <branch>", "repository branch to deploy (defaults to 'main')", "main")
Expand Down
1 change: 1 addition & 0 deletions src/commands/apphosting-builds-get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { needProjectId } from "../projectUtils";

export const command = new Command("apphosting:builds:get <backendId> <buildId>")
.description("get a build for an App Hosting backend")
.firebaseNotRequired()
.option("-l, --location <location>", "specify the region of the backend", "us-central1")
.before(apphosting.ensureApiEnabled)
.action(async (backendId: string, buildId: string, options: Options) => {
Expand Down
1 change: 1 addition & 0 deletions src/commands/apphosting-config-export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const command = new Command("apphosting:config:export")
.description(
"Export App Hosting configurations such as secrets into an apphosting.local.yaml file",
)
.firebaseNotRequired()
.option(
"-s, --secrets <apphosting.yaml or apphosting.<environment>.yaml file to export secrets from>",
"This command combines the base apphosting.yaml with the specified environment-specific file (e.g., apphosting.staging.yaml). If keys conflict, the environment-specific file takes precedence.",
Expand Down
1 change: 1 addition & 0 deletions src/commands/apphosting-repos-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { requireTosAcceptance } from "../requireTosAcceptance";

export const command = new Command("apphosting:repos:create")
.description("create a Firebase App Hosting Developer Connect Git Repository Link")
.firebaseNotRequired()
.option("-l, --location <location>", "specify the location of the backend", "")
.option("-g, --gitconnection <connection>", "id of the connection", "")
.before(ensureApiEnabled)
Expand Down
1 change: 1 addition & 0 deletions src/commands/apphosting-rollouts-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { createRollout } from "../apphosting/rollout";

export const command = new Command("apphosting:rollouts:create <backendId>")
.description("create a rollout using a build for an App Hosting backend")
.firebaseNotRequired()
.option("-l, --location <location>", "specify the region of the backend", "-")
.option(
"-b, --git-branch <gitBranch>",
Expand Down
1 change: 1 addition & 0 deletions src/commands/apphosting-rollouts-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { needProjectId } from "../projectUtils";

export const command = new Command("apphosting:rollouts:list <backendId>")
.description("list rollouts of an App Hosting backend")
.firebaseNotRequired()
.option(
"-l, --location <location>",
"region of the rollouts (defaults to listing rollouts from all regions)",
Expand Down
1 change: 1 addition & 0 deletions src/commands/apphosting-secrets-access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const command = new Command("apphosting:secrets:access <secretName[@versi
.description(
"Access secret value given secret and its version. Defaults to accessing the latest version.",
)
.firebaseNotRequired()
.before(requireAuth)
.before(secretManager.ensureApi)
.before(requirePermissions, ["secretmanager.versions.access"])
Expand Down
1 change: 1 addition & 0 deletions src/commands/apphosting-secrets-describe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const Table = require("cli-table");

export const command = new Command("apphosting:secrets:describe <secretName>")
.description("Get metadata for secret and its versions.")
.firebaseNotRequired()
.before(requireAuth)
.before(secretManager.ensureApi)
.before(requirePermissions, ["secretmanager.secrets.get"])
Expand Down
1 change: 1 addition & 0 deletions src/commands/apphosting-secrets-grantaccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getBackendForAmbiguousLocation } from "../apphosting/backend";

export const command = new Command("apphosting:secrets:grantaccess <secretName>")
.description("grant service accounts permissions to the provided secret")
.firebaseNotRequired()
.option("-l, --location <location>", "backend location", "-")
.option("-b, --backend <backend>", "backend name")
.before(requireAuth)
Expand Down
1 change: 1 addition & 0 deletions src/commands/apphosting-secrets-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import * as utils from "../utils";

export const command = new Command("apphosting:secrets:set <secretName>")
.description("create or update a secret for use in Firebase App Hosting")
.firebaseNotRequired()
.option("-l, --location <location>", "optional location to retrict secret replication")
// TODO: What is the right --force behavior for granting access? Seems correct to grant permissions
// if there is only one set of accounts, but should maybe fail if there are more than one set of
Expand Down
2 changes: 1 addition & 1 deletion src/commands/apps-android-sha-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const command = new Command("apps:android:sha:create <appId> <shaHash>")
}),
`Creating Android SHA certificate ${clc.bold(
options.shaHash,
)}with Android app Id ${clc.bold(appId)}`,
)} with Android app Id ${clc.bold(appId)}`,
);

return shaCertificate;
Expand Down
1 change: 1 addition & 0 deletions src/commands/dataconnect-sdk-generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type GenerateOptions = Options & { watch?: boolean };

export const command = new Command("dataconnect:sdk:generate")
.description("generates typed SDKs for your Data Connect connectors")
.firebaseNotRequired()
.option(
"--watch",
"watch for changes to your connector GQL files and regenerate your SDKs when updates occur",
Expand Down
1 change: 1 addition & 0 deletions src/commands/dataconnect-services-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const Table = require("cli-table");

export const command = new Command("dataconnect:services:list")
.description("list all deployed services in your Firebase project")
.firebaseNotRequired()
.before(requirePermissions, [
"dataconnect.services.list",
"dataconnect.schemas.list",
Expand Down
1 change: 1 addition & 0 deletions src/commands/dataconnect-sql-diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const command = new Command("dataconnect:sql:diff [serviceId]")
.description(
"displays the differences between a local DataConnect schema and your CloudSQL database's current schema",
)
.firebaseNotRequired()
.before(requirePermissions, [
"firebasedataconnect.services.list",
"firebasedataconnect.schemas.list",
Expand Down
1 change: 1 addition & 0 deletions src/commands/dataconnect-sql-grant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const allowedRoles = Object.keys(fdcSqlRoleMap);

export const command = new Command("dataconnect:sql:grant [serviceId]")
.description("Grants the SQL role <role> to the provided user or service account <email>.")
.firebaseNotRequired()
.option("-R, --role <role>", "The SQL role to grant. One of: owner, writer, or reader.")
.option(
"-E, --email <email>",
Expand Down
1 change: 1 addition & 0 deletions src/commands/dataconnect-sql-migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { logLabeledSuccess } from "../utils";

export const command = new Command("dataconnect:sql:migrate [serviceId]")
.description("migrates your CloudSQL database's schema to match your local DataConnect schema")
.firebaseNotRequired()
.before(requirePermissions, [
"firebasedataconnect.services.list",
"firebasedataconnect.schemas.list",
Expand Down
1 change: 1 addition & 0 deletions src/commands/dataconnect-sql-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ async function mainShellLoop(conn: pg.PoolClient) {

export const command = new Command("dataconnect:sql:shell [serviceId]")
.description("Starts a shell connected directly to your dataconnect cloudsql instance.")
.firebaseNotRequired()
.before(requirePermissions, ["firebasedataconnect.services.list", "cloudsql.instances.connect"])
.before(requireAuth)
.action(async (serviceId: string, options: Options) => {
Expand Down
33 changes: 28 additions & 5 deletions src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import { FirebaseError } from "../error";
import { bold } from "colorette";
import { interactiveCreateHostingSite } from "../hosting/interactive";
import { logBullet } from "../utils";
import { Options } from "../options";
import { needProjectId } from "../projectUtils";
import { isFirebaseProject, printMigrateToFirebaseMessage } from "../management/projects";

// in order of least time-consuming to most time-consuming
export const VALID_DEPLOY_TARGETS = [
Expand Down Expand Up @@ -74,6 +77,7 @@ export const TARGET_PERMISSIONS: Record<(typeof VALID_DEPLOY_TARGETS)[number], s

export const command = new Command("deploy")
.description("deploy code and assets to your Firebase project")
.firebaseNotRequired()
.withForce(
"delete Cloud Functions missing from the current working directory and bypass interactive prompts",
)
Expand All @@ -97,19 +101,38 @@ export const command = new Command("deploy")
"In order to provide better validation, this may still enable APIs on the target project.",
)
.before(requireConfig)
.before((options) => {
.before((options: Options) => {
options.filteredTargets = filterTargets(options, VALID_DEPLOY_TARGETS);
const permissions = options.filteredTargets.reduce((perms: string[], target: string) => {
return perms.concat(TARGET_PERMISSIONS[target]);
}, []);
return requirePermissions(options, permissions);
})
.before((options) => {
.before(async (options: Options) => {
if (
options.filteredTargets.includes("database") ||
options.filteredTargets.includes("extensions") ||
options.filteredTargets.includes("functions") ||
options.filteredTargets.includes("hosting") ||
options.filteredTargets.includes("remoteconfig") ||
options.filteredTargets.includes("storage")
) {
const projectId = needProjectId(options);
if (!(await isFirebaseProject(projectId))) {
printMigrateToFirebaseMessage(projectId);
throw new FirebaseError(
"Some of the products in this deployment require that your Google Cloud project has Firebase added to it.",
);
}
}
})
.before((options: Options) => {
if (options.filteredTargets.includes("functions")) {
return checkServiceAccountIam(options.project);
const projectId = needProjectId(options);
return checkServiceAccountIam(projectId);
}
})
.before(async (options) => {
.before(async (options: Options) => {
// only fetch the default instance for hosting or database deploys
if (options.filteredTargets.includes("database")) {
await requireDatabaseInstance(options);
Expand Down Expand Up @@ -145,6 +168,6 @@ export const command = new Command("deploy")
}
})
.before(checkValidTargetFilters)
.action((options) => {
.action((options: Options) => {
return deploy(options.filteredTargets, options);
});
1 change: 1 addition & 0 deletions src/commands/emulators-exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const command = new Command("emulators:exec <script>")
.description(
"start the local Firebase emulators, " + "run a test script, then shut down the emulators",
)
.firebaseNotRequired()
.option(commandUtils.FLAG_ONLY, commandUtils.DESC_ONLY)
.option(commandUtils.FLAG_INSPECT_FUNCTIONS, commandUtils.DESC_INSPECT_FUNCTIONS)
.option(commandUtils.FLAG_IMPORT, commandUtils.DESC_IMPORT)
Expand Down
1 change: 1 addition & 0 deletions src/commands/emulators-export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as commandUtils from "../emulator/commandUtils";
const COMMAND_NAME = "emulators:export";
export const command = new Command(`${COMMAND_NAME} <path>`)
.description("export data from running emulators")
.firebaseNotRequired()
.withForce("overwrite any export data in the target directory")
.option(commandUtils.FLAG_ONLY, commandUtils.DESC_ONLY)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
1 change: 1 addition & 0 deletions src/commands/emulators-start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const command = new Command("emulators:start")
.before(commandUtils.setExportOnExitOptions)
.before(commandUtils.beforeEmulatorCommand)
.description("start the local Firebase emulators")
.firebaseNotRequired()
.option(commandUtils.FLAG_ONLY, commandUtils.DESC_ONLY)
.option(commandUtils.FLAG_INSPECT_FUNCTIONS, commandUtils.DESC_INSPECT_FUNCTIONS)
.option(commandUtils.FLAG_IMPORT, commandUtils.DESC_IMPORT)
Expand Down
1 change: 1 addition & 0 deletions src/commands/experimental-functions-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const command = new Command("experimental:functions:shell")
.description(
"launch full Node shell with emulated functions. (Alias for `firebase functions:shell.)",
)
.firebaseNotRequired()
.option("-p, --port <port>", "the port on which to emulate functions (default: 5000)", 5000)
.before(requireConfig)
.before(requirePermissions)
Expand Down
1 change: 1 addition & 0 deletions src/commands/experiments-describe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { last } from "../utils";

export const command = new Command("experiments:describe <experiment>")
.description("describe what an experiment does when enabled")
.firebaseNotRequired()
.action((experiment: string) => {
if (!experiments.isValidExperiment(experiment)) {
let message = `Cannot find experiment ${bold(experiment)}`;
Expand Down
1 change: 1 addition & 0 deletions src/commands/experiments-disable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { last } from "../utils";

export const command = new Command("experiments:disable <experiment>")
.description("disable an experiment on this machine")
.firebaseNotRequired()
.action((experiment: string) => {
if (experiments.isValidExperiment(experiment)) {
experiments.setEnabled(experiment, false);
Expand Down
1 change: 1 addition & 0 deletions src/commands/experiments-enable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { last } from "../utils";

export const command = new Command("experiments:enable <experiment>")
.description("enable an experiment on this machine")
.firebaseNotRequired()
.action((experiment: string) => {
if (experiments.isValidExperiment(experiment)) {
experiments.setEnabled(experiment, true);
Expand Down
1 change: 1 addition & 0 deletions src/commands/experiments-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const command = new Command("experiments:list")
.description(
"list all experiments, along with a description of each experiment and whether it is currently enabled",
)
.firebaseNotRequired()
.action(() => {
const table = new Table({
head: ["Enabled", "Name", "Description"],
Expand Down
1 change: 1 addition & 0 deletions src/commands/firestore-backups-delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { FirebaseError } from "../error";
export const command = new Command("firestore:backups:delete <backup>")
.description("Delete a backup under your Cloud Firestore database.")
.option("--force", "Attempt to delete backup without prompting for confirmation.")
.firebaseNotRequired()
.before(requirePermissions, ["datastore.backups.delete"])
.before(warnEmulatorNotSupported, Emulators.FIRESTORE)
.action(async (backupName: string, options: FirestoreOptions) => {
Expand Down
1 change: 1 addition & 0 deletions src/commands/firestore-backups-get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { PrettyPrint } from "../firestore/pretty-print";

export const command = new Command("firestore:backups:get <backup>")
.description("Get a Cloud Firestore database backup.")
.firebaseNotRequired()
.before(requirePermissions, ["datastore.backups.get"])
.before(warnEmulatorNotSupported, Emulators.FIRESTORE)
.action(async (backupName: string, options: FirestoreOptions) => {
Expand Down
Loading
Loading