diff --git a/src/utils/command-helpers.ts b/src/utils/command-helpers.ts index 6397cddaf86..701768ae614 100644 --- a/src/utils/command-helpers.ts +++ b/src/utils/command-helpers.ts @@ -102,8 +102,7 @@ export const pollForToken = async ({ } return accessToken } catch (error_) { - // @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'. - if (error_.name === 'TimeoutError') { + if (error_ instanceof Error && error_.name === 'TimeoutError') { return logAndThrowError( `Timed out waiting for authorization. If you do not have a ${chalk.bold.greenBright( 'Netlify', diff --git a/src/utils/dev.ts b/src/utils/dev.ts index 8f929be9270..12b30ce0f47 100644 --- a/src/utils/dev.ts +++ b/src/utils/dev.ts @@ -41,8 +41,7 @@ const ENV_VAR_SOURCES = { const ERROR_CALL_TO_ACTION = "Double-check your login status with 'netlify status' or contact support with details of your error." -// @ts-expect-error TS(7031) FIXME: Binding element 'site' implicitly has an 'any' typ... Remove this comment to see the full error message -const validateSiteInfo = ({ site, siteInfo }) => { +const validateSiteInfo = ({ site, siteInfo }: { site: { id?: string }; siteInfo: SiteInfo }) => { if (isEmpty(siteInfo)) { return logAndThrowError( `Failed to retrieve project information for project ${chalk.yellow(site.id)}. ${ERROR_CALL_TO_ACTION}`, @@ -78,10 +77,13 @@ const getAccounts = async ({ api }: { api: NetlifyAPI }) => { } } -// @ts-expect-error TS(7031) FIXME: Binding element 'api' implicitly has an 'any' type... Remove this comment to see the full error message -const getAddons = async ({ api, site }) => { +const getAddons = async ({ api, site }: { api: NetlifyAPI; site: { id?: string } }) => { + const { id } = site + if (!id) { + return [] + } try { - const addons = await api.listServiceInstancesForSite({ siteId: site.id }) + const addons = await api.listServiceInstancesForSite({ siteId: id }) return addons } catch (error_) { return logAndThrowError( @@ -92,21 +94,21 @@ const getAddons = async ({ api, site }) => { } } -// @ts-expect-error TS(7031) FIXME: Binding element 'addons' implicitly has an 'any' t... Remove this comment to see the full error message -const getAddonsInformation = ({ addons, siteInfo }) => { +type Addon = Awaited>[number] + +const getAddonsInformation = ({ addons, siteInfo }: { addons: Addon[]; siteInfo: SiteInfo }) => { const urls = Object.fromEntries( - // @ts-expect-error TS(7006) FIXME: Parameter 'addon' implicitly has an 'any' type. addons.map((addon) => [addon.service_slug, `${siteInfo.ssl_url}${addon.service_path}`]), ) - // @ts-expect-error TS(7006) FIXME: Parameter 'addon' implicitly has an 'any' type. const env = Object.assign({}, ...addons.map((addon) => addon.env)) return { urls, env } } const getSiteAccount = ({ accounts, siteInfo }: { accounts: Account[]; siteInfo: SiteInfo }): Account | undefined => { - const siteAccount = accounts.find((account) => account.slug === siteInfo.account_slug) + const { account_id: accountId, account_slug: accountSlug } = siteInfo + const siteAccount = accounts.find((account) => account.slug === accountSlug || account.id === accountId) if (!siteAccount) { - warn(`Could not find account for project '${siteInfo.name}' with account slug '${siteInfo.account_slug}'`) + warn(`Could not find account for project '${siteInfo.name}' with account slug '${accountSlug}'`) return undefined } return siteAccount @@ -155,7 +157,7 @@ export const getSiteInformation = async ({ return { addonsUrls, siteUrl: siteInfo.ssl_url, - accountId: account?.id, + accountId: account?.id ?? siteInfo.account_id, capabilities: { backgroundFunctions: supportsBackgroundFunctions(account), aiGatewayDisabled: siteInfo.capabilities?.ai_gateway_disabled ?? false, @@ -181,10 +183,11 @@ export const getSiteInformation = async ({ } } -// @ts-expect-error TS(7006) FIXME: Parameter 'source' implicitly has an 'any' type. -const getEnvSourceName = (source) => { - // @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message - const { name = source, printFn = chalk.green } = ENV_VAR_SOURCES[source] || {} +const getEnvSourceName = (source: string) => { + const sourceConfig = ( + ENV_VAR_SOURCES as Record string } | undefined> + )[source] + const { name = source, printFn = chalk.green } = sourceConfig || {} return printFn(name) } @@ -192,10 +195,18 @@ const getEnvSourceName = (source) => { /** * @param {{devConfig: any, env: Record, site: any}} param0 */ -// @ts-expect-error TS(7031) FIXME: Binding element 'devConfig' implicitly has an 'any... Remove this comment to see the full error message -export const getDotEnvVariables = async ({ devConfig, env, site }): Promise => { - const dotEnvFiles = await loadDotEnvFiles({ envFiles: devConfig.envFiles, projectDir: site.root }) - // @ts-expect-error TS(2339) FIXME: Property 'env' does not exist on type '{ warning: ... Remove this comment to see the full error message +export const getDotEnvVariables = async ({ + devConfig, + env, + site, +}: { + devConfig: { envFiles?: string[]; env_files?: string[]; [key: string]: unknown } + env: EnvironmentVariables + site: { root?: string; [key: string]: unknown } +}): Promise => { + const envFiles = devConfig.envFiles || devConfig.env_files + // eslint-disable-next-line no-restricted-properties + const dotEnvFiles = await loadDotEnvFiles({ envFiles, projectDir: site.root || process.cwd() }) dotEnvFiles.forEach(({ env: fileEnv, file }) => { const newSourceName = `${file} file` @@ -272,11 +283,17 @@ export const acquirePort = async ({ return acquiredPort } -// @ts-expect-error TS(7006) FIXME: Parameter 'fn' implicitly has an 'any' type. -export const processOnExit = (fn) => { +export const processOnExit = (fn: (codeOrSignal: string | number) => void | Promise) => { const signals = ['SIGINT', 'SIGTERM', 'SIGQUIT', 'SIGHUP', 'exit'] signals.forEach((signal) => { - process.on(signal, fn) + process.on(signal, (codeOrSignal) => { + const result = fn(codeOrSignal) + if (result instanceof Promise) { + result.catch(() => { + // ignore + }) + } + }) }) } diff --git a/src/utils/dot-env.ts b/src/utils/dot-env.ts index 27cebc92163..c8f2de88845 100644 --- a/src/utils/dot-env.ts +++ b/src/utils/dot-env.ts @@ -7,28 +7,46 @@ import { isFileAsync } from '../lib/fs.js' import { warn } from './command-helpers.js' -// @ts-expect-error TS(7031) FIXME: Binding element 'envFiles' implicitly has an 'any'... Remove this comment to see the full error message -export const loadDotEnvFiles = async function ({ envFiles, projectDir }) { +export type DotEnvFileResult = { + file: string + env: Record +} + +export type DotEnvWarningResult = { + warning: string +} + +export type DotEnvResult = DotEnvFileResult | DotEnvWarningResult + +export const loadDotEnvFiles = async function ({ + envFiles, + projectDir, +}: { + envFiles?: string[] + projectDir: string +}): Promise { const response = await tryLoadDotEnvFiles({ projectDir, dotenvFiles: envFiles }) - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - const filesWithWarning = response.filter((el) => el.warning) + const filesWithWarning = response.filter((el): el is DotEnvWarningResult => 'warning' in el) filesWithWarning.forEach((el) => { - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. warn(el.warning) }) - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - return response.filter((el) => el.file && el.env) + return response.filter((el): el is DotEnvFileResult => 'file' in el && 'env' in el) } // in the user configuration, the order is highest to lowest const defaultEnvFiles = ['.env.development.local', '.env.local', '.env.development', '.env'] -// @ts-expect-error TS(7031) FIXME: Binding element 'projectDir' implicitly has an 'an... Remove this comment to see the full error message -export const tryLoadDotEnvFiles = async ({ dotenvFiles = defaultEnvFiles, projectDir }) => { +export const tryLoadDotEnvFiles = async ({ + dotenvFiles = defaultEnvFiles, + projectDir, +}: { + dotenvFiles?: string[] + projectDir: string +}): Promise => { const results = await Promise.all( - dotenvFiles.map(async (file) => { + dotenvFiles.map(async (file): Promise => { const filepath = path.resolve(projectDir, file) try { const isFile = await isFileAsync(filepath) @@ -37,8 +55,9 @@ export const tryLoadDotEnvFiles = async ({ dotenvFiles = defaultEnvFiles, projec } } catch (error) { return { - // @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'. - warning: `Failed reading env variables from file: ${filepath}: ${error.message}`, + warning: `Failed reading env variables from file: ${filepath}: ${ + error instanceof Error ? error.message : String(error) + }`, } } const content = await readFile(filepath, 'utf-8') @@ -48,5 +67,5 @@ export const tryLoadDotEnvFiles = async ({ dotenvFiles = defaultEnvFiles, projec ) // we return in order of lowest to highest priority - return results.filter(Boolean).reverse() + return results.filter((result): result is DotEnvResult => Boolean(result)).reverse() } diff --git a/src/utils/telemetry/report-error.ts b/src/utils/telemetry/report-error.ts index 81fc774de4f..e3fb543c0ab 100644 --- a/src/utils/telemetry/report-error.ts +++ b/src/utils/telemetry/report-error.ts @@ -2,7 +2,9 @@ import os from 'os' import { dirname, join } from 'path' import process, { version as nodejsVersion } from 'process' import { fileURLToPath } from 'url' +import { inspect } from 'util' +import { type Event } from '@bugsnag/js' import { getGlobalConfigStore } from '@netlify/dev-utils' import { isCI } from 'ci-info' @@ -12,44 +14,75 @@ import { cliVersion } from './utils.js' const dirPath = dirname(fileURLToPath(import.meta.url)) +interface ReportErrorConfig { + severity?: Event['severity'] + metadata?: Record> +} + /** - * - * @param {import('@bugsnag/js').NotifiableError} error - * @param {object} config - * @param {import('@bugsnag/js').Event['severity']} config.severity - * @param {Record>} [config.metadata] - * @returns {Promise} + * Report an error to telemetry */ -// @ts-expect-error TS(7006) FIXME: Parameter 'error' implicitly has an 'any' type. -export const reportError = async function (error, config = {}) { +export const reportError = async function (error: unknown, config: ReportErrorConfig = {}): Promise { if (isCI) { return } // convert a NotifiableError to an error class - const err = error instanceof Error ? error : typeof error === 'string' ? new Error(error) : error + let err: Error + if (error instanceof Error) { + err = error + } else if (typeof error === 'string') { + err = new Error(error) + } else if (typeof error === 'object' && error !== null && ('message' in error || 'name' in error)) { + const errorObject = error as Record + const message = typeof errorObject.message === 'string' ? errorObject.message : 'Unknown error' + err = new Error(message) + if (typeof errorObject.name === 'string') { + err.name = errorObject.name + } + if (typeof errorObject.stack === 'string') { + err.stack = errorObject.stack + } + } else { + err = new Error(typeof error === 'object' && error !== null ? inspect(error) : String(error)) + } const globalConfig = await getGlobalConfigStore() - const options = JSON.stringify({ - type: 'error', - data: { - message: err.message, - name: err.name, - stack: err.stack, - cause: err.cause, - // @ts-expect-error TS(2339) FIXME: Property 'severity' does not exist on type '{}'. - severity: config.severity, - user: { - id: globalConfig.get('userId'), + let options: string + try { + options = JSON.stringify({ + type: 'error', + data: { + message: err.message, + name: err.name, + stack: err.stack, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + cause: (err as any).cause ? inspect((err as any).cause) : undefined, + severity: config.severity, + user: { + id: globalConfig.get('userId'), + }, + metadata: config.metadata ? inspect(config.metadata) : undefined, + osName: `${os.platform()}-${os.arch()}`, + cliVersion, + nodejsVersion, }, - // @ts-expect-error TS(2339) FIXME: Property 'metadata' does not exist on type '{}'. - metadata: config.metadata, - osName: `${os.platform()}-${os.arch()}`, - cliVersion, - nodejsVersion, - }, - }) + }) + } catch { + // If stringify fails, we at least try to report a simplified error + options = JSON.stringify({ + type: 'error', + data: { + message: `Error reporting failed: ${err.message}`, + name: 'ErrorReportingError', + severity: config.severity, + osName: `${os.platform()}-${os.arch()}`, + cliVersion, + nodejsVersion, + }, + }) + } // spawn detached child process to handle send and wait for the http request to finish // otherwise it can get canceled diff --git a/src/utils/types.ts b/src/utils/types.ts index f2c33e4b541..76aa80304a0 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -219,7 +219,14 @@ export interface Template { } type EnvironmentVariableScope = 'builds' | 'functions' | 'runtime' | 'post_processing' -export type EnvironmentVariableSource = 'account' | 'addons' | 'configFile' | 'general' | 'internal' | 'ui' +export type EnvironmentVariableSource = + | 'account' + | 'addons' + | 'configFile' + | 'general' + | 'internal' + | 'ui' + | (string & {}) export type EnvironmentVariables = Record< string,