diff --git a/firebase-vscode/src/data-connect/ad-hoc-mutations.ts b/firebase-vscode/src/data-connect/ad-hoc-mutations.ts index 3fe6511465f..710e24bd8e0 100644 --- a/firebase-vscode/src/data-connect/ad-hoc-mutations.ts +++ b/firebase-vscode/src/data-connect/ad-hoc-mutations.ts @@ -130,7 +130,7 @@ export function registerAdHoc( const queryName = `${ast.name.value.charAt(0).toLowerCase()}${ast.name.value.slice(1)}s`; return ` -# This is a file for you to write an un-named queries. +# This is a file for you to write an un-named query. # Only one un-named query is allowed per file. query { ${queryName}${buildRecursiveObjectQuery(ast)!} @@ -158,7 +158,9 @@ query { } const schema = buildClientSchema(introspect.data); const dataType = schema.getType(`${ast.name.value}_Data`); - if (!isInputObjectType(dataType)) return; + if (!isInputObjectType(dataType)) { + return; + } const adhocMutation = print( await makeAdHocMutation( @@ -206,7 +208,9 @@ query { for (const field of fields) { const type = getNamedType(field.type); const defaultValue = getDefaultScalarValue(type.name); - if (!defaultValue) continue; + if (!defaultValue) { + continue; + } argumentFields.push({ kind: Kind.OBJECT_FIELD, diff --git a/firebase-vscode/src/data-connect/explorer-provider.ts b/firebase-vscode/src/data-connect/explorer-provider.ts index 3c704e50a11..72c954a1bbf 100644 --- a/firebase-vscode/src/data-connect/explorer-provider.ts +++ b/firebase-vscode/src/data-connect/explorer-provider.ts @@ -124,7 +124,7 @@ export class ExplorerTreeDataProvider return { name: f.name, baseType: OPERATION_TYPE.mutation }; }); } - const field = this._field(element) + const field = this._field(element); if (field) { const unwrapped = this._baseType(field); const type = this._unref(unwrapped); diff --git a/src/appdistribution/client.ts b/src/appdistribution/client.ts index 4ed64962bc5..512b9bccf23 100644 --- a/src/appdistribution/client.ts +++ b/src/appdistribution/client.ts @@ -1,11 +1,12 @@ import { ReadStream } from "fs"; -import { appDistributionOrigin } from "../api"; -import { Client, ClientResponse } from "../apiv2"; -import { FirebaseError } from "../error"; -import * as operationPoller from "../operation-poller"; import * as utils from "../utils"; +import * as operationPoller from "../operation-poller"; import { Distribution } from "./distribution"; +import { FirebaseError, getErrMsg } from "../error"; +import { Client, ClientResponse } from "../apiv2"; +import { appDistributionOrigin } from "../api"; + import { AabInfo, BatchRemoveTestersResponse, @@ -83,8 +84,8 @@ export class AppDistributionClient { try { await this.appDistroV1Client.patch(`/${releaseName}`, data, { queryParams }); - } catch (err: any) { - throw new FirebaseError(`failed to update release notes with ${err?.message}`); + } catch (err: unknown) { + throw new FirebaseError(`failed to update release notes with ${getErrMsg(err)}`); } utils.logSuccess("added release notes successfully"); @@ -172,8 +173,8 @@ export class AppDistributionClient { path: `${projectName}/testers:batchAdd`, body: { emails: emails }, }); - } catch (err: any) { - throw new FirebaseError(`Failed to add testers ${err}`); + } catch (err: unknown) { + throw new FirebaseError(`Failed to add testers ${getErrMsg(err)}`); } utils.logSuccess(`Testers created successfully`); @@ -190,8 +191,8 @@ export class AppDistributionClient { path: `${projectName}/testers:batchRemove`, body: { emails: emails }, }); - } catch (err: any) { - throw new FirebaseError(`Failed to remove testers ${err}`); + } catch (err: unknown) { + throw new FirebaseError(`Failed to remove testers ${getErrMsg(err)}`); } return apiResponse.body; } @@ -229,8 +230,8 @@ export class AppDistributionClient { alias === undefined ? `${projectName}/groups` : `${projectName}/groups?groupId=${alias}`, body: { displayName: displayName }, }); - } catch (err: any) { - throw new FirebaseError(`Failed to create group ${err}`); + } catch (err: unknown) { + throw new FirebaseError(`Failed to create group ${getErrMsg(err)}`); } return apiResponse.body; } @@ -241,8 +242,8 @@ export class AppDistributionClient { method: "DELETE", path: groupName, }); - } catch (err: any) { - throw new FirebaseError(`Failed to delete group ${err}`); + } catch (err: unknown) { + throw new FirebaseError(`Failed to delete group ${getErrMsg(err)}`); } utils.logSuccess(`Group deleted successfully`); @@ -255,8 +256,8 @@ export class AppDistributionClient { path: `${groupName}:batchJoin`, body: { emails: emails }, }); - } catch (err: any) { - throw new FirebaseError(`Failed to add testers to group ${err}`); + } catch (err: unknown) { + throw new FirebaseError(`Failed to add testers to group ${getErrMsg(err)}`); } utils.logSuccess(`Testers added to group successfully`); @@ -269,8 +270,8 @@ export class AppDistributionClient { path: `${groupName}:batchLeave`, body: { emails: emails }, }); - } catch (err: any) { - throw new FirebaseError(`Failed to remove testers from group ${err}`); + } catch (err: unknown) { + throw new FirebaseError(`Failed to remove testers from group ${getErrMsg(err)}`); } utils.logSuccess(`Testers removed from group successfully`); @@ -291,8 +292,8 @@ export class AppDistributionClient { }, }); return response.body; - } catch (err: any) { - throw new FirebaseError(`Failed to create release test ${err}`); + } catch (err: unknown) { + throw new FirebaseError(`Failed to create release test ${getErrMsg(err)}`); } } diff --git a/src/appdistribution/distribution.ts b/src/appdistribution/distribution.ts index 3175bde6c03..3f03d7ea35b 100644 --- a/src/appdistribution/distribution.ts +++ b/src/appdistribution/distribution.ts @@ -1,5 +1,5 @@ import * as fs from "fs-extra"; -import { FirebaseError } from "../error"; +import { FirebaseError, getErrMsg } from "../error"; import { logger } from "../logger"; import * as pathUtil from "path"; @@ -33,8 +33,8 @@ export class Distribution { let stat; try { stat = fs.statSync(path); - } catch (err: any) { - logger.info(err); + } catch (err: unknown) { + logger.info(getErrMsg(err)); throw new FirebaseError(`File ${path} does not exist: verify that file points to a binary`); } if (!stat.isFile()) { diff --git a/src/apphosting/backend.ts b/src/apphosting/backend.ts index 42a563ddae5..8a5aa579901 100644 --- a/src/apphosting/backend.ts +++ b/src/apphosting/backend.ts @@ -16,7 +16,7 @@ import { import { Backend, BackendOutputOnlyFields, API_VERSION } from "../gcp/apphosting"; import { addServiceAccountToRoles } from "../gcp/resourceManager"; import * as iam from "../gcp/iam"; -import { FirebaseError } from "../error"; +import { FirebaseError, getErrStatus, getError } from "../error"; import { promptOnce } from "../prompt"; import { DEFAULT_LOCATION } from "./constants"; import { ensure } from "../ensureApiEnabled"; @@ -269,13 +269,13 @@ async function promptNewBackendId( const backendId = await promptOnce(prompt); try { await apphosting.getBackend(projectId, location, backendId); - } catch (err: any) { - if (err.status === 404) { + } catch (err: unknown) { + if (getErrStatus(err) === 404) { return backendId; } throw new FirebaseError( `Failed to check if backend with id ${backendId} already exists in ${location}`, - { original: err }, + { original: getError(err) }, ); } logWarning(`Backend with id ${backendId} already exists in ${location}`); @@ -331,9 +331,9 @@ async function provisionDefaultComputeServiceAccount(projectId: string): Promise "Default service account used to run builds and deploys for Firebase App Hosting", "Firebase App Hosting compute service account", ); - } catch (err: any) { + } catch (err: unknown) { // 409 Already Exists errors can safely be ignored. - if (err.status !== 409) { + if (getErrStatus(err) !== 409) { throw err; } } @@ -422,9 +422,9 @@ export async function getBackendForLocation( ): Promise { try { return await apphosting.getBackend(projectId, location, backendId); - } catch (err: any) { + } catch (err: unknown) { throw new FirebaseError(`No backend named "${backendId}" found in ${location}.`, { - original: err, + original: getError(err), }); } } diff --git a/src/apphosting/secrets/index.ts b/src/apphosting/secrets/index.ts index 6e8fbab8a53..1a59e88044d 100644 --- a/src/apphosting/secrets/index.ts +++ b/src/apphosting/secrets/index.ts @@ -1,4 +1,4 @@ -import { FirebaseError } from "../../error"; +import { FirebaseError, getErrStatus, getError } from "../../error"; import * as iam from "../../gcp/iam"; import * as gcsm from "../../gcp/secretManager"; import * as gcb from "../../gcp/cloudbuild"; @@ -100,10 +100,10 @@ export async function grantSecretAccess( let existingBindings; try { existingBindings = (await gcsm.getIamPolicy({ projectId, name: secretName })).bindings || []; - } catch (err: any) { + } catch (err: unknown) { throw new FirebaseError( `Failed to get IAM bindings on secret: ${secretName}. Ensure you have the permissions to do so and try again.`, - { original: err }, + { original: getError(err) }, ); } @@ -111,10 +111,10 @@ export async function grantSecretAccess( // TODO: Merge with existing bindings with the same role const updatedBindings = existingBindings.concat(newBindings); await gcsm.setIamPolicy({ projectId, name: secretName }, updatedBindings); - } catch (err: any) { + } catch (err: unknown) { throw new FirebaseError( `Failed to set IAM bindings ${JSON.stringify(newBindings)} on secret: ${secretName}. Ensure you have the permissions to do so and try again.`, - { original: err }, + { original: getError(err) }, ); } @@ -136,9 +136,9 @@ export async function upsertSecret( let existing: gcsm.Secret; try { existing = await gcsm.getSecret(project, secret); - } catch (err: any) { - if (err.status !== 404) { - throw new FirebaseError("Unexpected error loading secret", { original: err }); + } catch (err: unknown) { + if (getErrStatus(err) !== 404) { + throw new FirebaseError("Unexpected error loading secret", { original: getError(err) }); } await gcsm.createSecret(project, secret, gcsm.labels("apphosting"), location); return true; diff --git a/src/archiveDirectory.ts b/src/archiveDirectory.ts index 46a6945a83e..cbcadd992b3 100644 --- a/src/archiveDirectory.ts +++ b/src/archiveDirectory.ts @@ -5,7 +5,7 @@ import * as path from "path"; import * as tar from "tar"; import * as tmp from "tmp"; -import { FirebaseError } from "./error"; +import { FirebaseError, getError } from "./error"; import { listFiles } from "./listFiles"; import { logger } from "./logger"; import { Readable, Writable } from "stream"; @@ -62,11 +62,11 @@ export async function archiveDirectory( const archive = await makeArchive; logger.debug(`Archived ${filesize(archive.size)} in ${sourceDirectory}.`); return archive; - } catch (err: any) { + } catch (err: unknown) { if (err instanceof FirebaseError) { throw err; } - throw new FirebaseError("Failed to create archive.", { original: err }); + throw new FirebaseError("Failed to create archive.", { original: getError(err) }); } } diff --git a/src/auth.ts b/src/auth.ts index 368c676a19d..7ca487ad429 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -8,7 +8,7 @@ import * as url from "url"; import * as apiv2 from "./apiv2"; import { configstore } from "./configstore"; -import { FirebaseError } from "./error"; +import { FirebaseError, getErrMsg } from "./error"; import * as utils from "./utils"; import { logger } from "./logger"; import { promptOnce } from "./prompt"; @@ -318,7 +318,7 @@ async function getTokensFromAuthorizationCode( headers: form.getHeaders(), skipLog: { body: true, queryParams: true, resBody: true }, }); - } catch (err: any) { + } catch (err: unknown) { if (err instanceof Error) { logger.debug("Token Fetch Error:", err.stack || ""); } else { @@ -515,7 +515,7 @@ async function loginWithLocalhost( const tokens = await getTokens(queryCode, callbackUrl); respondHtml(req, res, 200, successHtml); resolve(tokens); - } catch (err: any) { + } catch (err: unknown) { const html = await readTemplate("loginFailure.html"); respondHtml(req, res, 400, html); reject(err); @@ -730,8 +730,8 @@ export async function getAccessToken(refreshToken: string, authScopes: string[]) } else { try { return refreshAuth(); - } catch (err: any) { - logger.debug(`Unable to refresh token: ${err}`); + } catch (err: unknown) { + logger.debug(`Unable to refresh token: ${getErrMsg(err)}`); } throw new FirebaseError("Unable to getAccessToken"); } diff --git a/src/commands/appdistribution-distribute.ts b/src/commands/appdistribution-distribute.ts index e67738b64e5..53f490e8cee 100644 --- a/src/commands/appdistribution-distribute.ts +++ b/src/commands/appdistribution-distribute.ts @@ -10,7 +10,7 @@ import { UploadReleaseResult, TestDevice, } from "../appdistribution/types"; -import { FirebaseError } from "../error"; +import { FirebaseError, getErrMsg, getErrStatus } from "../error"; import { Distribution, DistributionFileType } from "../appdistribution/distribution"; import { ensureFileExists, @@ -96,8 +96,8 @@ export const command = new Command("appdistribution:distribute ") try { utils.logBullet(`Deleting group from project`); await appDistroClient.deleteGroup(`${projectName}/groups/${alias}`); - } catch (err: any) { - throw new FirebaseError(`Failed to delete group ${err}`); + } catch (err: unknown) { + throw new FirebaseError(`Failed to delete group ${getErrMsg(err)}`); } utils.logSuccess(`Group ${alias} has successfully been deleted`); }); diff --git a/src/commands/appdistribution-testers-remove.ts b/src/commands/appdistribution-testers-remove.ts index c3fb8c5b8bf..fbe6bbc8cf8 100644 --- a/src/commands/appdistribution-testers-remove.ts +++ b/src/commands/appdistribution-testers-remove.ts @@ -1,7 +1,7 @@ import { Command } from "../command"; import * as utils from "../utils"; import { requireAuth } from "../requireAuth"; -import { FirebaseError } from "../error"; +import { FirebaseError, getErrMsg } from "../error"; import { AppDistributionClient } from "../appdistribution/client"; import { getEmails, getProjectName } from "../appdistribution/options-parser-util"; import { logger } from "../logger"; @@ -29,8 +29,8 @@ export const command = new Command("appdistribution:testers:remove [emails...]") try { utils.logBullet(`Deleting ${emailsArr.length} testers from project`); deleteResponse = await appDistroClient.removeTesters(projectName, emailsArr); - } catch (err: any) { - throw new FirebaseError(`Failed to remove testers ${err}`); + } catch (err: unknown) { + throw new FirebaseError(`Failed to remove testers ${getErrMsg(err)}`); } if (!deleteResponse.emails) { diff --git a/src/commands/apphosting-backends-delete.ts b/src/commands/apphosting-backends-delete.ts index c3d50f94a9e..d6caed07346 100644 --- a/src/commands/apphosting-backends-delete.ts +++ b/src/commands/apphosting-backends-delete.ts @@ -1,7 +1,7 @@ import { Command } from "../command"; import { Options } from "../options"; import { needProjectId } from "../projectUtils"; -import { FirebaseError } from "../error"; +import { FirebaseError, getError } from "../error"; import { promptOnce } from "../prompt"; import * as utils from "../utils"; import * as apphosting from "../gcp/apphosting"; @@ -53,8 +53,10 @@ export const command = new Command("apphosting:backends:delete ") try { await deleteBackendAndPoll(projectId, location, backendId); spinner.succeed(`Successfully deleted the backend: ${backendId}`); - } catch (err: any) { + } catch (err: unknown) { spinner.stop(); - throw new FirebaseError(`Failed to delete backend: ${backendId}.`, { original: err }); + throw new FirebaseError(`Failed to delete backend: ${backendId}.`, { + original: getError(err), + }); } }); diff --git a/src/commands/apphosting-backends-get.ts b/src/commands/apphosting-backends-get.ts index 2f3832a5939..c4f5f1dd083 100644 --- a/src/commands/apphosting-backends-get.ts +++ b/src/commands/apphosting-backends-get.ts @@ -1,7 +1,7 @@ import { Command } from "../command"; import { Options } from "../options"; import { needProjectId } from "../projectUtils"; -import { FirebaseError } from "../error"; +import { FirebaseError, getError } from "../error"; import { logWarning } from "../utils"; import * as apphosting from "../gcp/apphosting"; import { printBackendsTable } from "./apphosting-backends-list"; @@ -24,10 +24,10 @@ export const command = new Command("apphosting:backends:get ") const allBackends = resp.backends || []; backendsList = allBackends.filter((bkd) => bkd.name.split("/").pop() === backend); } - } catch (err: any) { + } catch (err: unknown) { throw new FirebaseError( `Failed to get backend: ${backend}. Please check the parameters you have provided.`, - { original: err }, + { original: getError(err) }, ); } if (backendsList.length === 0) { diff --git a/src/commands/apps-create.ts b/src/commands/apps-create.ts index fa08038dcc7..7826bc7848c 100644 --- a/src/commands/apps-create.ts +++ b/src/commands/apps-create.ts @@ -103,7 +103,7 @@ async function initiateIosAppCreation(options: CreateIosAppOptions): Promise [infile]") let responses; try { responses = await importer.execute(); - } catch (err: any) { + } catch (err: unknown) { if (err instanceof FirebaseError) { throw err; } - logger.debug(err); - throw new FirebaseError(`Unexpected error while importing data: ${err}`, { exit: 2 }); + logger.debug(getErrMsg(err)); + throw new FirebaseError(`Unexpected error while importing data: ${getErrMsg(err)}`, { + exit: 2, + }); } if (responses.length) { diff --git a/src/commands/database-instances-list.ts b/src/commands/database-instances-list.ts index faafdbfa2ea..42505f50610 100644 --- a/src/commands/database-instances-list.ts +++ b/src/commands/database-instances-list.ts @@ -36,7 +36,7 @@ export const command = new Command("database:instances:list") let instances: DatabaseInstance[] = []; try { instances = await listDatabaseInstances(projectId, location); - } catch (err: any) { + } catch (err: unknown) { spinner.fail(); throw err; } diff --git a/src/commands/database-push.ts b/src/commands/database-push.ts index 0551970a684..7a9ea92c606 100644 --- a/src/commands/database-push.ts +++ b/src/commands/database-push.ts @@ -4,7 +4,7 @@ import * as fs from "fs"; import { Client } from "../apiv2"; import { Command } from "../command"; import { Emulators } from "../emulator/types"; -import { FirebaseError } from "../error"; +import { FirebaseError, getErrMsg } from "../error"; import { populateInstanceDetails } from "../management/database"; import { printNoticeIfEmulated } from "../emulator/commandUtils"; import { realtimeOriginOrEmulatorOrCustomUrl } from "../database/api"; @@ -52,9 +52,11 @@ export const command = new Command("database:push [infile]") body: inStream, queryParams: u.searchParams, }); - } catch (err: any) { - logger.debug(err); - throw new FirebaseError(`Unexpected error while pushing data: ${err}`, { exit: 2 }); + } catch (err: unknown) { + logger.debug(getErrMsg(err)); + throw new FirebaseError(`Unexpected error while pushing data: ${getErrMsg(err)}`, { + exit: 2, + }); } if (!path.endsWith("/")) { diff --git a/src/commands/database-set.ts b/src/commands/database-set.ts index b53762ce43c..1b79fbf0e65 100644 --- a/src/commands/database-set.ts +++ b/src/commands/database-set.ts @@ -4,7 +4,7 @@ import * as fs from "fs"; import { Client } from "../apiv2"; import { Command } from "../command"; import { Emulators } from "../emulator/types"; -import { FirebaseError } from "../error"; +import { FirebaseError, getErrMsg } from "../error"; import { populateInstanceDetails } from "../management/database"; import { printNoticeIfEmulated } from "../emulator/commandUtils"; import { promptOnce } from "../prompt"; @@ -67,9 +67,11 @@ export const command = new Command("database:set [infile]") body: inStream, queryParams: dbJsonURL.searchParams, }); - } catch (err: any) { - logger.debug(err); - throw new FirebaseError(`Unexpected error while setting data: ${err}`, { exit: 2 }); + } catch (err: unknown) { + logger.debug(getErrMsg(err)); + throw new FirebaseError(`Unexpected error while setting data: ${getErrMsg(err)}`, { + exit: 2, + }); } utils.logSuccess("Data persisted successfully"); diff --git a/src/commands/database-settings-get.ts b/src/commands/database-settings-get.ts index 46281c2e0a5..f6109d3c503 100644 --- a/src/commands/database-settings-get.ts +++ b/src/commands/database-settings-get.ts @@ -4,7 +4,7 @@ import { Client } from "../apiv2"; import { Command } from "../command"; import { DATABASE_SETTINGS, HELP_TEXT, INVALID_PATH_ERROR } from "../database/settings"; import { Emulators } from "../emulator/types"; -import { FirebaseError } from "../error"; +import { FirebaseError, getError } from "../error"; import { populateInstanceDetails } from "../management/database"; import { realtimeOriginOrCustomUrl } from "../database/api"; import { requirePermissions } from "../requirePermissions"; @@ -40,10 +40,10 @@ export const command = new Command("database:settings:get ") let res; try { res = await c.get(u.pathname); - } catch (err: any) { + } catch (err: unknown) { throw new FirebaseError(`Unexpected error fetching configs at ${path}`, { exit: 2, - original: err, + original: getError(err), }); } // strictTriggerValidation returns an object, not a single string. diff --git a/src/commands/database-settings-set.ts b/src/commands/database-settings-set.ts index eacd478bd1a..2b3ac73b956 100644 --- a/src/commands/database-settings-set.ts +++ b/src/commands/database-settings-set.ts @@ -4,7 +4,7 @@ import { Client } from "../apiv2"; import { Command } from "../command"; import { DATABASE_SETTINGS, HELP_TEXT, INVALID_PATH_ERROR } from "../database/settings"; import { Emulators } from "../emulator/types"; -import { FirebaseError } from "../error"; +import { FirebaseError, getError } from "../error"; import { populateInstanceDetails } from "../management/database"; import { realtimeOriginOrCustomUrl } from "../database/api"; import { requirePermissions } from "../requirePermissions"; @@ -44,10 +44,10 @@ export const command = new Command("database:settings:set ") const c = new Client({ urlPrefix: u.origin, auth: true }); try { await c.put(u.pathname, JSON.stringify(parsedValue)); - } catch (err: any) { + } catch (err: unknown) { throw new FirebaseError(`Unexpected error fetching configs at ${path}`, { exit: 2, - original: err, + original: getError(err), }); } utils.logSuccess("Successfully set setting."); diff --git a/src/commands/database-update.ts b/src/commands/database-update.ts index 8ef2848e563..37e32039f8b 100644 --- a/src/commands/database-update.ts +++ b/src/commands/database-update.ts @@ -68,7 +68,7 @@ export const command = new Command("database:update [infile]") body: inStream, queryParams: jsonUrl.searchParams, }); - } catch (err: any) { + } catch (err: unknown) { throw new FirebaseError("Unexpected error while setting data"); } diff --git a/src/commands/ext-dev-init.ts b/src/commands/ext-dev-init.ts index 4cfa2c782f7..b383fa577ea 100644 --- a/src/commands/ext-dev-init.ts +++ b/src/commands/ext-dev-init.ts @@ -4,7 +4,7 @@ import { markedTerminal } from "marked-terminal"; import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; import { Command } from "../command"; import { Config } from "../config"; -import { FirebaseError } from "../error"; +import { FirebaseError, getErrMsg, getError } from "../error"; import { promptOnce } from "../prompt"; import { logger } from "../logger"; import * as npmDependencies from "../init/features/functions/npm-dependencies"; @@ -69,12 +69,12 @@ export const command = new Command("ext:dev:init") await npmDependencies.askInstallDependencies({ source: "functions" }, config); return logger.info("\n" + marked(welcome)); - } catch (err: any) { + } catch (err: unknown) { if (!(err instanceof FirebaseError)) { throw new FirebaseError( - `Error occurred when initializing files for new extension: ${err.message}`, + `Error occurred when initializing files for new extension: ${getErrMsg(err)}`, { - original: err, + original: getError(err), }, ); } diff --git a/src/commands/ext-dev-list.ts b/src/commands/ext-dev-list.ts index 7474ecf5ad1..f47c51a4dda 100644 --- a/src/commands/ext-dev-list.ts +++ b/src/commands/ext-dev-list.ts @@ -2,7 +2,7 @@ import * as clc from "colorette"; const Table = require("cli-table"); import { Command } from "../command"; -import { FirebaseError } from "../error"; +import { FirebaseError, getErrMsg } from "../error"; import { last, logLabeledBullet } from "../utils"; import { listExtensions } from "../extensions/publisherApi"; import { logger } from "../logger"; @@ -19,8 +19,8 @@ export const command = new Command("ext:dev:list ") let extensions; try { extensions = await listExtensions(publisherId); - } catch (err: any) { - throw new FirebaseError(err); + } catch (err: unknown) { + throw new FirebaseError(getErrMsg(err)); } if (extensions.length < 1) { diff --git a/src/commands/ext-dev-register.ts b/src/commands/ext-dev-register.ts index ab5233b55c9..6c0697e1d68 100644 --- a/src/commands/ext-dev-register.ts +++ b/src/commands/ext-dev-register.ts @@ -12,7 +12,7 @@ import { } from "../extensions/extensionsHelper"; import { acceptLatestPublisherTOS } from "../extensions/tos"; import { requirePermissions } from "../requirePermissions"; -import { FirebaseError } from "../error"; +import { FirebaseError, getErrMsg, getErrStatus } from "../error"; import * as utils from "../utils"; import { PublisherProfile } from "../extensions/types"; @@ -42,8 +42,8 @@ export const command = new Command("ext:dev:register") let profile: PublisherProfile; try { profile = await registerPublisherProfile(projectId, publisherId); - } catch (err: any) { - if (err.status === 409) { + } catch (err: unknown) { + if (getErrStatus(err) === 409) { const error = `Couldn't register the publisher ID '${clc.bold(publisherId)}' to the project '${clc.bold( projectId, @@ -59,7 +59,7 @@ export const command = new Command("ext:dev:register") throw new FirebaseError( `Failed to register publisher ID ${clc.bold(publisherId)} for project ${clc.bold( projectId, - )}: ${err.message}`, + )}: ${getErrMsg(err)}`, ); } utils.logLabeledSuccess( diff --git a/src/commands/ext-dev-upload.ts b/src/commands/ext-dev-upload.ts index 3bbec6e8078..81a46fe6bc9 100644 --- a/src/commands/ext-dev-upload.ts +++ b/src/commands/ext-dev-upload.ts @@ -16,7 +16,7 @@ import { findExtensionYaml } from "../extensions/localHelper"; import { consoleInstallLink } from "../extensions/publishHelpers"; import { ExtensionVersion, PublisherProfile } from "../extensions/types"; import { requireAuth } from "../requireAuth"; -import { FirebaseError } from "../error"; +import { FirebaseError, getErrStatus } from "../error"; import { acceptLatestPublisherTOS } from "../extensions/tos"; import * as utils from "../utils"; import { Options } from "../options"; @@ -80,8 +80,8 @@ export async function uploadExtensionAction( let profile: PublisherProfile | undefined; try { profile = await getPublisherProfile("-", publisherId); - } catch (err: any) { - if (err.status === 404) { + } catch (err: unknown) { + if (getErrStatus(err) === 404) { throw getMissingPublisherError(publisherId); } throw err; diff --git a/src/commands/ext-dev-usage.ts b/src/commands/ext-dev-usage.ts index ac448afa678..e9cff76b2e8 100644 --- a/src/commands/ext-dev-usage.ts +++ b/src/commands/ext-dev-usage.ts @@ -8,7 +8,7 @@ import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; import { buildMetricsTableRow, parseTimeseriesResponse } from "../extensions/metricsUtils"; import { getPublisherProfile, listExtensions } from "../extensions/publisherApi"; import { getPublisherProjectFromName, logPrefix } from "../extensions/extensionsHelper"; -import { FirebaseError } from "../error"; +import { FirebaseError, getErrMsg, getError } from "../error"; import { logger } from "../logger"; import { promptOnce } from "../prompt"; import { shortenUrl } from "../shortenUrl"; @@ -37,8 +37,8 @@ export const command = new Command("ext:dev:usage ") let extensions; try { extensions = await listExtensions(publisherId); - } catch (err: any) { - throw new FirebaseError(err); + } catch (err: unknown) { + throw new FirebaseError(getErrMsg(err)); } if (extensions.length < 1) { @@ -89,11 +89,11 @@ export const command = new Command("ext:dev:usage ") let response; try { response = await queryTimeSeries(query, projectNumber); - } catch (err: any) { + } catch (err: unknown) { throw new FirebaseError( `Error occurred when fetching usage data for extension ${extensionName}`, { - original: err, + original: getError(err), }, ); } diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 1d2c4d33a74..ae87a324750 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -5,7 +5,7 @@ import { displayExtensionVersionInfo } from "../extensions/displayExtensionInfo" import * as askUserForEventsConfig from "../extensions/askUserForEventsConfig"; import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; import { Command } from "../command"; -import { FirebaseError } from "../error"; +import { FirebaseError, getErrMsg, getError } from "../error"; import { logger } from "../logger"; import { getProjectId, needProjectId } from "../projectUtils"; import * as extensionsApi from "../extensions/extensionsApi"; @@ -151,11 +151,14 @@ export const command = new Command("ext:install [extensionRef]") nonInteractive: options.nonInteractive, force: options.force, }); - } catch (err: any) { + } catch (err: unknown) { if (!(err instanceof FirebaseError)) { - throw new FirebaseError(`Error occurred saving the extension to manifest: ${err.message}`, { - original: err, - }); + throw new FirebaseError( + `Error occurred saving the extension to manifest: ${getErrMsg(err)}`, + { + original: getError(err), + }, + ); } throw err; } diff --git a/src/commands/ext.ts b/src/commands/ext.ts index 8c93aa336d8..f48af49e1e5 100644 --- a/src/commands/ext.ts +++ b/src/commands/ext.ts @@ -41,7 +41,7 @@ export const command = new Command("ext") await requirePermissions(options, ["firebaseextensions.instances.list"]); const projectId = needProjectId(options); return listExtensions(projectId); - } catch (err: any) { + } catch (err: unknown) { return; } }); diff --git a/src/deploy/extensions/deploy.ts b/src/deploy/extensions/deploy.ts index dc0bd7a095d..1cfdee1dbfc 100644 --- a/src/deploy/extensions/deploy.ts +++ b/src/deploy/extensions/deploy.ts @@ -9,7 +9,13 @@ import { bulkCheckProductsProvisioned } from "../../extensions/provisioningHelpe import { handleSecretParams } from "./secrets"; import { checkBilling } from "./validate"; -export async function deploy(context: Context, options: Options, payload: Payload) { +/** + * Deploys extensions + * @param context The deploy context + * @param options The deploy options + * @param payload The deploy payload + */ +export async function deploy(context: Context, options: Options, payload: Payload): Promise { const projectId = needProjectId(options); // First, check that billing is enabled await checkBilling(projectId, options.nonInteractive); @@ -21,8 +27,10 @@ export async function deploy(context: Context, options: Options, payload: Payloa ...(payload.instancesToConfigure ?? []), ]); - // Then, check if the secrets used exist, and prompt to create them if not. - await handleSecretParams(payload, context.have!, options.nonInteractive); + if (context.have) { + // Then, check if the secrets used exist, and prompt to create them if not. + await handleSecretParams(payload, context.have, options.nonInteractive); + } // Then, run validateOnly calls. const errorHandler = new ErrorHandler(); diff --git a/src/deploy/extensions/deploymentSummary.ts b/src/deploy/extensions/deploymentSummary.ts index d8957191cd8..9775ae38b13 100644 --- a/src/deploy/extensions/deploymentSummary.ts +++ b/src/deploy/extensions/deploymentSummary.ts @@ -3,20 +3,25 @@ import * as clc from "colorette"; import * as planner from "./planner"; import * as refs from "../../extensions/refs"; -export const humanReadable = (dep: planner.InstanceSpec) => +/** + * humanReadable returns a human readable instanceID and reference + * @param dep An instance spec to get the information from + * @return a string indicating the instanceID and where it was installed from. + */ +export const humanReadable = (dep: planner.InstanceSpec): string => `${clc.bold(dep.instanceId)} (${ dep.ref ? `${refs.toExtensionVersionRef(dep.ref)}` : `Installed from local source` })`; -const humanReadableUpdate = (from: planner.InstanceSpec, to: planner.InstanceSpec) => { +const humanReadableUpdate = (from: planner.InstanceSpec, to: planner.InstanceSpec): string => { if ( from.ref && to.ref && from.ref.publisherId === to.ref.publisherId && from.ref.extensionId === to.ref.extensionId ) { - return `\t${clc.bold(from.instanceId)} (${refs.toExtensionVersionRef(from.ref!)} => ${ - to.ref?.version + return `\t${clc.bold(from.instanceId)} (${refs.toExtensionVersionRef(from.ref)} => ${ + to.ref?.version || "" })`; } else { const fromRef = from.ref @@ -27,6 +32,11 @@ const humanReadableUpdate = (from: planner.InstanceSpec, to: planner.InstanceSpe } }; +/** + * createsSummary returns a formatted string of instance to be created. + * @param toCreate a list of instances to create + * @return a formatted string of instances to create. + */ export function createsSummary(toCreate: planner.InstanceSpec[]): string { const instancesToCreate = toCreate.map((s) => `\t${humanReadable(s)}`).join("\n"); return toCreate.length @@ -34,6 +44,12 @@ export function createsSummary(toCreate: planner.InstanceSpec[]): string { : ""; } +/** + * updatesSummary returns a formatted string of instances to be updated + * @param toUpdate a list of instances to be updated + * @param have a list of extensions that are deployed + * @return a formatted string of instances to be updated + */ export function updatesSummary( toUpdate: planner.InstanceSpec[], have: planner.InstanceSpec[], @@ -41,7 +57,10 @@ export function updatesSummary( const instancesToUpdate = toUpdate .map((to) => { const from = have.find((exists) => exists.instanceId === to.instanceId); - return humanReadableUpdate(from!, to); + if (!from) { + return ""; + } + return humanReadableUpdate(from, to); }) .join("\n"); return toUpdate.length @@ -49,18 +68,28 @@ export function updatesSummary( : ""; } -export function configuresSummary(toConfigure: planner.InstanceSpec[]) { +/** + * configureSummary shows a summary of what can be configured. + * @param toConfigure The list of instances to configure + * @return a formatted string of what will be configured + */ +export function configuresSummary(toConfigure: planner.InstanceSpec[]): string { const instancesToConfigure = toConfigure.map((s) => `\t${humanReadable(s)}`).join("\n"); return toConfigure.length ? `The following extension instances will be configured:\n${instancesToConfigure}\n` : ""; } -export function deletesSummary(toDelete: planner.InstanceSpec[], isDynamic: boolean) { +/** + * deleteSummary shows a summary of what can be deleted. + * @param toDelete The list of instances that could be deleted + * @param isDynamic If we are looking at extensions defined dynamically or not + * @return A formatted string containing the instances to be deleted + */ +export function deletesSummary(toDelete: planner.InstanceSpec[], isDynamic: boolean): string { const instancesToDelete = toDelete.map((s) => `\t${humanReadable(s)}`).join("\n"); const definedLocation = isDynamic ? "your local source code" : "'firebase.json'"; return toDelete.length ? `The following extension instances are found in your project but do not exist in ${definedLocation}:\n${instancesToDelete}\n` : ""; } -// diff --git a/src/deploy/extensions/planner.ts b/src/deploy/extensions/planner.ts index 8ae6260abb6..00e4e28035f 100644 --- a/src/deploy/extensions/planner.ts +++ b/src/deploy/extensions/planner.ts @@ -2,7 +2,7 @@ import * as semver from "semver"; import * as extensionsApi from "../../extensions/extensionsApi"; import * as refs from "../../extensions/refs"; -import { FirebaseError } from "../../error"; +import { FirebaseError, getErrMsg } from "../../error"; import { getFirebaseProjectParams, isLocalPath, @@ -85,7 +85,10 @@ export async function getExtension(i: InstanceSpec): Promise { return i.extension; } -/** Caching fetcher for the corresponding ExtensionSpec for an instance spec. +/** + * Caching fetcher for the corresponding ExtensionSpec for an instance spec. + * @param i The instance spec to get the extension spec for. + * @return the extension spec for the instance spec. */ export async function getExtensionSpec(i: InstanceSpec): Promise { if (!i.extensionSpec) { @@ -94,25 +97,33 @@ export async function getExtensionSpec(i: InstanceSpec): Promise i.extensionSpec = extensionVersion.spec; } else if (i.localPath) { i.extensionSpec = await readExtensionYaml(i.localPath); - i.extensionSpec!.postinstallContent = await readPostinstall(i.localPath); + i.extensionSpec.postinstallContent = await readPostinstall(i.localPath); } else { throw new FirebaseError("InstanceSpec had no ref or localPath, unable to get extensionSpec"); } } - return i.extensionSpec!; + if (!i.extensionSpec) { + throw new FirebaseError("Internal error getting extension"); + } + return i.extensionSpec; } /** * haveDynamic checks a project for what extension instances created by SDK are * currently installed, and returns them as a list of instanceSpecs. - * @param projectId + * @param projectId The projectId we are getting a list of extensions for + * @return a list of extensions deployed from functions deploy */ export async function haveDynamic(projectId: string): Promise { return (await extensionsApi.listInstances(projectId)) .filter((i) => i.labels?.createdBy === "SDK") .map((i) => { + const instanceId = i.name.split("/").pop(); + if (!instanceId) { + throw new FirebaseError(`Internal error getting instanceId from ${i.name}`); + } const dep: DeploymentInstanceSpec = { - instanceId: i.name.split("/").pop()!, + instanceId, params: i.config.params, systemParams: i.config.systemParams ?? {}, allowedEventTypes: i.config.allowedEventTypes, @@ -132,14 +143,19 @@ export async function haveDynamic(projectId: string): Promise { return (await extensionsApi.listInstances(projectId)) .filter((i) => !(i.labels?.createdBy === "SDK")) .map((i) => { + const instanceId = i.name.split("/").pop(); + if (!instanceId) { + throw new FirebaseError(`Internal error getting instanceId from ${i.name}`); + } const dep: DeploymentInstanceSpec = { - instanceId: i.name.split("/").pop()!, + instanceId, params: i.config.params, systemParams: i.config.systemParams ?? {}, allowedEventTypes: i.config.allowedEventTypes, @@ -161,6 +177,7 @@ export async function have(projectId: string): Promise /** * wantDynamic checks the user's code for Extension SDKs usage and returns * any extensions the user has defined that way. + * @param args The various args passed to wantDynamic * @param args.projectId The project we are deploying to * @param args.projectNumber The project number we are deploying to. * @param args.extensions The extensions section of firebase.json @@ -225,6 +242,7 @@ export async function wantDynamic(args: { /** * want checks firebase.json and the extensions directory for which extensions * the user wants installed on their project. + * @param args The various args passed to want. * @param args.projectId The project we are deploying to * @param args.projectNumber The project number we are deploying to. Used for checking .env files. * @param args.aliases An array of aliases for the project we are deploying to. Used for checking .env files. @@ -232,6 +250,7 @@ export async function wantDynamic(args: { * @param args.extensions The extensions section of firebase.jsonm * @param args.emulatorMode Whether the output will be used by the Extensions emulator. * If true, this will check {instanceId}.env.local for params and will respect `demo-` project rules. + * @return an array of deployment instance specs to deploy. */ export async function want(args: { projectId: string; @@ -296,8 +315,8 @@ export async function want(args: { eventarcChannel: eventarcChannel, }); } - } catch (err: any) { - logger.debug(`Got error reading extensions entry ${e}: ${err}`); + } catch (err: unknown) { + logger.debug(`Got error reading extensions entry ${e[0]} (${e[1]}): ${getErrMsg(err)}`); errors.push(err as FirebaseError); } } @@ -311,7 +330,6 @@ export async function want(args: { /** * Resolves a semver string to the max matching version. If no version is specified, * it will default to the extension's latest approved version if set, otherwise to the latest version. - * * @param ref the extension version ref * @param extension the extension (optional) */ diff --git a/src/deploy/extensions/prepare.ts b/src/deploy/extensions/prepare.ts index a60fed542ee..61474b7eecf 100644 --- a/src/deploy/extensions/prepare.ts +++ b/src/deploy/extensions/prepare.ts @@ -22,6 +22,18 @@ import { Build } from "../functions/build"; import { getEndpointFilters } from "../functions/functionsDeployHelper"; import { DeployOptions } from ".."; +const matchesInstanceId = (dep: planner.InstanceSpec) => (test: planner.InstanceSpec) => { + return dep.instanceId === test.instanceId; +}; + +const isUpdate = (dep: planner.InstanceSpec) => (test: planner.InstanceSpec) => { + return dep.instanceId === test.instanceId && !refs.equal(dep.ref, test.ref); +}; + +const isConfigure = (dep: planner.InstanceSpec) => (test: planner.InstanceSpec) => { + return dep.instanceId === test.instanceId && refs.equal(dep.ref, test.ref); +}; + // This is called by prepare and also prepareDynamicExtensions async function prepareHelper( context: Context, @@ -30,7 +42,7 @@ async function prepareHelper( wantExtensions: planner.DeploymentInstanceSpec[], haveExtensions: planner.DeploymentInstanceSpec[], isDynamic: boolean, -) { +): Promise { const projectId = needProjectId(options); context.want = wantExtensions; @@ -141,13 +153,19 @@ async function prepareHelper( } } -// This is called by functions/prepare so we can deploy the extensions defined by SDKs +/** + * This is called by functions/prepare so we can deploy the extensions defined by SDKs + * @param context The prepare context + * @param options The prepare options + * @param payload The prepare payload + * @param builds firebase functions builds + */ export async function prepareDynamicExtensions( context: Context, options: DeployOptions, payload: Payload, builds: Record, -) { +): Promise { const filters = getEndpointFilters(options); const extensions = extractExtensionsFromBuilds(builds, filters); const projectId = needProjectId(options); @@ -182,7 +200,17 @@ export async function prepareDynamicExtensions( ); } -export async function prepare(context: Context, options: DeployOptions, payload: Payload) { +/** + * static Extensions prepare (not to be confused with dynamic extensions) + * @param context The prepare context + * @param options The prepare options + * @param payload The prepare payload + */ +export async function prepare( + context: Context, + options: DeployOptions, + payload: Payload, +): Promise { context.extensionsStartTime = Date.now(); const projectId = needProjectId(options); const projectNumber = await needProjectNumber(options); @@ -197,7 +225,7 @@ export async function prepare(context: Context, options: DeployOptions, payload: projectNumber, aliases, projectDir, - extensions: options.config.get("extensions", {}), + extensions: options.config.get("extensions", {}) as Record, }); const haveExtensions = await planner.have(projectId); @@ -211,15 +239,3 @@ export async function prepare(context: Context, options: DeployOptions, payload: false /* isDynamic */, ); } - -const matchesInstanceId = (dep: planner.InstanceSpec) => (test: planner.InstanceSpec) => { - return dep.instanceId === test.instanceId; -}; - -const isUpdate = (dep: planner.InstanceSpec) => (test: planner.InstanceSpec) => { - return dep.instanceId === test.instanceId && !refs.equal(dep.ref, test.ref); -}; - -const isConfigure = (dep: planner.InstanceSpec) => (test: planner.InstanceSpec) => { - return dep.instanceId === test.instanceId && refs.equal(dep.ref, test.ref); -}; diff --git a/src/error.ts b/src/error.ts index a7c87c7cb0d..ac8c4e0adf6 100644 --- a/src/error.ts +++ b/src/error.ts @@ -68,6 +68,18 @@ export function getErrStatus(err: unknown, defaultStatus?: number): number { return defaultStatus || DEFAULT_STATUS; } +/** + * Safely gets an error object from an unknown object + * @param err The error to get an Error for. + * @return an Error object + */ +export function getError(err: unknown): Error { + if (err instanceof Error) { + return err; + } + return Error(getErrMsg(err)); +} + /** * Checks if a FirebaseError is caused by attempting something * that requires billing enabled while billing is not enabled. diff --git a/src/operation-poller.spec.ts b/src/operation-poller.spec.ts index 1a9a9121cf4..6db9a7f1198 100644 --- a/src/operation-poller.spec.ts +++ b/src/operation-poller.spec.ts @@ -93,7 +93,7 @@ describe("OperationPoller", () => { let error; try { await pollOperation(pollerOptions); - } catch (err: any) { + } catch (err: unknown) { error = err; } expect(error).to.be.instanceOf(TimeoutError); diff --git a/src/requireDatabaseInstance.ts b/src/requireDatabaseInstance.ts index e11f56505fe..a5eb6cc2bf4 100644 --- a/src/requireDatabaseInstance.ts +++ b/src/requireDatabaseInstance.ts @@ -1,5 +1,5 @@ import * as clc from "colorette"; -import { FirebaseError } from "./error"; +import { FirebaseError, getError } from "./error"; import { getDefaultDatabaseInstance } from "./getDefaultDatabaseInstance"; /** @@ -21,9 +21,9 @@ export async function requireDatabaseInstance(options: any): Promise { let instance; try { instance = await getDefaultDatabaseInstance(options); - } catch (err: any) { + } catch (err: unknown) { throw new FirebaseError(`Failed to get details for project: ${options.project}.`, { - original: err, + original: getError(err), }); } if (instance === "") { diff --git a/src/requirePermissions.ts b/src/requirePermissions.ts index 9389f0b3c40..f2291f43c6a 100644 --- a/src/requirePermissions.ts +++ b/src/requirePermissions.ts @@ -2,7 +2,7 @@ import { bold } from "colorette"; import { getProjectId } from "./projectUtils"; import { requireAuth } from "./requireAuth"; import { logger } from "./logger"; -import { FirebaseError } from "./error"; +import { FirebaseError, getErrMsg } from "./error"; import { testIamPermissions } from "./gcp/iam"; // Permissions required for all commands. @@ -37,8 +37,8 @@ export async function requirePermissions(options: any, permissions: string[] = [ )}:\n\n ${iamResult.missing.join("\n ")}`, ); } - } catch (err: any) { - logger.debug(`[iam] error while checking permissions, command may fail: ${err}`); + } catch (err: unknown) { + logger.debug(`[iam] error while checking permissions, command may fail: ${getErrMsg(err)}`); return; } } diff --git a/src/rulesDeploy.ts b/src/rulesDeploy.ts index ddd8cc81e07..9f8207b65cb 100644 --- a/src/rulesDeploy.ts +++ b/src/rulesDeploy.ts @@ -4,7 +4,7 @@ import * as fs from "fs-extra"; import * as gcp from "./gcp"; import { logger } from "./logger"; -import { FirebaseError } from "./error"; +import { FirebaseError, getErrStatus } from "./error"; import * as utils from "./utils"; import { promptOnce } from "./prompt"; @@ -202,8 +202,8 @@ export class RulesDeploy { this.rulesetNames[filename] = await rulesetName; createdRulesetNames.push(await rulesetName); } - } catch (err: any) { - if (err.status !== QUOTA_EXCEEDED_STATUS_CODE) { + } catch (err: unknown) { + if (getErrStatus(err) !== QUOTA_EXCEEDED_STATUS_CODE) { throw err; } utils.logLabeledBullet(RulesetType[this.type], "quota exceeded error while uploading rules"); diff --git a/src/utils.ts b/src/utils.ts index e6dc0d32ecb..d58b5ce3741 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -19,7 +19,7 @@ import { stripVTControlCharacters } from "node:util"; import { getPortPromise as getPort } from "portfinder"; import { configstore } from "./configstore"; -import { FirebaseError } from "./error"; +import { FirebaseError, getErrMsg, getError } from "./error"; import { logger, LogLevel } from "./logger"; import { LogDataOrUndefined } from "./emulator/loggingEmulator"; import { promptOnce } from "./prompt"; @@ -424,7 +424,7 @@ export async function promiseWhile( return resolve(res); } setTimeout(run, interval); - } catch (err: any) { + } catch (err: unknown) { return promiseReject(err); } }; @@ -536,7 +536,7 @@ export async function promiseWithSpinner(action: () => Promise, message: s try { data = await action(); spinner.succeed(); - } catch (err: any) { + } catch (err: unknown) { spinner.fail(); throw err; } @@ -871,8 +871,8 @@ export function readFileFromDirectory( export function wrappedSafeLoad(source: string): any { try { return yaml.parse(source); - } catch (err: any) { - throw new FirebaseError(`YAML Error: ${err.message}`, { original: err }); + } catch (err: unknown) { + throw new FirebaseError(`YAML Error: ${getErrMsg(err)}`, { original: getError(err) }); } }