diff --git a/src/commands/base-command.ts b/src/commands/base-command.ts index 4ec2c680369..90cfda60481 100644 --- a/src/commands/base-command.ts +++ b/src/commands/base-command.ts @@ -410,7 +410,6 @@ export default class BaseCommand extends Command { const authLink = `${webUI}/authorize?response_type=ticket&ticket=${ticket.id}` log(`Opening ${authLink}`) - // @ts-expect-error TS(2345) FIXME: Argument of type '{ url: string; }' is not assigna... Remove this comment to see the full error message await openBrowser({ url: authLink }) const accessToken = await pollForToken({ diff --git a/src/commands/deploy/deploy.ts b/src/commands/deploy/deploy.ts index ef4c0b4396b..4ae56fe9546 100644 --- a/src/commands/deploy/deploy.ts +++ b/src/commands/deploy/deploy.ts @@ -207,24 +207,14 @@ const validateFolders = async ({ return { deployFolderStat, functionsFolderStat } } -/** - * @param {object} config - * @param {string} config.deployFolder - * @param {*} config.site - * @returns - */ -// @ts-expect-error TS(7031) FIXME: Binding element 'deployFolder' implicitly has an '... Remove this comment to see the full error message -const getDeployFilesFilter = ({ deployFolder, site }) => { +const getDeployFilesFilter = ({ deployFolder, site }: { deployFolder: string; site: { root: string } }) => { // site.root === deployFolder can happen when users run `netlify deploy --dir .` // in that specific case we don't want to publish the repo node_modules // when site.root !== deployFolder the behaviour matches our buildbot const skipNodeModules = site.root === deployFolder - /** - * @param {string} filename - */ - // @ts-expect-error TS(7006) FIXME: Parameter 'filename' implicitly has an 'any' type. - return (filename) => { + return (filename: string) => { + // TODO(serhalp) Per types, this should not be possible. Confirm and remove this check. if (filename == null) { return false } @@ -269,16 +259,20 @@ const prepareProductionDeploy = async ({ api, siteData }) => { log('Deploying to main site URL...') } -// @ts-expect-error TS(7006) FIXME: Parameter 'actual' implicitly has an 'any' type. -const hasErrorMessage = (actual, expected) => { +const hasErrorMessage = (actual: unknown, expected: string): boolean => { if (typeof actual === 'string') { return actual.includes(expected) } return false } -// @ts-expect-error TS(7031) FIXME: Binding element 'error_' implicitly has an 'any' t... Remove this comment to see the full error message -const reportDeployError = ({ error_, failAndExit }) => { +const reportDeployError = ({ + error_, + failAndExit, +}: { + error_: (Error & { json?: Record; status?: number }) | any + failAndExit: (errorOrMessage: Error | string) => void +}) => { switch (true) { case error_.name === 'JSONHTTPError': { const message = error_?.json?.message ?? '' @@ -488,7 +482,6 @@ const runDeploy = async ({ const { headers } = await parseAllHeaders({ configHeaders: config.headers, - // @ts-expect-error TS(2322) FIXME: Type 'string' is not assignable to type 'never'. headersFiles: [headersPath], minimal: true, }) @@ -906,7 +899,6 @@ export const deploy = async (options: OptionValues, command: BaseCommand) => { if (options.open) { const urlToOpen = deployToProduction ? results.siteUrl : results.deployUrl - // @ts-expect-error TS(2345) FIXME: Argument of type '{ url: any; }' is not assignable... Remove this comment to see the full error message await openBrowser({ url: urlToOpen }) exit() } diff --git a/src/commands/functions/functions-create.ts b/src/commands/functions/functions-create.ts index 10d98945f8e..530f2273ba1 100644 --- a/src/commands/functions/functions-create.ts +++ b/src/commands/functions/functions-create.ts @@ -640,7 +640,7 @@ const handleAddonDidInstall = async ({ addonCreated, addonDidInstall, command, f return } - await injectEnvVariables({ + injectEnvVariables({ devConfig: { ...config.dev }, env: command.netlify.cachedConfig.env, site: command.netlify.site, diff --git a/src/commands/open/open-admin.ts b/src/commands/open/open-admin.ts index bd34c37b205..68690eb4cc4 100644 --- a/src/commands/open/open-admin.ts +++ b/src/commands/open/open-admin.ts @@ -12,7 +12,6 @@ export const openAdmin = async (options: OptionValues, command: BaseCommand) => log(`Opening "${siteInfo.name}" site admin UI:`) log(`> ${siteInfo.admin_url}`) - // @ts-expect-error TS(2345) FIXME: Argument of type '{ url: any; }' is not assignable... Remove this comment to see the full error message await openBrowser({ url: siteInfo.admin_url }) exit() } diff --git a/src/commands/open/open-site.ts b/src/commands/open/open-site.ts index a4730a096fc..46969c38831 100644 --- a/src/commands/open/open-site.ts +++ b/src/commands/open/open-site.ts @@ -13,7 +13,6 @@ export const openSite = async (options: OptionValues, command: BaseCommand) => { log(`Opening "${siteInfo.name}" site url:`) log(`> ${url}`) - // @ts-expect-error TS(2345) FIXME: Argument of type '{ url: any; }' is not assignable... Remove this comment to see the full error message await openBrowser({ url }) exit() } diff --git a/src/commands/types.d.ts b/src/commands/types.d.ts index f2ddc80de61..4c5d3d56908 100644 --- a/src/commands/types.d.ts +++ b/src/commands/types.d.ts @@ -5,7 +5,7 @@ import type { NetlifyAPI } from 'netlify' import type { FrameworksAPIPaths } from "../utils/frameworks-api.ts"; import type StateConfig from '../utils/state-config.js' import type { Account } from "../utils/types.ts"; -import type { CachedConfig } from "../utils/build.js" +import type { CachedConfig } from "../lib/build.js" // eslint-disable-next-line @typescript-eslint/no-explicit-any type $TSFixMe = any; @@ -15,7 +15,7 @@ export type NetlifySite = { configPath?: string siteId?: string get id(): string | undefined - set id(id: string): void + set id(id: string) } type PatchedConfig = NetlifyTOML & Pick & { diff --git a/src/lib/functions/server.ts b/src/lib/functions/server.ts index 41974583788..abe57b2821a 100644 --- a/src/lib/functions/server.ts +++ b/src/lib/functions/server.ts @@ -259,7 +259,7 @@ interface GetFunctionsServerOptions { functionsRegistry: FunctionsRegistry siteUrl: string siteInfo?: $TSFixMe - accountId: string + accountId?: string | undefined geoCountry: string offline: boolean state: $TSFixMe diff --git a/src/utils/dev.ts b/src/utils/dev.ts index 5e08de4aafd..2478accddcd 100644 --- a/src/utils/dev.ts +++ b/src/utils/dev.ts @@ -7,6 +7,11 @@ import { supportsBackgroundFunctions } from '../lib/account.js' import { NETLIFYDEVLOG, chalk, error, log, warn, APIError } from './command-helpers.js' import { loadDotEnvFiles } from './dot-env.js' +import type { NetlifyAPI } from 'netlify' +import type { SiteInfo } from './types.js' +import { CachedConfig } from '../lib/build.js' +import { NetlifySite } from '../commands/types.js' +import { DevConfig } from '../commands/dev/types.js' // Possible sources of environment variables. For the purpose of printing log messages only. Order does not matter. const ENV_VAR_SOURCES = { @@ -39,15 +44,13 @@ 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: NetlifySite; siteInfo: SiteInfo }): void => { if (isEmpty(siteInfo)) { error(`Failed retrieving site information for site ${chalk.yellow(site.id)}. ${ERROR_CALL_TO_ACTION}`) } } -// @ts-expect-error TS(7031) FIXME: Binding element 'api' implicitly has an 'any' type... Remove this comment to see the full error message -const getAccounts = async ({ api }) => { +const getAccounts = async ({ api }: { api: NetlifyAPI }) => { try { const accounts = await api.listAccountsForUser() return accounts @@ -56,9 +59,9 @@ const getAccounts = async ({ api }) => { } } -// @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: NetlifySite }) => { try { + // @ts-expect-error(serhalp) One of three types is incorrect here (is `site.id` optional?). Dig and fix. const addons = await api.listServiceInstancesForSite({ siteId: site.id }) return addons } catch (error_) { @@ -70,20 +73,17 @@ 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 Addons = Awaited> +const getAddonsInformation = ({ addons, siteInfo }: { addons: Addons; 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 } } -// @ts-expect-error TS(7031) FIXME: Binding element 'accounts' implicitly has an 'any'... Remove this comment to see the full error message -const getSiteAccount = ({ accounts, siteInfo }) => { - // @ts-expect-error TS(7006) FIXME: Parameter 'account' implicitly has an 'any' type. +type Accounts = Awaited> +const getSiteAccount = ({ accounts, siteInfo }: { accounts: Accounts; siteInfo: SiteInfo }) => { const siteAccount = accounts.find((account) => account.slug === siteInfo.account_slug) if (!siteAccount) { warn(`Could not find account for site '${siteInfo.name}' with account slug '${siteInfo.account_slug}'`) @@ -98,17 +98,17 @@ const SYNCHRONOUS_FUNCTION_TIMEOUT = 30 // default 15 minutes for background functions const BACKGROUND_FUNCTION_TIMEOUT = 900 -/** - * - * @param {object} config - * @param {boolean} config.offline - * @param {*} config.api - * @param {*} config.site - * @param {*} config.siteInfo - * @returns - */ -// @ts-expect-error TS(7031) FIXME: Binding element 'api' implicitly has an 'any' type... Remove this comment to see the full error message -export const getSiteInformation = async ({ api, offline, site, siteInfo }) => { +export const getSiteInformation = async ({ + api, + offline, + site, + siteInfo, +}: { + api: NetlifyAPI + offline: boolean + site: NetlifySite + siteInfo: SiteInfo +}) => { if (site.id && !offline) { validateSiteInfo({ site, siteInfo }) const [accounts, addons] = await Promise.all([getAccounts({ api }), getAddons({ api, site })]) @@ -142,22 +142,22 @@ export const getSiteInformation = async ({ api, offline, site, siteInfo }) => { } } -// @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 { name = source, printFn = chalk.green } = ENV_VAR_SOURCES[source] ?? {} return printFn(name) } -/** - * @param {{devConfig: any, env: Record, site: any}} param0 - * @returns {Promise>} - */ -// @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 }) => { +export const getDotEnvVariables = async ({ + devConfig, + env, + site, +}: { + devConfig: DevConfig + env: CachedConfig['env'] + site: NetlifySite +}): 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 dotEnvFiles.forEach(({ env: fileEnv, file }) => { const newSourceName = `${file} file` @@ -169,6 +169,7 @@ export const getDotEnvVariables = async ({ devConfig, env, site }) => { } env[key] = { + // @ts-expect-error(serhalp) Something isn't right with these types but it's a can of worms. sources, value: fileEnv[key], } @@ -180,20 +181,14 @@ export const getDotEnvVariables = async ({ devConfig, env, site }) => { /** * Takes a set of environment variables in the format provided by @netlify/config and injects them into `process.env` - * @param {Record} env - * @return {void} */ -// @ts-expect-error TS(7006) FIXME: Parameter 'env' implicitly has an 'any' type. -export const injectEnvVariables = (env) => { +export const injectEnvVariables = (env: Record): void => { for (const [key, variable] of Object.entries(env)) { const existsInProcess = process.env[key] !== undefined - // @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'. const [usedSource, ...overriddenSources] = existsInProcess ? ['process', ...variable.sources] : variable.sources const usedSourceName = getEnvSourceName(usedSource) - // @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'. const isInternal = variable.sources.includes('internal') - // @ts-expect-error TS(7006) FIXME: Parameter 'source' implicitly has an 'any' type. overriddenSources.forEach((source) => { const sourceName = getEnvSourceName(source) @@ -212,7 +207,6 @@ export const injectEnvVariables = (env) => { log(`${NETLIFYDEVLOG} Injected ${usedSourceName} env var: ${chalk.yellow(key)}`) } - // @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'. process.env[key] = variable.value } } @@ -234,8 +228,7 @@ 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: (...args: unknown[]) => void) => { const signals = ['SIGINT', 'SIGTERM', 'SIGQUIT', 'SIGHUP', 'exit'] signals.forEach((signal) => { process.on(signal, fn) diff --git a/src/utils/dot-env.ts b/src/utils/dot-env.ts index 27cebc92163..a56187e68dd 100644 --- a/src/utils/dot-env.ts +++ b/src/utils/dot-env.ts @@ -1,35 +1,48 @@ import { readFile } from 'fs/promises' import path from 'path' -import dotenv from 'dotenv' +import dotenv, { type DotenvParseOutput } from 'dotenv' 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 }) { - const response = await tryLoadDotEnvFiles({ projectDir, dotenvFiles: envFiles }) +interface LoadedDotEnvFile { + file: string + env: DotenvParseOutput +} + +export const loadDotEnvFiles = async function ({ + envFiles, + projectDir, +}: { + envFiles?: string[] + projectDir?: string +}): Promise { + const loadedDotEnvFiles = await tryLoadDotEnvFiles({ projectDir, dotenvFiles: envFiles }) - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - const filesWithWarning = response.filter((el) => el.warning) - filesWithWarning.forEach((el) => { - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - warn(el.warning) - }) + loadedDotEnvFiles + .filter((el): el is { warning: string } => 'warning' in el) + .forEach((el) => { + warn(el.warning) + }) - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - return response.filter((el) => el.file && el.env) + return loadedDotEnvFiles.filter((el): el is LoadedDotEnvFile => '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) => { - const filepath = path.resolve(projectDir, file) + const filepath = path.resolve(projectDir ?? '', file) try { const isFile = await isFileAsync(filepath) if (!isFile) { @@ -37,8 +50,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 : error?.toString() + }`, } } const content = await readFile(filepath, 'utf-8') @@ -48,5 +62,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 LoadedDotEnvFile => result != null).reverse() } diff --git a/src/utils/gh-auth.ts b/src/utils/gh-auth.ts index 1739b91e6b9..516e4ea2c99 100644 --- a/src/utils/gh-auth.ts +++ b/src/utils/gh-auth.ts @@ -81,7 +81,6 @@ export const authWithNetlify = async () => { }) const url = `${webUI}/cli?${urlParams.toString()}` - // @ts-expect-error TS(2345) FIXME: Argument of type '{ url: string; }' is not assigna... Remove this comment to see the full error message await openBrowser({ url }) return deferredPromise diff --git a/src/utils/headers.ts b/src/utils/headers.ts index de5f9cdf5bf..a9bebe254a4 100644 --- a/src/utils/headers.ts +++ b/src/utils/headers.ts @@ -1,33 +1,29 @@ -import { parseAllHeaders } from '@netlify/headers-parser' +import { type Header, type MinimalHeader, parseAllHeaders } from '@netlify/headers-parser' import { NETLIFYDEVERR, log } from './command-helpers.js' /** * Get the matching headers for `path` given a set of `rules`. - * - * @param {Object>!} headers - * The rules to use for matching. - * - * @param {string!} path - * The path to match against. - * - * @returns {Object} */ -// @ts-expect-error TS(7006) FIXME: Parameter 'headers' implicitly has an 'any' type. -export const headersForPath = function (headers, path) { - // @ts-expect-error TS(7031) FIXME: Binding element 'forRegExp' implicitly has an 'any... Remove this comment to see the full error message - const matchingHeaders = headers.filter(({ forRegExp }) => forRegExp.test(path)).map(getHeaderValues) - const headersRules = Object.assign({}, ...matchingHeaders) +export const headersForPath = function (headers: Header[], path: string) { + const matchingHeaders = headers.filter(({ forRegExp }) => forRegExp.test(path)).map(({ values }) => values) + const headersRules = { ...matchingHeaders } return headersRules } -// @ts-expect-error TS(7031) FIXME: Binding element 'values' implicitly has an 'any' t... Remove this comment to see the full error message -const getHeaderValues = function ({ values }) { - return values -} - -// @ts-expect-error TS(7031) FIXME: Binding element 'configPath' implicitly has an 'an... Remove this comment to see the full error message -export const parseHeaders = async function ({ config, configPath, headersFiles }): Promise { +export const parseHeaders = async function ({ + config, + configPath, + headersFiles, +}: { + config?: + | undefined + | { + headers?: undefined | MinimalHeader[] + } + configPath?: undefined | string + headersFiles?: undefined | string[] +}): Promise { const { errors, headers } = await parseAllHeaders({ headersFiles, netlifyConfigPath: configPath, @@ -35,11 +31,10 @@ export const parseHeaders = async function ({ config, configPath, headersFiles } configHeaders: config?.headers || [], }) handleHeadersErrors(errors) - return headers + return headers as Header[] } -// @ts-expect-error TS(7006) FIXME: Parameter 'errors' implicitly has an 'any' type. -const handleHeadersErrors = function (errors) { +const handleHeadersErrors = function (errors: Error[]): void { if (errors.length === 0) { return } @@ -48,8 +43,7 @@ const handleHeadersErrors = function (errors) { log(NETLIFYDEVERR, `Headers syntax errors:\n${errorMessage}`) } -// @ts-expect-error TS(7031) FIXME: Binding element 'message' implicitly has an 'any' ... Remove this comment to see the full error message -const getErrorMessage = function ({ message }) { +const getErrorMessage = function ({ message }: Error): string { return message } diff --git a/src/utils/open-browser.ts b/src/utils/open-browser.ts index 7c7df65db4b..22eaebc7e53 100644 --- a/src/utils/open-browser.ts +++ b/src/utils/open-browser.ts @@ -19,7 +19,7 @@ const unableToOpenBrowserMessage = function ({ message, url }: BrowserUnableMess } type OpenBrowsrProps = { - silentBrowserNoneError: boolean + silentBrowserNoneError?: boolean url: string } diff --git a/src/utils/proxy-server.ts b/src/utils/proxy-server.ts index 157293f2397..3a5e6d25da1 100644 --- a/src/utils/proxy-server.ts +++ b/src/utils/proxy-server.ts @@ -63,8 +63,8 @@ export const startProxyServer = async ({ siteInfo, state, }: { - accountId: string - addonsUrls: $TSFixMe + accountId?: string | undefined + addonsUrls: Record api?: NetlifyOptions['api'] blobsContext?: BlobsContextWithEdgeAccess command: BaseCommand diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts index b90c067df15..32e59feb3e9 100644 --- a/src/utils/proxy.ts +++ b/src/utils/proxy.ts @@ -1,13 +1,13 @@ import { Buffer } from 'buffer' import { once } from 'events' import { readFile } from 'fs/promises' -import http, { ServerResponse } from 'http' +import http, { type ServerResponse } from 'http' import https from 'https' import { isIPv6 } from 'net' import { Readable } from 'node:stream' import path from 'path' import process from 'process' -import { Duplex } from 'stream' +import type { Duplex } from 'stream' import util from 'util' import zlib from 'zlib' @@ -16,16 +16,16 @@ import cookie from 'cookie' import { getProperty } from 'dot-prop' import generateETag from 'etag' import getAvailablePort from 'get-port' -import httpProxy from 'http-proxy' +import httpProxy, { type ServerOptions } from 'http-proxy' import { createProxyMiddleware } from 'http-proxy-middleware' -import { jwtDecode } from 'jwt-decode' +import { jwtDecode, type JwtPayload } from 'jwt-decode' import { locatePath } from 'locate-path' -import { Match } from 'netlify-redirector' +import type { Match } from 'netlify-redirector' import pFilter from 'p-filter' import throttle from 'lodash/throttle.js' -import { BaseCommand } from '../commands/index.js' -import { $TSFixMe, NetlifyOptions } from '../commands/types.js' +import type { BaseCommand } from '../commands/index.js' +import type { $TSFixMe, NetlifyOptions } from '../commands/types.js' import { handleProxyRequest, initializeProxy as initializeEdgeFunctionsProxy, @@ -43,7 +43,24 @@ import { NFFunctionName, NFFunctionRoute, NFRequestID, headersForPath, parseHead import { generateRequestID } from './request-id.js' import { createRewriter, onChanges } from './rules-proxy.js' import { signRedirect } from './sign-redirect.js' -import { Request, Rewriter, ServerSettings } from './types.js' +import type { Rewriter, ExtraServerOptions, ServerSettings } from './types.js' +import { ClientRequest, IncomingMessage } from 'node:http' + +declare module 'http' { + // This is only necessary because we're attaching custom junk to the `req` given to us + // by the `http-proxy` module. Since it in turn imports its request object type from `http`, + // we have no choice but to augment the `http` module itself globally. + // NOTE: to be extra clear, this is *augmenting* the existing type: + // https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces. + interface IncomingMessage { + originalBody?: Buffer | null + protocol?: string + hostname?: string + __expectHeader?: string + alternativePaths?: string[] + proxyOptions: ServerOptions + } +} const gunzip = util.promisify(zlib.gunzip) const gzip = util.promisify(zlib.gzip) @@ -51,7 +68,31 @@ const brotliDecompress = util.promisify(zlib.brotliDecompress) const brotliCompress = util.promisify(zlib.brotliCompress) const deflate = util.promisify(zlib.deflate) const inflate = util.promisify(zlib.inflate) -const shouldGenerateETag = Symbol('Internal: response should generate ETag') + +const shouldGenerateETagSymbol = Symbol('Internal: response should generate ETag') +type ShouldGenerateETag = ({ statusCode }: { statusCode: number }) => boolean +const getShouldGenerateETag = ( + req: IncomingMessage, + // @ts-expect-error(serhalp -- See `types/http/index.d.ts`. It isn't possible to reference + // a unique symbol from within our `http` type declaration augmentation. This function at + // least lets us limit the poor typing blast radius. +): undefined | ShouldGenerateETag => req[shouldGenerateETagSymbol] +const setShouldGenerateETag = (req: IncomingMessage, shouldGenerateETag: ShouldGenerateETag): void => { + // @ts-expect-error(serhalp -- See above + req[shouldGenerateETagSymbol] = shouldGenerateETag +} + +type ExtendedServerOptions = ServerOptions | ExtraServerOptions + +const getExtraServerOption = ( + options: ExtendedServerOptions, + name: keyof ExtendedServerOptions, +): ExtendedServerOptions[typeof name] => { + if (name in options) { + return options[name] + } + return +} const decompressResponseBody = async function (body: Buffer, contentEncoding = ''): Promise { switch (contentEncoding) { @@ -102,8 +143,7 @@ const injectHtml = async function ( return await compressResponseBody(bodyWithInjections, proxyRes.headers['content-encoding']) } -// @ts-expect-error TS(7006) FIXME: Parameter 'errorBuffer' implicitly has an 'any' ty... Remove this comment to see the full error message -const formatEdgeFunctionError = (errorBuffer, acceptsHtml) => { +const formatEdgeFunctionError = (errorBuffer: Buffer, acceptsHtml: boolean): string => { const { error: { message, name, stack }, } = JSON.parse(errorBuffer.toString()) @@ -127,13 +167,13 @@ function isFunction(functionsPort: boolean | number | undefined, url: string) { return functionsPort && url.match(DEFAULT_FUNCTION_URL_EXPRESSION) } -function getAddonUrl(addonsUrls: Record, req: http.IncomingMessage) { +function getAddonUrl(addonsUrls: Record, req: IncomingMessage) { const matches = req.url?.match(/^\/.netlify\/([^/]+)(\/.*)/) const addonUrl = matches && addonsUrls[matches[1]] return addonUrl ? `${addonUrl}${matches[2]}` : null } -const getStatic = async function (pathname: string, publicFolder: string) { +const getStatic = async function (pathname: string, publicFolder: string): Promise { const alternatives = [pathname, ...alternativePathsFor(pathname)].map((filePath) => path.resolve(publicFolder, filePath.slice(1)), ) @@ -146,7 +186,7 @@ const getStatic = async function (pathname: string, publicFolder: string) { return `/${path.relative(publicFolder, file)}` } -const isEndpointExists = async function (endpoint: string, origin: string) { +const isEndpointExists = async function (endpoint: string, origin?: string | undefined) { const url = new URL(endpoint, origin) try { const res = await fetch(url, { method: 'HEAD' }) @@ -156,13 +196,11 @@ const isEndpointExists = async function (endpoint: string, origin: string) { } } -// @ts-expect-error TS(7006) FIXME: Parameter 'match' implicitly has an 'any' type. -const isExternal = function (match) { - return match.to && match.to.match(/^https?:\/\//) +const isExternal = function (match: Match) { + return 'to' in match && match.to.match(/^https?:\/\//) } -// @ts-expect-error TS(7031) FIXME: Binding element 'hash' implicitly has an 'any' typ... Remove this comment to see the full error message -const stripOrigin = function ({ hash, pathname, search }) { +const stripOrigin = function ({ hash, pathname, search }: URL): string { return `${pathname}${search}${hash}` } @@ -174,7 +212,7 @@ const proxyToExternalUrl = function ({ }: { dest: URL destURL: string - req: Request + req: IncomingMessage res: ServerResponse }) { const handler = createProxyMiddleware({ @@ -185,32 +223,40 @@ const proxyToExternalUrl = function ({ logLevel: 'warn', ...(Buffer.isBuffer(req.originalBody) && { buffer: Readable.from(req.originalBody) }), }) - // @ts-expect-error TS(2345) FIXME: Argument of type 'Request' is not assignable to parameter of type 'Request>'. + // @ts-expect-error TS(2345) FIXME: Argument of type 'IncomingMessage' is not assignable to parameter of type 'Request>'. return handler(req, res, () => {}) } -// @ts-expect-error TS(7031) FIXME: Binding element 'addonUrl' implicitly has an 'any'... Remove this comment to see the full error message -const handleAddonUrl = function ({ addonUrl, req, res }) { +const handleAddonUrl = function ({ + addonUrl, + req, + res, +}: { + req: IncomingMessage + res: ServerResponse + addonUrl: string +}) { const dest = new URL(addonUrl) const destURL = stripOrigin(dest) return proxyToExternalUrl({ req, res, dest, destURL }) } -// @ts-expect-error TS(7006) FIXME: Parameter 'match' implicitly has an 'any' type. -const isRedirect = function (match) { - return match.status && match.status >= 300 && match.status <= 400 +const isRedirect = function (match: Match) { + return 'status' in match && match.status >= 300 && match.status <= 400 } -// @ts-expect-error TS(7006) FIXME: Parameter 'publicFolder' implicitly has an 'any' t... Remove this comment to see the full error message -const render404 = async function (publicFolder) { - const maybe404Page = path.resolve(publicFolder, '404.html') +const render404 = async function (publicFolder?: string) { + const maybe404Page = path.resolve(publicFolder ?? '', '404.html') try { const isFile = await isFileAsync(maybe404Page) if (isFile) return await readFile(maybe404Page, 'utf-8') } catch (error) { - // @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'. - console.warn(NETLIFYDEVWARN, 'Error while serving 404.html file', error.message) + console.warn( + NETLIFYDEVWARN, + 'Error while serving 404.html file', + error instanceof Error ? error.message : error?.toString(), + ) } return 'Not Found' @@ -219,13 +265,12 @@ const render404 = async function (publicFolder) { // Used as an optimization to avoid dual lookups for missing assets const assetExtensionRegExp = /\.(html?|png|jpg|js|css|svg|gif|ico|woff|woff2)$/ -// @ts-expect-error TS(7006) FIXME: Parameter 'url' implicitly has an 'any' type. -const alternativePathsFor = function (url) { +const alternativePathsFor = function (url: string) { if (isFunction(true, url)) { return [] } - const paths = [] + const paths: string[] = [] if (url[url.length - 1] === '/') { const end = url.length - 1 if (url !== '/') { @@ -257,16 +302,21 @@ const serveRedirect = async function ({ res, siteInfo, }: { + options: ExtendedServerOptions + req: IncomingMessage + res: ServerResponse match: Match | null } & Record) { if (!match) return proxy.web(req, res, options) - options = options || req.proxyOptions || {} - options.match = null + options = { + ...(options ?? req.proxyOptions), + match: null, + } if (match.force404) { res.writeHead(404) - res.end(await render404(options.publicFolder)) + res.end(await render404(getExtraServerOption(options, 'publicFolder'))) return } @@ -294,11 +344,11 @@ const serveRedirect = async function ({ } } - if (isFunction(options.functionsPort, req.url)) { + if (isFunction(getExtraServerOption(options, 'functionsPort'), req.url ?? '')) { return proxy.web(req, res, { target: options.functionsServer }) } - const urlForAddons = getAddonUrl(options.addonsUrls, req) + const urlForAddons = getAddonUrl(getExtraServerOption(options, 'addonsUrls') ?? {}, req) if (urlForAddons) { return handleAddonUrl({ req, res, addonUrl: urlForAddons }) } @@ -317,24 +367,32 @@ const serveRedirect = async function ({ req.url = '/.netlify/non-existent-path' if (token) { - let jwtValue = {} + let jwtValue: JwtPayload = {} try { - jwtValue = jwtDecode(token) || {} + jwtValue = jwtDecode(token) } catch (error) { - // @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'. - console.warn(NETLIFYDEVWARN, 'Error while decoding JWT provided in request', error.message) + console.warn( + NETLIFYDEVWARN, + 'Error while decoding JWT provided in request', + error instanceof Error ? error.message : error?.toString(), + ) res.writeHead(400) res.end('Invalid JWT provided. Please see logs for more info.') return } - // @ts-expect-error TS(2339) FIXME: Property 'exp' does not exist on type '{}'. if ((jwtValue.exp || 0) < Math.round(Date.now() / MILLISEC_TO_SEC)) { console.warn(NETLIFYDEVWARN, 'Expired JWT provided in request', req.url) } else { - const presentedRoles = getProperty(jwtValue, options.jwtRolePath) || [] + // I think through some circuitous callback logic `options.jwtRolePath` is guaranteed to + // be defined at this point, but I don't think it's possible to convince TS of this. + const presentedRoles = getProperty(jwtValue, getExtraServerOption(options, 'jwtRolePath')) ?? [] if (!Array.isArray(presentedRoles)) { - console.warn(NETLIFYDEVWARN, `Invalid roles value provided in JWT ${options.jwtRolePath}`, presentedRoles) + console.warn( + NETLIFYDEVWARN, + `Invalid roles value provided in JWT ${getExtraServerOption(options, 'jwtRolePath')}`, + presentedRoles, + ) res.writeHead(400) res.end('Invalid JWT provided. Please see logs for more info.') return @@ -353,12 +411,18 @@ const serveRedirect = async function ({ match.proxyHeaders && Object.entries(match.proxyHeaders).some(([key, val]) => key.toLowerCase() === 'x-nf-hidden-proxy' && val === 'true') - const staticFile = await getStatic(decodeURIComponent(reqUrl.pathname), options.publicFolder) + const staticFile = await getStatic( + decodeURIComponent(reqUrl.pathname), + getExtraServerOption(options, 'publicFolder') ?? '', + ) const endpointExists = !staticFile && !isHiddenProxy && process.env.NETLIFY_DEV_SERVER_CHECK_SSG_ENDPOINTS && - (await isEndpointExists(decodeURIComponent(reqUrl.pathname), options.target)) + // @ts-expect-error(serhalp) -- TODO verify if the intent is that `options.target` is + // always a string (if so, use `typeof` to only pass strings), or if this is implicitly + // relying on built-in coercion to a string of the various support target URL-ish types. + (await isEndpointExists(decodeURIComponent(reqUrl.pathname), getExtraServerOption(options, 'target'))) if (staticFile || endpointExists) { const pathname = staticFile || reqUrl.pathname req.url = encodeURI(pathname) + reqUrl.search @@ -368,7 +432,7 @@ const serveRedirect = async function ({ } } - if (match.force || !staticFile || !options.framework || req.method === 'POST') { + if (match.force || !staticFile || !getExtraServerOption(options, 'framework') || req.method === 'POST') { // construct destination URL from redirect rule match const dest = new URL(match.to, `${reqUrl.protocol}//${reqUrl.host}`) @@ -417,17 +481,18 @@ const serveRedirect = async function ({ !isInternal(destURL) && (ct.endsWith('/x-www-form-urlencoded') || ct === 'multipart/form-data') ) { - return proxy.web(req, res, { target: options.functionsServer }) + return proxy.web(req, res, { target: getExtraServerOption(options, 'functionsServer') }) } - const destStaticFile = await getStatic(dest.pathname, options.publicFolder) + const destStaticFile = await getStatic(dest.pathname, getExtraServerOption(options, 'functionsServer') ?? '') const matchingFunction = functionsRegistry && (await functionsRegistry.getFunctionForURLPath(destURL, req.method, () => Boolean(destStaticFile))) - let statusValue + let statusValue: number | undefined if ( match.force || - (!staticFile && ((!options.framework && destStaticFile) || isInternal(destURL) || matchingFunction)) + (!staticFile && + ((!getExtraServerOption(options, 'framework') && destStaticFile) || isInternal(destURL) || matchingFunction)) ) { req.url = destStaticFile ? destStaticFile + dest.search : destURL const { status } = match @@ -446,12 +511,12 @@ const serveRedirect = async function ({ req.headers['x-netlify-original-pathname'] = url.pathname req.headers['x-netlify-original-search'] = url.search - return proxy.web(req, res, { headers: functionHeaders, target: options.functionsServer }) + return proxy.web(req, res, { headers: functionHeaders, target: getExtraServerOption(options, 'functionsServer') }) } if (isImageRequest(req)) { return imageProxy(req, res) } - const addonUrl = getAddonUrl(options.addonsUrls, req) + const addonUrl = getAddonUrl(getExtraServerOption(options, 'addonsUrls') ?? {}, req) if (addonUrl) { return handleAddonUrl({ req, res, addonUrl }) } @@ -462,10 +527,9 @@ const serveRedirect = async function ({ return proxy.web(req, res, options) } -// @ts-expect-error TS(7006) FIXME: Parameter 'req' implicitly has an 'any' type. -const reqToURL = function (req, pathname) { +const reqToURL = function (req: IncomingMessage, pathname: undefined | string) { return new URL( - pathname, + pathname ?? '', `${req.protocol || (req.headers.scheme && `${req.headers.scheme}:`) || 'http:'}//${ req.headers.host || req.hostname }`, @@ -506,8 +570,8 @@ const initializeProxy = async function ({ headers = await parseHeaders({ headersFiles, configPath, config }) }) - // @ts-expect-error TS(2339) FIXME: Property 'before' does not exist on type 'Server'. - proxy.before('web', 'stream', (req) => { + // @ts-expect-error TS(2339) FIXME: Property 'on' does not exist on type 'ProxyServer'. Remove this comment to see the full error message + proxy.before('web', 'stream', (req: IncomingMessage) => { // See https://github.com/http-party/node-http-proxy/issues/1219#issuecomment-511110375 if (req.headers.expect) { req.__expectHeader = req.headers.expect @@ -527,7 +591,7 @@ const initializeProxy = async function ({ res.end(message) }) - proxy.on('proxyReq', (proxyReq, req) => { + proxy.on('proxyReq', (proxyReq: ClientRequest, req: IncomingMessage, _res: unknown, _opts: unknown) => { const requestID = generateRequestID() proxyReq.setHeader(NFRequestID, requestID) @@ -537,18 +601,14 @@ const initializeProxy = async function ({ handleProxyRequest(req, proxyReq) } - // @ts-expect-error TS(2339) FIXME: Property '__expectHeader' does not exist on type '... Remove this comment to see the full error message if (req.__expectHeader) { - // @ts-expect-error TS(2339) FIXME: Property '__expectHeader' does not exist on type '... Remove this comment to see the full error message proxyReq.setHeader('Expect', req.__expectHeader) } - // @ts-expect-error TS(2339) FIXME: Property 'originalBody' does not exist on type 'In... Remove this comment to see the full error message if (req.originalBody) { - // @ts-expect-error TS(2339) FIXME: Property 'originalBody' does not exist on type 'In... Remove this comment to see the full error message proxyReq.write(req.originalBody) } }) - proxy.on('proxyRes', (proxyRes, req, res) => { + proxy.on('proxyRes', (proxyRes: IncomingMessage, req: IncomingMessage, res: ServerResponse) => { res.setHeader('server', 'Netlify') const requestID = req.headers[NFRequestID] @@ -560,19 +620,15 @@ const initializeProxy = async function ({ if (proxyRes.statusCode === 404 || proxyRes.statusCode === 403) { // If a request for `/path` has failed, we'll a few variations like // `/path/index.html` to mimic the CDN behavior. - // @ts-expect-error TS(2339) FIXME: Property 'alternativePaths' does not exist on type... Remove this comment to see the full error message if (req.alternativePaths && req.alternativePaths.length !== 0) { - // @ts-expect-error TS(2339) FIXME: Property 'alternativePaths' does not exist on type... Remove this comment to see the full error message req.url = req.alternativePaths.shift() - // @ts-expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message return proxy.web(req, res, req.proxyOptions) } // The request has failed but we might still have a matching redirect // rule (without `force`) that should kick in. This is how we mimic the // file shadowing behavior from the CDN. - // @ts-expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message - if (req.proxyOptions && req.proxyOptions.match) { + if (req.proxyOptions?.match) { return serveRedirect({ // We don't want to match functions at this point because any redirects // to functions will have already been processed, so we don't supply a @@ -582,9 +638,7 @@ const initializeProxy = async function ({ res, proxy: handlers, imageProxy, - // @ts-expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message match: req.proxyOptions.match, - // @ts-expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message options: req.proxyOptions, siteInfo, env, @@ -592,7 +646,7 @@ const initializeProxy = async function ({ } } - // @ts-expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message + // @ts-expect-error(serhalp) -- This makes absolutely no sense. Take a deep breath and investigate. if (req.proxyOptions.staticFile && isRedirect({ status: proxyRes.statusCode }) && proxyRes.headers.location) { req.url = proxyRes.headers.location return serveRedirect({ @@ -605,17 +659,14 @@ const initializeProxy = async function ({ proxy: handlers, imageProxy, match: null, - // @ts-expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message options: req.proxyOptions, siteInfo, env, }) } - // @ts-expect-error TS(7034) FIXME: Variable 'responseData' implicitly has type 'any[]... Remove this comment to see the full error message - const responseData = [] - // @ts-expect-error TS(2345) FIXME: Argument of type 'string | undefined' is not assig... Remove this comment to see the full error message - const requestURL = new URL(req.url, `http://${req.headers.host || '127.0.0.1'}`) + const responseData: Uint8Array[] = [] + const requestURL = new URL(req.url ?? '', `http://${req.headers.host || '127.0.0.1'}`) const headersRules = headersForPath(headers, requestURL.pathname) const htmlInjections = @@ -634,8 +685,7 @@ const initializeProxy = async function ({ // @ts-expect-error TS(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message res.setHeader(key, val) }) - // @ts-expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message - res.writeHead(req.proxyOptions.status || proxyRes.statusCode, proxyRes.headers) + res.writeHead(req.proxyOptions.status ?? proxyRes.statusCode ?? 200, proxyRes.headers) proxyRes.on('data', function onData(data) { res.write(data) @@ -649,24 +699,18 @@ const initializeProxy = async function ({ } proxyRes.on('data', function onData(data) { - responseData.push(data) + responseData.push(data as Uint8Array) }) proxyRes.on('end', async function onEnd() { - // @ts-expect-error TS(7005) FIXME: Variable 'responseData' implicitly has an 'any[]' ... Remove this comment to see the full error message let responseBody = Buffer.concat(responseData) - // @ts-expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message let responseStatus = req.proxyOptions.status || proxyRes.statusCode // `req[shouldGenerateETag]` may contain a function that determines // whether the response should have an ETag header. - if ( - // @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message - typeof req[shouldGenerateETag] === 'function' && - // @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message - req[shouldGenerateETag]({ statusCode: responseStatus }) === true - ) { + const shouldGenerateETag = getShouldGenerateETag(req) + if (typeof shouldGenerateETag === 'function' && shouldGenerateETag({ statusCode: responseStatus }) === true) { const etag = generateETag(responseBody, { weak: true }) if (req.headers['if-none-match'] === etag) { @@ -684,7 +728,7 @@ const initializeProxy = async function ({ const isUncaughtError = proxyRes.headers['x-nf-uncaught-error'] === '1' if (isEdgeFunctionsRequest(req) && isUncaughtError) { - const acceptsHtml = req.headers && req.headers.accept && req.headers.accept.includes('text/html') + const acceptsHtml = req.headers?.accept?.includes('text/html') ?? false const decompressedBody = await decompressResponseBody(responseBody, proxyRes.headers['content-encoding']) const formattedBody = formatEdgeFunctionError(decompressedBody, acceptsHtml) const errorResponse = acceptsHtml @@ -709,7 +753,7 @@ const initializeProxy = async function ({ delete proxyResHeaders['transfer-encoding'] } - res.writeHead(responseStatus, proxyResHeaders) + res.writeHead(responseStatus ?? 200, proxyResHeaders) if (responseStatus !== 304) { res.write(responseBody) @@ -720,17 +764,24 @@ const initializeProxy = async function ({ }) const handlers = { - // @ts-expect-error TS(7006) FIXME: Parameter 'req' implicitly has an 'any' type. - web: (req, res, options) => { - const requestURL = new URL(req.url, 'http://127.0.0.1') + web: (req: IncomingMessage, res: ServerResponse, options: IncomingMessage['proxyOptions']) => { + const requestURL = new URL(req.url ?? '', 'http://127.0.0.1') req.proxyOptions = options req.alternativePaths = alternativePathsFor(requestURL.pathname).map((filePath) => filePath + requestURL.search) // Ref: https://nodejs.org/api/net.html#net_socket_remoteaddress - req.headers['x-forwarded-for'] = req.connection.remoteAddress || '' + req.headers['x-forwarded-for'] = req.socket.remoteAddress || '' return proxy.web(req, res, options) }, - // @ts-expect-error TS(7006) FIXME: Parameter 'req' implicitly has an 'any' type. - ws: (req, socket, head, options) => proxy.ws(req, socket, head, options), + ws: ( + req: IncomingMessage, + socket: Duplex, + head: Buffer, + options: { + target?: string + changeOrigin?: boolean + pathRewrite?: () => string + }, + ) => proxy.ws(req, socket, head, options), } return handlers @@ -749,11 +800,13 @@ const onRequest = async ( rewriter, settings, siteInfo, - }: { rewriter: Rewriter; settings: ServerSettings; edgeFunctionsProxy?: EdgeFunctionsProxy } & Record< - string, - $TSFixMe - >, - req: Request, + }: { + addonsUrls: Record + rewriter: Rewriter + settings: ServerSettings + edgeFunctionsProxy?: EdgeFunctionsProxy + } & Record, + req: IncomingMessage, res: ServerResponse, ) => { req.originalBody = @@ -777,16 +830,13 @@ const onRequest = async ( if (functionMatch) { // Setting an internal header with the function name so that we don't // have to match the URL again in the functions server. - /** @type {Record} */ - const headers = {} + const headers: Record = {} if (functionMatch.func) { - // @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message headers[NFFunctionName] = functionMatch.func.name } if (functionMatch.route) { - // @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message headers[NFFunctionRoute] = functionMatch.route.pattern } @@ -814,8 +864,7 @@ const onRequest = async ( if (match) { // We don't want to generate an ETag for 3xx redirects. - // @ts-expect-error TS(7031) FIXME: Binding element 'statusCode' implicitly has an 'an... Remove this comment to see the full error message - req[shouldGenerateETag] = ({ statusCode }) => statusCode < 300 || statusCode >= 400 + setShouldGenerateETag(req, ({ statusCode }: { statusCode: number }) => statusCode < 300 || statusCode >= 400) return serveRedirect({ req, res, proxy, imageProxy, match, options, siteInfo, env, functionsRegistry }) } @@ -823,8 +872,7 @@ const onRequest = async ( // The request will be served by the framework server, which means we want to // generate an ETag unless we're rendering an error page. The only way for // us to know that is by looking at the status code - // @ts-expect-error TS(7031) FIXME: Binding element 'statusCode' implicitly has an 'an... Remove this comment to see the full error message - req[shouldGenerateETag] = ({ statusCode }) => statusCode >= 200 && statusCode < 300 + setShouldGenerateETag(req, ({ statusCode }: { statusCode: number }) => statusCode >= 200 && statusCode < 300) const hasFormSubmissionHandler: boolean = functionsRegistry && getFormHandler({ functionsRegistry, logWarning: false }) @@ -876,7 +924,12 @@ export const startProxy = async function ({ settings, siteInfo, state, -}: { command: BaseCommand; settings: ServerSettings; disableEdgeFunctions: boolean } & Record) { +}: { + addonsUrls: Record + command: BaseCommand + settings: ServerSettings + disableEdgeFunctions: boolean +} & Record) { const secondaryServerPort = settings.https ? await getAvailablePort() : null const functionsServer = settings.functionsPort ? `http://127.0.0.1:${settings.functionsPort}` : null @@ -952,7 +1005,7 @@ export const startProxy = async function ({ const primaryServer = settings.https ? https.createServer({ cert: settings.https.cert, key: settings.https.key }, onRequestWithOptions) : http.createServer(onRequestWithOptions) - const onUpgrade = async function onUpgrade(req: http.IncomingMessage, socket: Duplex, head: Buffer) { + const onUpgrade = async function onUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer) { const match = await rewriter(req) if (match && !match.force404 && isExternal(match)) { const reqUrl = reqToURL(req, req.url) diff --git a/src/utils/rules-proxy.ts b/src/utils/rules-proxy.ts index 924d75e1aac..8c40986bcf0 100644 --- a/src/utils/rules-proxy.ts +++ b/src/utils/rules-proxy.ts @@ -1,6 +1,8 @@ +import type { Stats } from 'fs' +import type { IncomingHttpHeaders, IncomingMessage } from 'http' import path from 'path' -import chokidar from 'chokidar' +import chokidar, { type FSWatcher } from 'chokidar' import cookie from 'cookie' import redirector from 'netlify-redirector' import type { Match, RedirectMatcher } from 'netlify-redirector' @@ -10,14 +12,14 @@ import { fileExistsAsync } from '../lib/fs.js' import { NETLIFYDEVLOG } from './command-helpers.js' import { parseRedirects } from './redirects.js' -import { Request, Rewriter } from './types.js' +import { Rewriter } from './types.js' -// @ts-expect-error TS(7034) FIXME: Variable 'watchers' implicitly has type 'any[]' in... Remove this comment to see the full error message -const watchers = [] +// Not exported by chokidar for some reason +type FSListener = (path: string, stats?: Stats) => void -// @ts-expect-error TS(7006) FIXME: Parameter 'files' implicitly has an 'any' type. -export const onChanges = function (files, listener) { - // @ts-expect-error TS(7006) FIXME: Parameter 'file' implicitly has an 'any' type. +const watchers: FSWatcher[] = [] + +export const onChanges = function (files: string[], listener: FSListener) { files.forEach((file) => { const watcher = chokidar.watch(file) watcher.on('change', listener) @@ -26,13 +28,11 @@ export const onChanges = function (files, listener) { }) } -export const getWatchers = function () { - // @ts-expect-error TS(7005) FIXME: Variable 'watchers' implicitly has an 'any[]' type... Remove this comment to see the full error message +export const getWatchers = function (): FSWatcher[] { return watchers } -// @ts-expect-error TS(7006) FIXME: Parameter 'headers' implicitly has an 'any' type. -export const getLanguage = function (headers) { +export const getLanguage = function (headers: IncomingHttpHeaders): string { if (headers['accept-language']) { return headers['accept-language'].split(',')[0].slice(0, 2) } @@ -86,8 +86,7 @@ export const createRewriter = async function ({ } } - // @ts-expect-error TS(7006) FIXME: Parameter 'req' implicitly has an 'any' type. - return async function rewriter(req: Request): Promise { + return async function rewriter(req: IncomingMessage): Promise { const matcherFunc = await getMatcher() const reqUrl = new URL( req.url ?? '', diff --git a/src/utils/types.ts b/src/utils/types.ts index 356ce2be3ff..c339a9d99e5 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,7 +1,5 @@ -import { Buffer } from 'buffer' -import { IncomingMessage } from 'http' - -import { Match } from 'netlify-redirector' +import type { IncomingMessage } from 'http' +import type { Match } from 'netlify-redirector' export type FrameworkNames = '#static' | '#auto' | '#custom' | string @@ -55,13 +53,20 @@ export type ServerSettings = BaseServerSettings & { skipWaitPort?: boolean } -export interface Request extends IncomingMessage { - originalBody?: Buffer | null - protocol?: string - hostname?: string +export interface ExtraServerOptions { + status?: number + match: Match | null + staticFile?: string | false + target: string + publicFolder?: string | undefined + functionsPort: number + jwtRolePath: string + framework?: string | undefined + addonsUrls?: Record + functionsServer?: string | null } -export type Rewriter = (req: Request) => Match | null +export type Rewriter = (req: IncomingMessage) => Promise export interface SiteInfo { account_name: string @@ -88,6 +93,10 @@ export interface SiteInfo { deploy_url: string domain_aliases: string[] force_ssl: boolean + functions_config?: { + timeout?: number + } + functions_timeout?: number git_provider: string id: string managed_dns: boolean diff --git a/tsconfig.json b/tsconfig.json index df827291514..27ee01faf9b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,9 @@ "compilerOptions": { "outDir": "dist", "incremental": true, - "lib": ["es2020"], + "lib": [ + "es2020" + ], "target": "es2020", "module": "NodeNext", "moduleResolution": "NodeNext", @@ -14,8 +16,15 @@ "strict": true, "skipLibCheck": true /* Skip type checking all .d.ts files. */, "allowJs": true, - "typeRoots": ["node_modules/@types", "types"] + "typeRoots": [ + "node_modules/@types", + "types" + ] }, - "include": ["src/**/*.ts"], - "exclude": ["node_modules"] + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "node_modules" + ] }