From 868088d7896257bfa6f590d9610a065689c82184 Mon Sep 17 00:00:00 2001 From: Marcus Weiner Date: Tue, 19 Mar 2024 23:34:36 +0100 Subject: [PATCH 1/5] chore: provide more types --- src/commands/dev/dev.ts | 2 +- src/commands/serve/serve.ts | 1 - src/lib/images/proxy.ts | 6 +- src/utils/create-stream-promise.ts | 22 ++---- src/utils/proxy-server.ts | 98 ++++++++++-------------- src/utils/proxy.ts | 112 ++++++++++------------------ src/utils/rules-proxy.ts | 29 +++---- src/utils/run-build.ts | 38 +++++----- src/utils/types.ts | 13 ++++ types/netlify-redirector/index.d.ts | 39 ++++++++++ 10 files changed, 183 insertions(+), 177 deletions(-) create mode 100644 types/netlify-redirector/index.d.ts diff --git a/src/commands/dev/dev.ts b/src/commands/dev/dev.ts index 15e574bacf0..165cdc8f819 100644 --- a/src/commands/dev/dev.ts +++ b/src/commands/dev/dev.ts @@ -172,7 +172,7 @@ export const dev = async (options: OptionValues, command: BaseCommand) => { }, }) - const mutatedConfig = applyMutations(config, configMutations) + const mutatedConfig: typeof config = applyMutations(config, configMutations) const functionsRegistry = await startFunctionsServer({ blobsContext, diff --git a/src/commands/serve/serve.ts b/src/commands/serve/serve.ts index 3b9c94180a5..757dfcd28c6 100644 --- a/src/commands/serve/serve.ts +++ b/src/commands/serve/serve.ts @@ -141,7 +141,6 @@ export const serve = async (options: OptionValues, command: BaseCommand) => { } const inspectSettings = generateInspectSettings(options.edgeInspect, options.edgeInspectBrk) - // @ts-expect-error TS(2345) FIXME: Argument of type '{ addonsUrls: { [k: string]: any... Remove this comment to see the full error message const url = await startProxyServer({ addonsUrls, command, diff --git a/src/lib/images/proxy.ts b/src/lib/images/proxy.ts index 218d6fbee61..ec015906d99 100644 --- a/src/lib/images/proxy.ts +++ b/src/lib/images/proxy.ts @@ -1,3 +1,5 @@ +import { IncomingMessage } from 'http' + import { NetlifyConfig } from '@netlify/build' import express from 'express' import { createIPX, ipxFSStorage, ipxHttpStorage, createIPXNodeServer } from 'ipx' @@ -83,8 +85,8 @@ export const parseRemoteImages = async function ({ config }) { return remotePatterns } -export const isImageRequest = function (req: Request): boolean { - return req.url.startsWith(IMAGE_URL_PATTERN) +export const isImageRequest = function (req: IncomingMessage): boolean { + return req.url?.startsWith(IMAGE_URL_PATTERN) ?? false } export const transformImageParams = function (query: QueryParams): string { diff --git a/src/utils/create-stream-promise.ts b/src/utils/create-stream-promise.ts index 634c022d764..ab631eff381 100644 --- a/src/utils/create-stream-promise.ts +++ b/src/utils/create-stream-promise.ts @@ -1,37 +1,36 @@ import { Buffer } from 'buffer' +import { IncomingMessage } from 'http' const SEC_TO_MILLISEC = 1e3 // 6 MiB const DEFAULT_BYTES_LIMIT = 6e6 -// @ts-expect-error TS(7006) FIXME: Parameter 'stream' implicitly has an 'any' type. -const createStreamPromise = function (stream, timeoutSeconds, bytesLimit = DEFAULT_BYTES_LIMIT) { +const createStreamPromise = function ( + stream: IncomingMessage, + timeoutSeconds: number, + bytesLimit = DEFAULT_BYTES_LIMIT, +): Promise { return new Promise(function streamPromiseFunc(resolve, reject) { - // @ts-expect-error TS(7034) FIXME: Variable 'data' implicitly has type 'any[]' in som... Remove this comment to see the full error message - let data = [] + let data: unknown[] | null = [] let dataLength = 0 // @ts-expect-error TS(7034) FIXME: Variable 'timeoutId' implicitly has type 'any' in ... Remove this comment to see the full error message - let timeoutId = null + let timeoutId: NodeJS.Timeout = null if (timeoutSeconds != null && Number.isFinite(timeoutSeconds)) { timeoutId = setTimeout(() => { - // @ts-expect-error TS(2322) FIXME: Type 'null' is not assignable to type 'any[]'. data = null reject(new Error('Request timed out waiting for body')) }, timeoutSeconds * SEC_TO_MILLISEC) } - // @ts-expect-error TS(7006) FIXME: Parameter 'chunk' implicitly has an 'any' type. stream.on('data', function onData(chunk) { - // @ts-expect-error TS(7005) FIXME: Variable 'data' implicitly has an 'any[]' type. if (!Array.isArray(data)) { // Stream harvesting closed return } dataLength += chunk.length if (dataLength > bytesLimit) { - // @ts-expect-error TS(2322) FIXME: Type 'null' is not assignable to type 'any[]'. data = null reject(new Error('Stream body too big')) } else { @@ -39,18 +38,13 @@ const createStreamPromise = function (stream, timeoutSeconds, bytesLimit = DEFAU } }) - // @ts-expect-error TS(7006) FIXME: Parameter 'error' implicitly has an 'any' type. stream.on('error', function onError(error) { - // @ts-expect-error TS(2322) FIXME: Type 'null' is not assignable to type 'any[]'. data = null reject(error) - // @ts-expect-error TS(7005) FIXME: Variable 'timeoutId' implicitly has an 'any' type. clearTimeout(timeoutId) }) stream.on('end', function onEnd() { - // @ts-expect-error TS(7005) FIXME: Variable 'timeoutId' implicitly has an 'any' type. clearTimeout(timeoutId) - // @ts-expect-error TS(7005) FIXME: Variable 'data' implicitly has an 'any[]' type. if (data) { // @ts-expect-error TS(7005) FIXME: Variable 'data' implicitly has an 'any[]' type. resolve(Buffer.concat(data)) diff --git a/src/utils/proxy-server.ts b/src/utils/proxy-server.ts index 0cd4f5ff6e7..c450b2033a5 100644 --- a/src/utils/proxy-server.ts +++ b/src/utils/proxy-server.ts @@ -1,20 +1,26 @@ +import BaseCommand from '../commands/base-command.js' +import { $TSFixMe, NetlifyOptions } from '../commands/types.js' +import { BlobsContext } from '../lib/blobs/blobs.js' +import { FunctionsRegistry } from '../lib/functions/registry.js' + import { exit, log, NETLIFYDEVERR } from './command-helpers.js' import { startProxy } from './proxy.js' +import type StateConfig from './state-config.js' +import { ServerSettings } from './types.js' -/** - * @typedef {Object} InspectSettings - * @property {boolean} enabled - Inspect enabled - * @property {boolean} pause - Pause on breakpoints - * @property {string|undefined} address - Host/port override (optional) - */ +interface InspectSettings { + // Inspect enabled + enabled: boolean + // Pause on breakpoints + pause: boolean + // Host/port override (optional) + address?: string +} -/** - * @param {boolean|string} edgeInspect - * @param {boolean|string} edgeInspectBrk - * @returns {InspectSettings} - */ -// @ts-expect-error TS(7006) FIXME: Parameter 'edgeInspect' implicitly has an 'any' ty... Remove this comment to see the full error message -export const generateInspectSettings = (edgeInspect, edgeInspectBrk) => { +export const generateInspectSettings = ( + edgeInspect: boolean | string, + edgeInspectBrk: boolean | string, +): InspectSettings => { const enabled = Boolean(edgeInspect) || Boolean(edgeInspectBrk) const pause = Boolean(edgeInspectBrk) const getAddress = () => { @@ -33,71 +39,49 @@ export const generateInspectSettings = (edgeInspect, edgeInspectBrk) => { } } -/** - * - * @param {object} params - * @param {string=} params.accountId - * @param {*} params.addonsUrls - * @param {import("../lib/blobs/blobs.js").BlobsContext} blobsContext - * @param {import('../commands/types.js').NetlifyOptions["config"]} params.config - * @param {string} [params.configPath] An override for the Netlify config path - * @param {boolean} params.debug - * @param {import('../commands/types.js').NetlifyOptions["cachedConfig"]['env']} params.env - * @param {InspectSettings} params.inspectSettings - * @param {() => Promise} params.getUpdatedConfig - * @param {string} params.geolocationMode - * @param {string} params.geoCountry - * @param {*} params.settings - * @param {boolean} params.offline - * @param {object} params.site - * @param {*} params.siteInfo - * @param {string} params.projectDir - * @param {string} params.repositoryRoot - * @param {import('./state-config.js').default} params.state - * @param {import('../lib/functions/registry.js').FunctionsRegistry=} params.functionsRegistry - * @returns - */ export const startProxyServer = async ({ - // @ts-expect-error TS(7031) FIXME: Binding element 'accountId' implicitly has an 'any... Remove this comment to see the full error message accountId, - // @ts-expect-error TS(7031) FIXME: Binding element 'addonsUrls' implicitly has an 'an... Remove this comment to see the full error message addonsUrls, - // @ts-expect-error TS(7031) FIXME: Binding element 'blobsContext' implicitly has an '... Remove this comment to see the full error message blobsContext, - // @ts-expect-error TS(7031) FIXME: Binding element 'accountId' implicitly has an 'any... Remove this comment to see the full error message command, - // @ts-expect-error TS(7031) FIXME: Binding element 'config' implicitly has an 'any' t... Remove this comment to see the full error message config, - // @ts-expect-error TS(7031) FIXME: Binding element 'configPath' implicitly has an 'an... Remove this comment to see the full error message configPath, - // @ts-expect-error TS(7031) FIXME: Binding element 'debug' implicitly has an 'any' ty... Remove this comment to see the full error message debug, - // @ts-expect-error TS(7031) FIXME: Binding element 'env' implicitly has an 'any' type... Remove this comment to see the full error message env, - // @ts-expect-error TS(7031) FIXME: Binding element 'functionsRegistry' implicitly has... Remove this comment to see the full error message functionsRegistry, - // @ts-expect-error TS(7031) FIXME: Binding element 'geoCountry' implicitly has an 'an... Remove this comment to see the full error message geoCountry, - // @ts-expect-error TS(7031) FIXME: Binding element 'geolocationMode' implicitly has a... Remove this comment to see the full error message geolocationMode, - // @ts-expect-error TS(7031) FIXME: Binding element 'getUpdatedConfig' implicitly has ... Remove this comment to see the full error message getUpdatedConfig, - // @ts-expect-error TS(7031) FIXME: Binding element 'inspectSettings' implicitly has a... Remove this comment to see the full error message inspectSettings, - // @ts-expect-error TS(7031) FIXME: Binding element 'offline' implicitly has an 'any' ... Remove this comment to see the full error message offline, - // @ts-expect-error TS(7031) FIXME: Binding element 'projectDir' implicitly has an 'an... Remove this comment to see the full error message projectDir, - // @ts-expect-error TS(7031) FIXME: Binding element 'repositoryRoot' implicitly has an... Remove this comment to see the full error message repositoryRoot, - // @ts-expect-error TS(7031) FIXME: Binding element 'settings' implicitly has an 'any'... Remove this comment to see the full error message settings, - // @ts-expect-error TS(7031) FIXME: Binding element 'site' implicitly has an 'any' typ... Remove this comment to see the full error message site, - // @ts-expect-error TS(7031) FIXME: Binding element 'siteInfo' implicitly has an 'any'... Remove this comment to see the full error message siteInfo, - // @ts-expect-error TS(7031) FIXME: Binding element 'state' implicitly has an 'any' ty... Remove this comment to see the full error message state, +}: { + accountId: string + addonsUrls: $TSFixMe + blobsContext?: BlobsContext + command: BaseCommand + config: NetlifyOptions['config'] + // An override for the Netlify config path + configPath?: string + debug: boolean + env: NetlifyOptions['cachedConfig']['env'] + inspectSettings: InspectSettings + getUpdatedConfig: () => Promise + geolocationMode: string + geoCountry: string + settings: ServerSettings + offline: boolean + site: $TSFixMe + siteInfo: $TSFixMe + projectDir: string + repositoryRoot?: string + state: StateConfig + functionsRegistry?: FunctionsRegistry }) => { const url = await startProxy({ addonsUrls, diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts index cf056ae1126..58a52815b85 100644 --- a/src/utils/proxy.ts +++ b/src/utils/proxy.ts @@ -1,10 +1,11 @@ import { Buffer } from 'buffer' import { once } from 'events' import { readFile } from 'fs/promises' -import http from 'http' +import http, { ServerResponse } from 'http' import https from 'https' import { isIPv6 } from 'net' import path from 'path' +import { Duplex } from 'stream' import util from 'util' import zlib from 'zlib' @@ -20,9 +21,12 @@ import httpProxy from 'http-proxy' import { createProxyMiddleware } from 'http-proxy-middleware' import jwtDecode from 'jwt-decode' import { locatePath } from 'locate-path' +import { Match } from 'netlify-redirector' import pFilter from 'p-filter' import toReadableStream from 'to-readable-stream' +import { BaseCommand } from '../commands/index.js' +import { $TSFixMe } from '../commands/types.js' import { handleProxyRequest, initializeProxy as initializeEdgeFunctionsProxy, @@ -40,6 +44,7 @@ import { headersForPath, parseHeaders, NFFunctionName, NFRequestID, NFFunctionRo import { generateRequestID } from './request-id.js' import { createRewriter, onChanges } from './rules-proxy.js' import { signRedirect } from './sign-redirect.js' +import { Rewriter, Request, ServerSettings } from './types.js' const gunzip = util.promisify(zlib.gunzip) const brotliDecompress = util.promisify(zlib.brotliDecompress) @@ -76,8 +81,8 @@ const formatEdgeFunctionError = (errorBuffer, acceptsHtml) => { }) } -function isInternal(url: string) { - return url.startsWith('/.netlify/') +function isInternal(url?: string): boolean { + return url?.startsWith('/.netlify/') ?? false } function isFunction(functionsPort: boolean | number | undefined, url: string) { @@ -113,8 +118,17 @@ const stripOrigin = function ({ hash, pathname, search }) { return `${pathname}${search}${hash}` } -// @ts-expect-error TS(7031) FIXME: Binding element 'dest' implicitly has an 'any' typ... Remove this comment to see the full error message -const proxyToExternalUrl = function ({ dest, destURL, req, res }) { +const proxyToExternalUrl = function ({ + dest, + destURL, + req, + res, +}: { + dest: URL + destURL: string + req: Request + res: ServerResponse +}) { console.log(`${NETLIFYDEVLOG} Proxying to ${dest}`) const handler = createProxyMiddleware({ target: dest.origin, @@ -122,6 +136,7 @@ const proxyToExternalUrl = function ({ dest, destURL, req, res }) { pathRewrite: () => destURL, ...(Buffer.isBuffer(req.originalBody) && { buffer: toReadableStream(req.originalBody) }), }) + // @ts-expect-error TS(2345) FIXME: Argument of type 'Request' is not assignable to parameter of type 'Request>'. return handler(req, res, () => {}) } @@ -176,30 +191,29 @@ const alternativePathsFor = function (url) { } const serveRedirect = async function ({ - // @ts-expect-error TS(7031) FIXME: Binding element 'env' implicitly has an 'any' type... Remove this comment to see the full error message env, - // @ts-expect-error TS(7031) FIXME: Binding element 'functionsRegistry' implicitly has... Remove this comment to see the full error message functionsRegistry, - // @ts-expect-error TS(7031) FIXME: Binding element 'imageProxy' implicitly has an 'an... Remove this comment to see the full error message imageProxy, - // @ts-expect-error TS(7031) FIXME: Binding element 'match' implicitly has an 'any' ty... Remove this comment to see the full error message match, - // @ts-expect-error TS(7031) FIXME: Binding element 'options' implicitly has an 'any' ... Remove this comment to see the full error message options, - // @ts-expect-error TS(7031) FIXME: Binding element 'proxy' implicitly has an 'any' ty... Remove this comment to see the full error message proxy, - // @ts-expect-error TS(7031) FIXME: Binding element 'req' implicitly has an 'any' type... Remove this comment to see the full error message req, - // @ts-expect-error TS(7031) FIXME: Binding element 'res' implicitly has an 'any' type... Remove this comment to see the full error message res, - // @ts-expect-error TS(7031) FIXME: Binding element 'siteInfo' implicitly has an 'any'... Remove this comment to see the full error message siteInfo, -}) { +}: { + match: Match | null +} & Record) { if (!match) return proxy.web(req, res, options) options = options || req.proxyOptions || {} options.match = null + if (match.force404) { + res.writeHead(404) + res.end(await render404(options.publicFolder)) + return + } + if (match.proxyHeaders && Object.keys(match.proxyHeaders).length >= 0) { Object.entries(match.proxyHeaders).forEach(([key, value]) => { req.headers[key] = value @@ -237,7 +251,6 @@ const serveRedirect = async function ({ if (match.exceptions && match.exceptions.JWT) { // Some values of JWT can start with :, so, make sure to normalize them const expectedRoles = new Set( - // @ts-expect-error TS(7006) FIXME: Parameter 'value' implicitly has an 'any' type. match.exceptions.JWT.split(',').map((value) => (value.startsWith(':') ? value.slice(1) : value)), ) @@ -290,11 +303,6 @@ const serveRedirect = async function ({ return proxy.web(req, res, { ...options, staticFile }) } } - if (match.force404) { - res.writeHead(404) - res.end(await render404(options.publicFolder)) - return - } if (match.force || !staticFile || !options.framework || req.method === 'POST') { // construct destination URL from redirect rule match @@ -653,35 +661,22 @@ const initializeProxy = async function ({ const onRequest = async ( { - // @ts-expect-error TS(7031) FIXME: Binding element 'addonsUrls' implicitly has an 'an... Remove this comment to see the full error message addonsUrls, - // @ts-expect-error TS(7031) FIXME: Binding element 'edgeFunctionsProxy' implicitly ha... Remove this comment to see the full error message edgeFunctionsProxy, - // @ts-expect-error TS(7031) FIXME: Binding element 'env' implicitly has an 'any' type... Remove this comment to see the full error message env, - // @ts-expect-error TS(7031) FIXME: Binding element 'functionsRegistry' implicitly has... Remove this comment to see the full error message functionsRegistry, - // @ts-expect-error TS(7031) FIXME: Binding element 'functionsServer' implicitly has a... Remove this comment to see the full error message functionsServer, - // @ts-expect-error TS(7031) FIXME: Binding element 'imageProxy' implicitly has an 'an... Remove this comment to see the full error message imageProxy, - // @ts-expect-error TS(7031) FIXME: Binding element 'proxy' implicitly has an 'any' ty... Remove this comment to see the full error message proxy, - // @ts-expect-error TS(7031) FIXME: Binding element 'rewriter' implicitly has an 'any'... Remove this comment to see the full error message rewriter, - // @ts-expect-error TS(7031) FIXME: Binding element 'settings' implicitly has an 'any'... Remove this comment to see the full error message settings, - // @ts-expect-error TS(7031) FIXME: Binding element 'siteInfo' implicitly has an 'any'... Remove this comment to see the full error message siteInfo, - }, - // @ts-expect-error TS(7006) FIXME: Parameter 'req' implicitly has an 'any' type. - req, - // @ts-expect-error TS(7006) FIXME: Parameter 'res' implicitly has an 'any' type. - res, + }: { rewriter: Rewriter; settings: ServerSettings } & Record, + req: Request, + res: ServerResponse, ) => { - req.originalBody = ['GET', 'OPTIONS', 'HEAD'].includes(req.method) - ? null - : await createStreamPromise(req, BYTES_LIMIT) + req.originalBody = + req.method && ['GET', 'OPTIONS', 'HEAD'].includes(req.method) ? null : await createStreamPromise(req, BYTES_LIMIT) if (isImageRequest(req)) { return imageProxy(req, res) @@ -696,7 +691,7 @@ const onRequest = async ( const functionMatch = functionsRegistry && (await functionsRegistry.getFunctionForURLPath(req.url, req.method, () => - getStatic(decodeURIComponent(reqToURL(req, req.url).pathname), settings.dist), + getStatic(decodeURIComponent(reqToURL(req, req.url).pathname), settings.dist ?? ''), )) if (functionMatch) { // Setting an internal header with the function name so that we don't @@ -726,9 +721,9 @@ const onRequest = async ( const options = { match, addonsUrls, - target: `http://${isIPv6(settings.frameworkHost) ? `[${settings.frameworkHost}]` : settings.frameworkHost}:${ - settings.frameworkPort - }`, + target: `http://${ + settings.frameworkHost && isIPv6(settings.frameworkHost) ? `[${settings.frameworkHost}]` : settings.frameworkHost + }:${settings.frameworkPort}`, publicFolder: settings.dist, functionsServer, functionsPort: settings.functionsPort, @@ -767,56 +762,32 @@ const onRequest = async ( proxy.web(req, res, options) } -/** - * @param {Pick} settings - * @returns - */ -// @ts-expect-error TS(7006) FIXME: Parameter 'settings' implicitly has an 'any' type. -export const getProxyUrl = function (settings) { +export const getProxyUrl = function (settings: Pick) { const scheme = settings.https ? 'https' : 'http' return `${scheme}://localhost:${settings.port}` } export const startProxy = async function ({ - // @ts-expect-error TS(7031) FIXME: Binding element 'accountId' implicitly has an 'any... Remove this comment to see the full error message accountId, - // @ts-expect-error TS(7031) FIXME: Binding element 'addonsUrls' implicitly has an 'an... Remove this comment to see the full error message addonsUrls, - // @ts-expect-error TS(7031) FIXME: Binding element 'blobsContext' implicitly has an '... Remove this comment to see the full error message blobsContext, - // @ts-expect-error TS(7031) FIXME: Binding element 'accountId' implicitly has an 'any... Remove this comment to see the full error message command, - // @ts-expect-error TS(7031) FIXME: Binding element 'config' implicitly has an 'any' t... Remove this comment to see the full error message config, - // @ts-expect-error TS(7031) FIXME: Binding element 'configPath' implicitly has an 'an... Remove this comment to see the full error message configPath, - // @ts-expect-error TS(7031) FIXME: Binding element 'debug' implicitly has an 'any' ty... Remove this comment to see the full error message debug, - // @ts-expect-error TS(7031) FIXME: Binding element 'env' implicitly has an 'any' type... Remove this comment to see the full error message env, - // @ts-expect-error TS(7031) FIXME: Binding element 'functionsRegistry' implicitly has... Remove this comment to see the full error message functionsRegistry, - // @ts-expect-error TS(7031) FIXME: Binding element 'geoCountry' implicitly has an 'an... Remove this comment to see the full error message geoCountry, - // @ts-expect-error TS(7031) FIXME: Binding element 'geolocationMode' implicitly has a... Remove this comment to see the full error message geolocationMode, - // @ts-expect-error TS(7031) FIXME: Binding element 'getUpdatedConfig' implicitly has ... Remove this comment to see the full error message getUpdatedConfig, - // @ts-expect-error TS(7031) FIXME: Binding element 'inspectSettings' implicitly has a... Remove this comment to see the full error message inspectSettings, - // @ts-expect-error TS(7031) FIXME: Binding element 'offline' implicitly has an 'any' ... Remove this comment to see the full error message offline, - // @ts-expect-error TS(7031) FIXME: Binding element 'projectDir' implicitly has an 'an... Remove this comment to see the full error message projectDir, - // @ts-expect-error TS(7031) FIXME: Binding element 'repositoryRoot' implicitly has an... Remove this comment to see the full error message repositoryRoot, - // @ts-expect-error TS(7031) FIXME: Binding element 'settings' implicitly has an 'any'... Remove this comment to see the full error message settings, - // @ts-expect-error TS(7031) FIXME: Binding element 'siteInfo' implicitly has an 'any'... Remove this comment to see the full error message siteInfo, - // @ts-expect-error TS(7031) FIXME: Binding element 'state' implicitly has an 'any' ty... Remove this comment to see the full error message state, -}) { +}: { command: BaseCommand; settings: ServerSettings } & Record) { const secondaryServerPort = settings.https ? await getAvailablePort() : null const functionsServer = settings.functionsPort ? `http://127.0.0.1:${settings.functionsPort}` : null const edgeFunctionsProxy = await initializeEdgeFunctionsProxy({ @@ -882,8 +853,7 @@ export const startProxy = async function ({ const primaryServer = settings.https ? https.createServer({ cert: settings.https.cert, key: settings.https.key }, onRequestWithOptions) : http.createServer(onRequestWithOptions) - // @ts-expect-error TS(7006) FIXME: Parameter 'req' implicitly has an 'any' type. - const onUpgrade = function onUpgrade(req, socket, head) { + const onUpgrade = function onUpgrade(req: http.IncomingMessage, socket: Duplex, head: Buffer) { proxy.ws(req, socket, head) } diff --git a/src/utils/rules-proxy.ts b/src/utils/rules-proxy.ts index 40edd5a6d17..a4e6b271567 100644 --- a/src/utils/rules-proxy.ts +++ b/src/utils/rules-proxy.ts @@ -3,14 +3,15 @@ import path from 'path' import chokidar from 'chokidar' // @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'cook... Remove this comment to see the full error message import cookie from 'cookie' -// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'netl... Remove this comment to see the full error message import redirector from 'netlify-redirector' +import type { Match, RedirectMatcher } from 'netlify-redirector' import pFilter from 'p-filter' import { fileExistsAsync } from '../lib/fs.js' import { NETLIFYDEVLOG } from './command-helpers.js' import { parseRedirects } from './redirects.js' +import { Request, 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 = [] @@ -54,9 +55,8 @@ export const createRewriter = async function ({ jwtSecret, // @ts-expect-error TS(7031) FIXME: Binding element 'projectDir' implicitly has an 'an... Remove this comment to see the full error message projectDir, -}) { - // @ts-expect-error TS(7034) FIXME: Variable 'matcher' implicitly has type 'any' in so... Remove this comment to see the full error message - let matcher = null +}): Promise { + let matcher: RedirectMatcher | null = null const redirectsFiles = [...new Set([path.resolve(distDir, '_redirects'), path.resolve(projectDir, '_redirects')])] let redirects = await parseRedirects({ config, redirectsFiles, configPath }) @@ -71,8 +71,7 @@ export const createRewriter = async function ({ matcher = null }) - const getMatcher = async () => { - // @ts-expect-error TS(7005) FIXME: Variable 'matcher' implicitly has an 'any' type. + const getMatcher = async (): Promise => { if (matcher) return matcher if (redirects.length !== 0) { @@ -89,16 +88,16 @@ export const createRewriter = async function ({ } // @ts-expect-error TS(7006) FIXME: Parameter 'req' implicitly has an 'any' type. - return async function rewriter(req) { + return async function rewriter(req: Request): Promise { const matcherFunc = await getMatcher() const reqUrl = new URL( - req.url, + req.url ?? '', `${req.protocol || (req.headers.scheme && `${req.headers.scheme}:`) || 'http:'}//${ req.hostname || req.headers.host }`, ) const cookieValues = cookie.parse(req.headers.cookie || '') - const headers = { + const headers: Record = { 'x-language': cookieValues.nf_lang || getLanguage(req.headers), 'x-country': cookieValues.nf_country || geoCountry || 'us', ...req.headers, @@ -112,10 +111,14 @@ export const createRewriter = async function ({ query: reqUrl.search.slice(1), headers, cookieValues, - // @ts-expect-error TS(7006) FIXME: Parameter 'name' implicitly has an 'any' type. - getHeader: (name) => headers[name.toLowerCase()] || '', - // @ts-expect-error TS(7006) FIXME: Parameter 'key' implicitly has an 'any' type. - getCookie: (key) => cookieValues[key] || '', + getHeader: (name: string) => { + const val = headers[name.toLowerCase()] + if (Array.isArray(val)) { + return val[0] + } + return val || '' + }, + getCookie: (key: string) => cookieValues[key] || '', } const match = matcherFunc.match(matchReq) return match diff --git a/src/utils/run-build.ts b/src/utils/run-build.ts index 055a67b27a8..aefe93d88cf 100644 --- a/src/utils/run-build.ts +++ b/src/utils/run-build.ts @@ -1,14 +1,17 @@ import { promises as fs } from 'fs' import path, { join } from 'path' +import BaseCommand from '../commands/base-command.js' +import { $TSFixMe } from '../commands/types.js' import { getBootstrapURL } from '../lib/edge-functions/bootstrap.js' import { INTERNAL_EDGE_FUNCTIONS_FOLDER } from '../lib/edge-functions/consts.js' import { getPathInProject } from '../lib/settings.js' import { error } from './command-helpers.js' +import { getFeatureFlagsFromSiteInfo } from './feature-flags.js' import { startFrameworkServer } from './framework-server.js' import { INTERNAL_FUNCTIONS_FOLDER } from './functions/index.js' -import { getFeatureFlagsFromSiteInfo } from './feature-flags.js' +import { ServerSettings } from './types.js' const netlifyBuildPromise = import('@netlify/build') @@ -45,18 +48,20 @@ const cleanInternalDirectory = async (basePath) => { await Promise.all(ops) } -/** - * @param {object} params - * @param {import('../commands/base-command.js').default} params.command - * @param {import('../commands/base-command.js').default} params.command - * @param {*} params.options The flags of the command - * @param {import('./types.js').ServerSettings} params.settings - * @param {NodeJS.ProcessEnv} [params.env] - * @param {'build' | 'dev'} [params.timeline] - * @returns - */ -// @ts-expect-error TS(7031) FIXME: Binding element 'command' implicitly has an 'any' ... Remove this comment to see the full error message -export const runNetlifyBuild = async ({ command, env = {}, options, settings, timeline = 'build' }) => { +export const runNetlifyBuild = async ({ + command, + env = {}, + options, + settings, + timeline = 'build', +}: { + command: BaseCommand + // The flags of the command + options: $TSFixMe + settings: ServerSettings + env: NodeJS.ProcessEnv + timeline: 'build' | 'dev' +}) => { const { apiOpts, cachedConfig, site } = command.netlify const { default: buildSite, startDev } = await netlifyBuildPromise @@ -158,11 +163,8 @@ export const runNetlifyBuild = async ({ command, env = {}, options, settings, ti return { configMutations } } -/** - * @param {Omit[0], 'timeline'>} options - */ -// @ts-expect-error TS(7006) FIXME: Parameter 'options' implicitly has an 'any' type. -export const runDevTimeline = (options) => runNetlifyBuild({ ...options, timeline: 'dev' }) +export const runDevTimeline = (options: Omit[0], 'timeline'>) => + runNetlifyBuild({ ...options, timeline: 'dev' }) /** * @param {Omit[0], 'timeline'>} options diff --git a/src/utils/types.ts b/src/utils/types.ts index 9491106febd..22ff19ab3af 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,3 +1,8 @@ +import { Buffer } from 'buffer' +import { IncomingMessage } from 'http' + +import { Match } from 'netlify-redirector' + export type FrameworkNames = '#static' | '#auto' | '#custom' | string export type FrameworkInfo = { @@ -48,3 +53,11 @@ export type ServerSettings = BaseServerSettings & { https?: { key: string; cert: string; keyFilePath: string; certFilePath: string } clearPublishDirectory?: boolean } + +export interface Request extends IncomingMessage { + originalBody?: Buffer | null + protocol?: string + hostname?: string +} + +export type Rewriter = (req: Request) => Match | null diff --git a/types/netlify-redirector/index.d.ts b/types/netlify-redirector/index.d.ts new file mode 100644 index 00000000000..f09c231e6d0 --- /dev/null +++ b/types/netlify-redirector/index.d.ts @@ -0,0 +1,39 @@ +declare module 'netlify-redirector' { + export interface Options { + jwtSecret?: string + jwtRoleClaim?: string + } + export interface Request { + scheme: string + host: string + path: string + query: string + getHeader: (name: string) => string + getCookie: (name: string) => string + } + export type Match = ( + | { + from: string + to: string + host: string + scheme: string + status: number + force: boolean + negative: boolean + proxyHeaders?: Record + signingSecret?: string + } + | { + force404: true + } + ) & { + force404?: boolean + conditions: Record + exceptions: Record + } + export interface RedirectMatcher { + match(req: Request): Match | null + } + export function parsePlain(rules: string, options: Options): Promise + export function parseJSON(rules: string, options: Options): Promise +} From 904d58046f01ccd6afe2142bfdce86dbe81cec2c Mon Sep 17 00:00:00 2001 From: Marcus Weiner Date: Tue, 19 Mar 2024 23:35:14 +0100 Subject: [PATCH 2/5] fix: reduce log level for proxy handler --- src/utils/proxy.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts index 58a52815b85..4c75f308258 100644 --- a/src/utils/proxy.ts +++ b/src/utils/proxy.ts @@ -134,6 +134,8 @@ const proxyToExternalUrl = function ({ target: dest.origin, changeOrigin: true, pathRewrite: () => destURL, + // hide debug logging + logLevel: 'info', ...(Buffer.isBuffer(req.originalBody) && { buffer: toReadableStream(req.originalBody) }), }) // @ts-expect-error TS(2345) FIXME: Argument of type 'Request' is not assignable to parameter of type 'Request>'. From c65666e54018e606f5b36afe447fc18474472e27 Mon Sep 17 00:00:00 2001 From: Marcus Weiner Date: Tue, 19 Mar 2024 23:35:53 +0100 Subject: [PATCH 3/5] feat: allow proxy rules to be hidden when inserted --- src/utils/proxy.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts index 4c75f308258..caba0f75295 100644 --- a/src/utils/proxy.ts +++ b/src/utils/proxy.ts @@ -129,7 +129,6 @@ const proxyToExternalUrl = function ({ req: Request res: ServerResponse }) { - console.log(`${NETLIFYDEVLOG} Proxying to ${dest}`) const handler = createProxyMiddleware({ target: dest.origin, changeOrigin: true, @@ -330,6 +329,14 @@ const serveRedirect = async function ({ // This is a redirect, so we set the complete external URL as destination destURL = `${dest}` } else { + const isHiddenProxy = + match.proxyHeaders && + Object.entries(match.proxyHeaders).some( + ([key, val]) => key.toLowerCase() === 'x-nf-hidden-proxy' && val === 'true', + ) + if (!isHiddenProxy) { + console.log(`${NETLIFYDEVLOG} Proxying to ${dest}`) + } return proxyToExternalUrl({ req, res, dest, destURL }) } } From eb4f20f714fd0011c44043c01c8f2a0633c262de Mon Sep 17 00:00:00 2001 From: Marcus Weiner Date: Wed, 20 Mar 2024 12:40:59 +0100 Subject: [PATCH 4/5] fix: adjust log level to hide internals --- src/utils/proxy.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts index caba0f75295..c91e4705f7c 100644 --- a/src/utils/proxy.ts +++ b/src/utils/proxy.ts @@ -133,8 +133,8 @@ const proxyToExternalUrl = function ({ target: dest.origin, changeOrigin: true, pathRewrite: () => destURL, - // hide debug logging - logLevel: 'info', + // hide logging + logLevel: 'warn', ...(Buffer.isBuffer(req.originalBody) && { buffer: toReadableStream(req.originalBody) }), }) // @ts-expect-error TS(2345) FIXME: Argument of type 'Request' is not assignable to parameter of type 'Request>'. From 61f0a34dcdfc3736f918b382a8eda0e70fe4ee45 Mon Sep 17 00:00:00 2001 From: Marcus Weiner Date: Wed, 20 Mar 2024 12:41:18 +0100 Subject: [PATCH 5/5] chore: test hiding logs --- .../commands/dev/dev-forms-and-redirects.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/integration/commands/dev/dev-forms-and-redirects.test.js b/tests/integration/commands/dev/dev-forms-and-redirects.test.js index b6c408c8702..fc3ba6f12cf 100644 --- a/tests/integration/commands/dev/dev-forms-and-redirects.test.js +++ b/tests/integration/commands/dev/dev-forms-and-redirects.test.js @@ -399,6 +399,9 @@ describe.concurrent('commands/dev-forms-and-redirects', () => { from: '/baz/*', to: 'http://localhost:${userServerPort}/:splat', status: 200, + headers: { + "X-NF-Hidden-Proxy": "true", + }, }); const server = http.createServer((_, res) => res.end("Hello world")); @@ -440,6 +443,9 @@ describe.concurrent('commands/dev-forms-and-redirects', () => { t.expect(response1.headers.get('x-test')).toEqual('value') t.expect(await response2.text()).toEqual('Hello world') t.expect(await response3.text()).toEqual('Hello world') + + t.expect(server.output).not.toContain(`Proxying to http://localhost:${userServerPort}/path`) + t.expect(server.output).not.toContain(`[HPM] Proxy created: / -> http://localhost:${userServerPort}`) }) }) })