From 698febdbd69c85d6dfa32898b9156445741e9700 Mon Sep 17 00:00:00 2001 From: Philippe Serhal Date: Tue, 25 Feb 2025 15:32:45 -0500 Subject: [PATCH 01/11] fix(types): fix @netlify/headers-parser types These types were very weak, containing plentiful `any`s, both explicit and implicit, as well as some incorrect inferred types. --- packages/headers-parser/src/all.ts | 11 ++- .../src/{for_regexp.js => for_regexp.ts} | 6 +- packages/headers-parser/src/index.ts | 2 +- packages/headers-parser/src/line_parser.ts | 43 +++++++---- packages/headers-parser/src/merge.ts | 19 +++-- .../src/netlify_config_parser.ts | 4 +- packages/headers-parser/src/normalize.ts | 76 ++++++++++++++----- packages/headers-parser/src/results.ts | 10 +-- packages/headers-parser/src/types.ts | 7 +- packages/headers-parser/tests/helpers/main.ts | 18 ++++- packages/headers-parser/tsconfig.json | 11 ++- 11 files changed, 143 insertions(+), 64 deletions(-) rename packages/headers-parser/src/{for_regexp.js => for_regexp.ts} (84%) diff --git a/packages/headers-parser/src/all.ts b/packages/headers-parser/src/all.ts index 8310197cb7..6f405402ee 100644 --- a/packages/headers-parser/src/all.ts +++ b/packages/headers-parser/src/all.ts @@ -3,6 +3,8 @@ import { mergeHeaders } from './merge.js' import { parseConfigHeaders } from './netlify_config_parser.js' import { normalizeHeaders } from './normalize.js' import { splitResults, concatResults } from './results.js' +import type { Header, MinimalHeader } from './types.js' +export type { Header, MinimalHeader } // Parse all headers from `netlify.toml` and `_headers` file, then normalize // and validate those. @@ -11,6 +13,11 @@ export const parseAllHeaders = async function ({ netlifyConfigPath, configHeaders = [], minimal = false, +}: { + headersFiles?: undefined | string[] + netlifyConfigPath?: undefined | string + configHeaders: undefined | MinimalHeader[] + minimal: undefined | boolean }) { const [ { headers: fileHeaders, errors: fileParseErrors }, @@ -37,12 +44,12 @@ export const parseAllHeaders = async function ({ return { headers, errors } } -const getFileHeaders = async function (headersFiles) { +const getFileHeaders = async function (headersFiles: string[]) { const resultsArrays = await Promise.all(headersFiles.map(parseFileHeaders)) return concatResults(resultsArrays) } -const getConfigHeaders = async function (netlifyConfigPath) { +const getConfigHeaders = async function (netlifyConfigPath?: undefined | string) { if (netlifyConfigPath === undefined) { return splitResults([]) } diff --git a/packages/headers-parser/src/for_regexp.js b/packages/headers-parser/src/for_regexp.ts similarity index 84% rename from packages/headers-parser/src/for_regexp.js rename to packages/headers-parser/src/for_regexp.ts index 480847a338..c5e34c79ce 100644 --- a/packages/headers-parser/src/for_regexp.js +++ b/packages/headers-parser/src/for_regexp.ts @@ -1,16 +1,16 @@ import escapeStringRegExp from 'escape-string-regexp' // Retrieve `forRegExp` which is a `RegExp` used to match the `for` path -export const getForRegExp = function (forPath) { +export const getForRegExp = function (forPath: string): RegExp { const pattern = forPath.split('/').map(trimString).filter(Boolean).map(getPartRegExp).join('/') return new RegExp(`^/${pattern}/?$`, 'iu') } -const trimString = function (part) { +const trimString = function (part: string): string { return part.trimEnd() } -const getPartRegExp = function (part) { +const getPartRegExp = function (part: string): string { // Placeholder like `/segment/:placeholder/test` // Matches everything up to a / if (part.startsWith(':')) { diff --git a/packages/headers-parser/src/index.ts b/packages/headers-parser/src/index.ts index 2977851a2c..93d6465a5e 100644 --- a/packages/headers-parser/src/index.ts +++ b/packages/headers-parser/src/index.ts @@ -1 +1 @@ -export { parseAllHeaders } from './all.js' +export { parseAllHeaders, type Header, type MinimalHeader } from './all.js' diff --git a/packages/headers-parser/src/line_parser.ts b/packages/headers-parser/src/line_parser.ts index 8134247ef4..8bb979d70a 100644 --- a/packages/headers-parser/src/line_parser.ts +++ b/packages/headers-parser/src/line_parser.ts @@ -1,12 +1,20 @@ -import { promises as fs } from 'fs' +import fs from 'fs/promises' import { pathExists } from 'path-exists' import { splitResults } from './results.js' +import type { MinimalHeader } from './types.js' + +type RawHeader = { path: string } | { name: string; value: string } + +export interface ParseHeadersResult { + headers: MinimalHeader[] + errors: Error[] +} // Parse `_headers` file to an array of objects following the same syntax as // the `headers` property in `netlify.toml` -export const parseFileHeaders = async function (headersFile: string) { +export const parseFileHeaders = async function (headersFile: string): Promise { const results = await parseHeaders(headersFile) const { headers, errors: parseErrors } = splitResults(results) const { headers: reducedHeaders, errors: reducedErrors } = headers.reduce(reduceLine, { headers: [], errors: [] }) @@ -14,7 +22,7 @@ export const parseFileHeaders = async function (headersFile: string) { return { headers: reducedHeaders, errors } } -const parseHeaders = async function (headersFile: string) { +const parseHeaders = async function (headersFile: string): Promise> { if (!(await pathExists(headersFile))) { return [] } @@ -23,7 +31,12 @@ const parseHeaders = async function (headersFile: string) { if (typeof text !== 'string') { return [text] } - return text.split('\n').map(normalizeLine).filter(hasHeader).map(parseLine).filter(Boolean) + return text + .split('\n') + .map(normalizeLine) + .filter(hasHeader) + .map(parseLine) + .filter((line): line is RawHeader => line != null) } const readHeadersFile = async function (headersFile: string) { @@ -38,22 +51,22 @@ const normalizeLine = function (line: string, index: number) { return { line: line.trim(), index } } -const hasHeader = function ({ line }) { +const hasHeader = function ({ line }: { line: string }) { return line !== '' && !line.startsWith('#') } -const parseLine = function ({ line, index }) { +const parseLine = function ({ line, index }: { line: string; index: number }) { try { return parseHeaderLine(line) } catch (error) { return new Error(`Could not parse header line ${index + 1}: ${line} -${error.message}`) +${error instanceof Error ? error.message : error?.toString()}`) } } // Parse a single header line -const parseHeaderLine = function (line: string) { +const parseHeaderLine = function (line: string): undefined | RawHeader { if (isPathLine(line)) { return { path: line } } @@ -63,7 +76,7 @@ const parseHeaderLine = function (line: string) { } const [rawName, ...rawValue] = line.split(HEADER_SEPARATOR) - const name = rawName.trim() + const name = rawName?.trim() ?? '' if (name === '') { throw new Error(`Missing header name`) @@ -83,18 +96,20 @@ const isPathLine = function (line: string) { const HEADER_SEPARATOR = ':' -const reduceLine = function ({ headers, errors }, { path, name, value }) { - if (path !== undefined) { +const reduceLine = function ({ headers, errors }: ParseHeadersResult, parsedHeader: RawHeader): ParseHeadersResult { + if ('path' in parsedHeader) { + const { path } = parsedHeader return { headers: [...headers, { for: path, values: {} }], errors } } - if (headers.length === 0) { + const { name, value } = parsedHeader + const previousHeaders = headers.slice(0, -1) + const currentHeader = headers[headers.length - 1] + if (headers.length === 0 || currentHeader == null) { const error = new Error(`Path should come before header "${name}"`) return { headers, errors: [...errors, error] } } - const previousHeaders = headers.slice(0, -1) - const currentHeader = headers[headers.length - 1] const { values } = currentHeader const newValue = values[name] === undefined ? value : `${values[name]}, ${value}` const newHeaders = [...previousHeaders, { ...currentHeader, values: { ...values, [name]: newValue } }] diff --git a/packages/headers-parser/src/merge.ts b/packages/headers-parser/src/merge.ts index f6e75b93ba..f408981f39 100644 --- a/packages/headers-parser/src/merge.ts +++ b/packages/headers-parser/src/merge.ts @@ -1,7 +1,7 @@ import stringify from 'fast-safe-stringify' import { splitResults } from './results.js' -import type { Header } from './types.js' +import type { Header, MinimalHeader } from './types.js' // Merge headers from `_headers` with the ones from `netlify.toml`. // When: @@ -21,8 +21,8 @@ export const mergeHeaders = function ({ fileHeaders, configHeaders, }: { - fileHeaders: (Error | Header)[] - configHeaders: (Error | Header)[] + fileHeaders: MinimalHeader[] | Header[] + configHeaders: MinimalHeader[] | Header[] }) { const results = [...fileHeaders, ...configHeaders] const { headers, errors } = splitResults(results) @@ -35,15 +35,14 @@ export const mergeHeaders = function ({ // `netlifyConfig.headers` is modified by plugins. // The latest duplicate value is the one kept, hence why we need to iterate the // array backwards and reverse it at the end -const removeDuplicates = function (headers: Header[]) { +const removeDuplicates = function (headers: MinimalHeader[] | Header[]) { const uniqueHeaders = new Set() - const result: Header[] = [] - for (let i = headers.length - 1; i >= 0; i--) { - const h = headers[i] - const key = generateHeaderKey(h) + const result: (MinimalHeader | Header)[] = [] + for (const header of [...headers].reverse()) { + const key = generateHeaderKey(header) if (uniqueHeaders.has(key)) continue uniqueHeaders.add(key) - result.push(h) + result.push(header) } return result.reverse() } @@ -51,7 +50,7 @@ const removeDuplicates = function (headers: Header[]) { // We generate a unique header key based on JSON stringify. However, because some // properties can be regexes, we need to replace those by their toString representation // given the default will be and empty object -const generateHeaderKey = function (header: Header) { +const generateHeaderKey = function (header: MinimalHeader | Header): string { return stringify.default.stableStringify(header, (_, value) => { if (value instanceof RegExp) return value.toString() return value diff --git a/packages/headers-parser/src/netlify_config_parser.ts b/packages/headers-parser/src/netlify_config_parser.ts index 743435cb7c..7015512d21 100644 --- a/packages/headers-parser/src/netlify_config_parser.ts +++ b/packages/headers-parser/src/netlify_config_parser.ts @@ -4,6 +4,7 @@ import { parse as loadToml } from '@iarna/toml' import { pathExists } from 'path-exists' import { splitResults } from './results.js' +import type { MinimalHeader } from './types.js' // Parse `headers` field in "netlify.toml" to an array of objects. // This field is already an array of objects, so it only validates and @@ -27,7 +28,8 @@ const parseConfig = async function (configPath: string) { if (!Array.isArray(headers)) { throw new TypeError(`"headers" must be an array`) } - return headers + // TODO(serhalp) Validate shape instead of assuming and asserting type + return headers as MinimalHeader[] } catch (error) { return [new Error(`Could not parse configuration file: ${error}`)] } diff --git a/packages/headers-parser/src/normalize.ts b/packages/headers-parser/src/normalize.ts index 2cb72a840c..4fc67881c3 100644 --- a/packages/headers-parser/src/normalize.ts +++ b/packages/headers-parser/src/normalize.ts @@ -4,39 +4,71 @@ import type { Mapper } from 'map-obj' import { getForRegExp } from './for_regexp.js' import { splitResults } from './results.js' -import type { Header } from './types.js' +import type { Header, MinimalHeader } from './types.js' + +export interface MinimalNormalizedHeaders { + headers: MinimalHeader[] + errors: Error[] +} + +export interface NormalizedHeaders { + headers: Header[] + errors: Error[] +} // Validate and normalize an array of `headers` objects. // This step is performed after `headers` have been parsed from either // `netlify.toml` or `_headers`. -export const normalizeHeaders = function (headers: any, minimal: boolean) { +export function normalizeHeaders(headers: MinimalHeader[], minimal: true): MinimalNormalizedHeaders +export function normalizeHeaders(headers: MinimalHeader[], minimal: false): NormalizedHeaders +export function normalizeHeaders( + headers: MinimalHeader[], + minimal: boolean, +): MinimalNormalizedHeaders | NormalizedHeaders +export function normalizeHeaders( + headers: MinimalHeader[], + minimal: boolean, +): MinimalNormalizedHeaders | NormalizedHeaders { if (!Array.isArray(headers)) { const error = new TypeError(`Headers must be an array not: ${headers}`) - return splitResults([error]) + return splitResults([error]) } + // TODO(serhalp) Workaround for poor TS type narrowing. Remove once on typescript@5.8. const results = headers - .map((header, index) => parseHeader(header, index, minimal)) - .filter
(Boolean as never) + .map((header, index) => (minimal ? parseHeader(header, index, true) : parseHeader(header, index, false))) + .filter((header) => header != null) return splitResults(results) } -const parseHeader = function (header: any, index: number, minimal: boolean) { +function parseHeader(header: MinimalHeader, index: number, minimal: true): undefined | Error | MinimalHeader +function parseHeader(header: MinimalHeader, index: number, minimal: false): undefined | Error | Header +function parseHeader( + header: MinimalHeader, + index: number, + minimal: boolean, +): undefined | Error | MinimalHeader | Header { if (!isPlainObj(header)) { return new TypeError(`Header must be an object not: ${header}`) } try { - return parseHeaderObject(header, minimal) + // TODO(serhalp) Workaround for poor TS type narrowing. Remove once on typescript@5.8. + return minimal ? parseHeaderObject(header, true) : parseHeaderObject(header, false) } catch (error) { return new Error(`Could not parse header number ${index + 1}: ${JSON.stringify(header)} -${error.message}`) +${error instanceof Error ? error.message : error?.toString()}`) } } // Parse a single `headers` object -const parseHeaderObject = function ({ for: rawPath, values: rawValues }: any, minimal: boolean) { +function parseHeaderObject(header: MinimalHeader, minimal: true): undefined | MinimalHeader +function parseHeaderObject(header: MinimalHeader, minimal: false): undefined | Header +function parseHeaderObject( + { for: rawPath, values: rawValues }: Header, + minimal: boolean, +): undefined | MinimalHeader | Header { const forPath = normalizePath(rawPath) if (rawValues === undefined) { @@ -49,20 +81,22 @@ const parseHeaderObject = function ({ for: rawPath, values: rawValues }: any, mi return } - const header: Header = { + const header = { for: forPath, values, } - if (!minimal) { - header.forRegExp = getForRegExp(forPath) + if (minimal) { + return header + } + return { + ...header, + forRegExp: getForRegExp(forPath), } - - return header } // Normalize and validate the `for` field -const normalizePath = function (rawPath: any) { +const normalizePath = function (rawPath?: undefined | string): string { if (rawPath === undefined) { throw new TypeError('Missing "for" field') } @@ -75,7 +109,7 @@ const normalizePath = function (rawPath: any) { } // Normalize and validate the `values` field -const normalizeValues = function (rawValues: Record) { +const normalizeValues = function (rawValues: Record): Record { if (!isPlainObj(rawValues)) { throw new TypeError(`"values" must be an object not: ${rawValues}`) } @@ -84,7 +118,7 @@ const normalizeValues = function (rawValues: Record) { } // Normalize and validate each header `values` -const normalizeValue: Mapper, string, any> = function (rawKey: string, rawValue: any) { +const normalizeValue: Mapper, string, string> = function (rawKey, rawValue) { const key: string = rawKey.trim() if (key === '' || key === 'undefined') { throw new Error('Empty header name') @@ -94,7 +128,7 @@ const normalizeValue: Mapper, string, any> = function (rawKe return [key, value] } -const normalizeRawValue = function (key: string, rawValue: any): string { +const normalizeRawValue = function (key: string, rawValue: string | string[]): string { if (typeof rawValue === 'string') { return normalizeMultipleValues(normalizeStringValue(rawValue)) } @@ -121,13 +155,13 @@ const normalizeRawValue = function (key: string, rawValue: any): string { // for = "/*" // [headers.values] // cache-control = "max-age=0, no-cache, no-store, must-revalidate" -const normalizeMultipleValues = function (value: string) { +const normalizeMultipleValues = function (value: string): string { return value.split(MULTIPLE_VALUES_REGEXP).join(', ') } const MULTIPLE_VALUES_REGEXP = /\s*,\s*/g -const normalizeArrayItemValue = function (key: string, singleValue: any) { +const normalizeArrayItemValue = function (key: string, singleValue: string): string { if (typeof singleValue !== 'string') { throw new TypeError(`Header "${key}" value must be a string not: ${singleValue}`) } @@ -135,6 +169,6 @@ const normalizeArrayItemValue = function (key: string, singleValue: any) { return normalizeStringValue(singleValue) } -const normalizeStringValue = function (stringValue: string) { +const normalizeStringValue = function (stringValue: string): string { return stringValue.trim() } diff --git a/packages/headers-parser/src/results.ts b/packages/headers-parser/src/results.ts index 8af696ef35..ae7377c92b 100644 --- a/packages/headers-parser/src/results.ts +++ b/packages/headers-parser/src/results.ts @@ -1,18 +1,18 @@ -import type { Header } from './types.js' +import type { MinimalHeader } from './types.js' // If one header fails to parse, we still try to return the other ones -export function splitResults(results: (Error | Type)[]) { - const headers = results.filter((result) => !isError(result)) as Type[] +export function splitResults(results: (Error | T)[]) { + const headers = results.filter((result): result is T => !isError(result)) const errors = results.filter(isError) return { headers, errors } } -const isError = function (result: any): result is Error { +const isError = function (result: unknown): result is Error { return result instanceof Error } // Concatenate an array of `{ headers, errors }` -export const concatResults = function (resultsArrays: { headers: Header[]; errors: Error[] }[]) { +export const concatResults = function (resultsArrays: { headers: MinimalHeader[]; errors: Error[] }[]) { const headers = resultsArrays.flatMap(({ headers }) => headers) const errors = resultsArrays.flatMap(({ errors }) => errors) return { headers, errors } diff --git a/packages/headers-parser/src/types.ts b/packages/headers-parser/src/types.ts index 1b186de786..2bfeb95d7f 100644 --- a/packages/headers-parser/src/types.ts +++ b/packages/headers-parser/src/types.ts @@ -1,7 +1,10 @@ -export type Header = { +export interface MinimalHeader { for: string - forRegExp?: RegExp values: { [key: string]: string } } + +export interface Header extends MinimalHeader { + forRegExp: RegExp +} diff --git a/packages/headers-parser/tests/helpers/main.ts b/packages/headers-parser/tests/helpers/main.ts index fa1aab4387..639e3a18e8 100644 --- a/packages/headers-parser/tests/helpers/main.ts +++ b/packages/headers-parser/tests/helpers/main.ts @@ -1,10 +1,20 @@ import { fileURLToPath } from 'url' -import { parseAllHeaders } from '../../src/index.js' +import { type MinimalHeader, parseAllHeaders } from '../../src/index.js' const FIXTURES_DIR = fileURLToPath(new URL('../fixtures', import.meta.url)) -export const parseHeaders = async function ({ headersFiles, netlifyConfigPath, configHeaders, ...input }: any) { +export const parseHeaders = async function ({ + headersFiles, + netlifyConfigPath, + configHeaders, + ...input +}: { + headersFiles?: undefined | string[] + netlifyConfigPath?: undefined | string + configHeaders?: undefined | MinimalHeader[] + minimal?: undefined | boolean +}) { return await parseAllHeaders({ ...(headersFiles && { headersFiles: headersFiles.map(addFileFixtureDir) }), ...(netlifyConfigPath && { netlifyConfigPath: addConfigFixtureDir(netlifyConfigPath) }), @@ -15,10 +25,10 @@ export const parseHeaders = async function ({ headersFiles, netlifyConfigPath, c }) } -const addFileFixtureDir = function (name) { +const addFileFixtureDir = function (name: string): string { return `${FIXTURES_DIR}/headers_file/${name}` } -const addConfigFixtureDir = function (name) { +const addConfigFixtureDir = function (name: string): string { return `${FIXTURES_DIR}/netlify_config/${name}.toml` } diff --git a/packages/headers-parser/tsconfig.json b/packages/headers-parser/tsconfig.json index a08982a671..8fa84e9ffc 100644 --- a/packages/headers-parser/tsconfig.json +++ b/packages/headers-parser/tsconfig.json @@ -1,7 +1,16 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "outDir": "lib" /* Specify an output folder for all emitted files. */ + "outDir": "lib" /* Specify an output folder for all emitted files. */, + "noImplicitAny": true, + "strictFunctionTypes": false /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */, + "strictPropertyInitialization": true, + "useUnknownInCatchVariables": true, + "exactOptionalPropertyTypes": true, + "noImplicitReturns": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true }, "include": ["src/**/*.js", "src/**/*.ts"], "exclude": ["tests/**"] From 46a428dcde77ab42d38099be86790fd37d9b510d Mon Sep 17 00:00:00 2001 From: Philippe Serhal Date: Tue, 25 Feb 2025 15:59:02 -0500 Subject: [PATCH 02/11] chore(tsconfig): simplify tsconfig strict config We should be opting *into* strict mode and only opting *out* of some flags incrementally while we fix errors. This commit: - flips the global `strict` on - removes values being set to the default via the above - stops disabling flags that obscured no errors (or very few, which I then fixed) - moves a few flag disablings to the specific packages that require it - explictly configures strict flags for already rather strict packages --- packages/build/tsconfig.json | 3 ++- packages/nock-udp/tsconfig.json | 3 ++- packages/testing/src/fixture.ts | 2 +- packages/testing/tsconfig.json | 3 ++- packages/zip-it-and-ship-it/src/config.ts | 2 ++ .../runtimes/node/in_source_config/index.ts | 2 ++ packages/zip-it-and-ship-it/tsconfig.json | 10 +++++++++- tsconfig.base.json | 19 +------------------ 8 files changed, 21 insertions(+), 23 deletions(-) diff --git a/packages/build/tsconfig.json b/packages/build/tsconfig.json index a08982a671..fad347476b 100644 --- a/packages/build/tsconfig.json +++ b/packages/build/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "outDir": "lib" /* Specify an output folder for all emitted files. */ + "outDir": "lib" /* Specify an output folder for all emitted files. */, + "strictBindCallApply": false /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ }, "include": ["src/**/*.js", "src/**/*.ts"], "exclude": ["tests/**"] diff --git a/packages/nock-udp/tsconfig.json b/packages/nock-udp/tsconfig.json index 679c862b17..ab319d69b5 100644 --- a/packages/nock-udp/tsconfig.json +++ b/packages/nock-udp/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "outDir": "lib" /* Specify an output folder for all emitted files. */ + "outDir": "lib" /* Specify an output folder for all emitted files. */, + "strictBindCallApply": false /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ }, "include": ["src/**/*.js", "src/**/*.ts"], "exclude": ["test/**"] diff --git a/packages/testing/src/fixture.ts b/packages/testing/src/fixture.ts index 440e59d331..20ae1a26e2 100644 --- a/packages/testing/src/fixture.ts +++ b/packages/testing/src/fixture.ts @@ -219,7 +219,7 @@ export class Fixture { async runDev(devCommand: unknown): Promise { const entryPoint = startDev.bind(null, devCommand) const { logs } = await entryPoint(this.getBuildFlags()) - return [logs.stdout.join('\n'), logs.stderr.join('\n')].filter(Boolean).join('\n\n') + return [logs?.stdout.join('\n'), logs?.stderr.join('\n')].filter(Boolean).join('\n\n') } /** use the CLI entry point instead of the Node.js main function */ diff --git a/packages/testing/tsconfig.json b/packages/testing/tsconfig.json index d309b97e2c..2f11952060 100644 --- a/packages/testing/tsconfig.json +++ b/packages/testing/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "outDir": "lib" /* Specify an output folder for all emitted files. */ + "outDir": "lib" /* Specify an output folder for all emitted files. */, + "strictBindCallApply": false /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ }, "include": ["src/**/*.ts"] } diff --git a/packages/zip-it-and-ship-it/src/config.ts b/packages/zip-it-and-ship-it/src/config.ts index c8936ee642..02d22a8ad9 100644 --- a/packages/zip-it-and-ship-it/src/config.ts +++ b/packages/zip-it-and-ship-it/src/config.ts @@ -2,6 +2,8 @@ import { promises as fs } from 'fs' import { basename, extname, dirname, join } from 'path' import isPathInside from 'is-path-inside' +// @ts-expect-error(serhalp) -- Remove once https://github.com/schnittstabil/merge-options/pull/28 is merged, or replace +// this dependency. import mergeOptions from 'merge-options' import { z } from 'zod' diff --git a/packages/zip-it-and-ship-it/src/runtimes/node/in_source_config/index.ts b/packages/zip-it-and-ship-it/src/runtimes/node/in_source_config/index.ts index 484689785b..e5fb8ba772 100644 --- a/packages/zip-it-and-ship-it/src/runtimes/node/in_source_config/index.ts +++ b/packages/zip-it-and-ship-it/src/runtimes/node/in_source_config/index.ts @@ -1,6 +1,8 @@ import { dirname } from 'path' import type { ArgumentPlaceholder, Expression, SpreadElement, JSXNamespacedName } from '@babel/types' +// @ts-expect-error(serhalp) -- Remove once https://github.com/schnittstabil/merge-options/pull/28 is merged, or replace +// this dependency. import mergeOptions from 'merge-options' import { z } from 'zod' diff --git a/packages/zip-it-and-ship-it/tsconfig.json b/packages/zip-it-and-ship-it/tsconfig.json index 5d25fd675c..68763e6e0c 100644 --- a/packages/zip-it-and-ship-it/tsconfig.json +++ b/packages/zip-it-and-ship-it/tsconfig.json @@ -3,7 +3,15 @@ "compilerOptions": { "outDir": "dist" /* Specify an output folder for all emitted files. */, "esModuleInterop": true, - "strict": true + "noImplicitAny": true, + "strictFunctionTypes": true, + "strictPropertyInitialization": true, + "useUnknownInCatchVariables": false, + "exactOptionalPropertyTypes": false, + "noImplicitReturns": false, + "noUncheckedIndexedAccess": false, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": false }, "include": ["src"], "exclude": ["tests/**"] diff --git a/tsconfig.base.json b/tsconfig.base.json index 5abd2ea0ff..2730ce03d3 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1,7 +1,6 @@ { "compilerOptions": { /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ @@ -9,7 +8,6 @@ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ "target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ @@ -23,7 +21,6 @@ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ "module": "NodeNext" /* Specify what module code is generated. */, // "rootDir": "./" /* Specify the root folder within your source files. */, @@ -39,12 +36,10 @@ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, // "declarationMap": true, /* Create sourcemaps for d.ts files. */ @@ -69,36 +64,24 @@ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ // "allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */, "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, - /* Type Checking */ /* REMOVE THOSE ALL ONCE WE HAVE HANDCRAFTED TYPES */ - "strict": false /* Enable all strict type-checking options. */, + "strict": true /* Enable all strict type-checking options. */, "noImplicitAny": false /* Enable error reporting for expressions and declarations with an implied 'any' type. */, - "strictNullChecks": true /* When type checking, take into account 'null' and 'undefined'. */, "strictFunctionTypes": false /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */, - "strictBindCallApply": false /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */, "strictPropertyInitialization": false /* Check for class properties that are declared but not set in the constructor. */, - "noImplicitThis": true /* Enable error reporting when 'this' is given the type 'any'. */, "useUnknownInCatchVariables": false /* Default catch clause variables as 'unknown' instead of 'any'. */, - "alwaysStrict": false /* Ensure 'use strict' is always emitted. */, - "noUnusedLocals": true /* Enable error reporting when local variables aren't read. */, - "noUnusedParameters": true /* Raise an error when a function parameter isn't read. */, "exactOptionalPropertyTypes": false /* Interpret optional property types as written, rather than adding 'undefined'. */, "noImplicitReturns": false /* Enable error reporting for codepaths that do not explicitly return in a function. */, - "noFallthroughCasesInSwitch": false /* Enable error reporting for fallthrough cases in switch statements. */, "noUncheckedIndexedAccess": false /* Add 'undefined' to a type when accessed using an index. */, "noImplicitOverride": false /* Ensure overriding members in derived classes are marked with an override modifier. */, "noPropertyAccessFromIndexSignature": false /* Enforces using indexed accessors for keys declared using an indexed type. */, - "allowUnusedLabels": false /* Disable error reporting for unused labels. */, - "allowUnreachableCode": false /* Disable error reporting for unreachable code. */, - /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ From 1fa08ca74254ecf2cb0a6ca283b8d9562b342f96 Mon Sep 17 00:00:00 2001 From: Philippe Serhal Date: Wed, 26 Feb 2025 16:51:08 -0500 Subject: [PATCH 03/11] fix(types): mark netlify.toml [[headers]].values required --- packages/build-info/src/settings/netlify-toml.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/build-info/src/settings/netlify-toml.ts b/packages/build-info/src/settings/netlify-toml.ts index 0516c94838..1a0bd177f8 100644 --- a/packages/build-info/src/settings/netlify-toml.ts +++ b/packages/build-info/src/settings/netlify-toml.ts @@ -324,7 +324,7 @@ export type RequestHeaders = { */ export type Headers = { for: For - values?: Values + values: Values } /** * Define the actual headers. From 6618e0ae6f2734101d21867d21106ee6da5e14e8 Mon Sep 17 00:00:00 2001 From: Philippe Serhal Date: Fri, 28 Feb 2025 10:20:06 -0500 Subject: [PATCH 04/11] style: add empty line after imports --- packages/headers-parser/src/all.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/headers-parser/src/all.ts b/packages/headers-parser/src/all.ts index 6f405402ee..3af8d87e9e 100644 --- a/packages/headers-parser/src/all.ts +++ b/packages/headers-parser/src/all.ts @@ -4,6 +4,7 @@ import { parseConfigHeaders } from './netlify_config_parser.js' import { normalizeHeaders } from './normalize.js' import { splitResults, concatResults } from './results.js' import type { Header, MinimalHeader } from './types.js' + export type { Header, MinimalHeader } // Parse all headers from `netlify.toml` and `_headers` file, then normalize From 58dc8cc5ce3be694155fe827d8ed76dabae26e34 Mon Sep 17 00:00:00 2001 From: Philippe Serhal Date: Fri, 28 Feb 2025 10:20:35 -0500 Subject: [PATCH 05/11] refactor: improve variable name --- packages/headers-parser/src/line_parser.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/headers-parser/src/line_parser.ts b/packages/headers-parser/src/line_parser.ts index 8bb979d70a..b7636b1c9d 100644 --- a/packages/headers-parser/src/line_parser.ts +++ b/packages/headers-parser/src/line_parser.ts @@ -5,7 +5,7 @@ import { pathExists } from 'path-exists' import { splitResults } from './results.js' import type { MinimalHeader } from './types.js' -type RawHeader = { path: string } | { name: string; value: string } +type RawHeaderFileLine = { path: string } | { name: string; value: string } export interface ParseHeadersResult { headers: MinimalHeader[] @@ -22,7 +22,7 @@ export const parseFileHeaders = async function (headersFile: string): Promise> { +const parseHeaders = async function (headersFile: string): Promise> { if (!(await pathExists(headersFile))) { return [] } @@ -36,7 +36,7 @@ const parseHeaders = async function (headersFile: string): Promise line != null) + .filter((line): line is RawHeaderFileLine => line != null) } const readHeadersFile = async function (headersFile: string) { @@ -66,7 +66,7 @@ ${error instanceof Error ? error.message : error?.toString()}`) } // Parse a single header line -const parseHeaderLine = function (line: string): undefined | RawHeader { +const parseHeaderLine = function (line: string): undefined | RawHeaderFileLine { if (isPathLine(line)) { return { path: line } } @@ -96,7 +96,10 @@ const isPathLine = function (line: string) { const HEADER_SEPARATOR = ':' -const reduceLine = function ({ headers, errors }: ParseHeadersResult, parsedHeader: RawHeader): ParseHeadersResult { +const reduceLine = function ( + { headers, errors }: ParseHeadersResult, + parsedHeader: RawHeaderFileLine, +): ParseHeadersResult { if ('path' in parsedHeader) { const { path } = parsedHeader return { headers: [...headers, { for: path, values: {} }], errors } From 27e6287ce936ee6fd872113a3c605c74a3b58841 Mon Sep 17 00:00:00 2001 From: Philippe Serhal Date: Fri, 28 Feb 2025 10:21:37 -0500 Subject: [PATCH 06/11] docs: add inline comment explain funky type --- packages/headers-parser/src/normalize.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/headers-parser/src/normalize.ts b/packages/headers-parser/src/normalize.ts index 4fc67881c3..8a04316bba 100644 --- a/packages/headers-parser/src/normalize.ts +++ b/packages/headers-parser/src/normalize.ts @@ -31,6 +31,8 @@ export function normalizeHeaders( ): MinimalNormalizedHeaders | NormalizedHeaders { if (!Array.isArray(headers)) { const error = new TypeError(`Headers must be an array not: ${headers}`) + // This looks odd but it is correct: it takes an array of `T | Error` and returns `{values: T[]: errors: Error[]}`, + // thus when given a literal array of type `Error[]` it can't infer `T`, so we explicitly pass in `never` as `T`. return splitResults([error]) } From fc3b1aa38c1e8ddbc90c6c55123e3e6396e4d072 Mon Sep 17 00:00:00 2001 From: Philippe Serhal Date: Fri, 28 Feb 2025 10:23:02 -0500 Subject: [PATCH 07/11] refactor: remove incorrect extraneous property from type --- packages/config/src/headers.js | 8 +------- packages/config/src/main.ts | 2 +- packages/config/src/mutations/update.js | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/config/src/headers.js b/packages/config/src/headers.js index c9ff8c0bbd..4e12595bcb 100644 --- a/packages/config/src/headers.js +++ b/packages/config/src/headers.js @@ -12,17 +12,11 @@ export const getHeadersPath = function ({ build: { publish } }) { const HEADERS_FILENAME = '_headers' // Add `config.headers` -export const addHeaders = async function ({ - config: { headers: configHeaders, ...config }, - headersPath, - logs, - featureFlags, -}) { +export const addHeaders = async function ({ config: { headers: configHeaders, ...config }, headersPath, logs }) { const { headers, errors } = await parseAllHeaders({ headersFiles: [headersPath], configHeaders, minimal: true, - featureFlags, }) warnHeadersParsing(logs, errors) warnHeadersCaseSensitivity(logs, headers) diff --git a/packages/config/src/main.ts b/packages/config/src/main.ts index a9e20759e7..025ca64ec8 100644 --- a/packages/config/src/main.ts +++ b/packages/config/src/main.ts @@ -288,7 +288,7 @@ const getFullConfig = async function ({ base: baseA, } = await resolveFiles({ packagePath, config: configA, repositoryRoot, base, baseRelDir }) const headersPath = getHeadersPath(configB) - const configC = await addHeaders({ config: configB, headersPath, logs, featureFlags }) + const configC = await addHeaders({ config: configB, headersPath, logs }) const redirectsPath = getRedirectsPath(configC) const configD = await addRedirects({ config: configC, redirectsPath, logs, featureFlags }) return { configPath, config: configD, buildDir, base: baseA, redirectsPath, headersPath } diff --git a/packages/config/src/mutations/update.js b/packages/config/src/mutations/update.js index 085027525c..09fee6bc50 100644 --- a/packages/config/src/mutations/update.js +++ b/packages/config/src/mutations/update.js @@ -33,7 +33,7 @@ export const updateConfig = async function ( const inlineConfig = applyMutations({}, configMutations) const normalizedInlineConfig = ensureConfigPriority(inlineConfig, context, branch) const updatedConfig = await mergeWithConfig(normalizedInlineConfig, configPath) - const configWithHeaders = await addHeaders({ config: updatedConfig, headersPath, logs, featureFlags }) + const configWithHeaders = await addHeaders({ config: updatedConfig, headersPath, logs }) const finalConfig = await addRedirects({ config: configWithHeaders, redirectsPath, logs, featureFlags }) const simplifiedConfig = simplifyConfig(finalConfig) From 62d0dedeb7b3edb4e7ad658b0749b587c384fdc4 Mon Sep 17 00:00:00 2001 From: Philippe Serhal Date: Fri, 28 Feb 2025 10:27:01 -0500 Subject: [PATCH 08/11] fix(parseAllHeaders)!: mark `minimal` as required All callers already pass in a non-nil `boolean`, so this is only technically breaking. --- packages/headers-parser/src/all.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/headers-parser/src/all.ts b/packages/headers-parser/src/all.ts index 3af8d87e9e..bc793a9440 100644 --- a/packages/headers-parser/src/all.ts +++ b/packages/headers-parser/src/all.ts @@ -13,12 +13,12 @@ export const parseAllHeaders = async function ({ headersFiles = [], netlifyConfigPath, configHeaders = [], - minimal = false, + minimal, }: { - headersFiles?: undefined | string[] + headersFiles: undefined | string[] netlifyConfigPath?: undefined | string configHeaders: undefined | MinimalHeader[] - minimal: undefined | boolean + minimal: boolean }) { const [ { headers: fileHeaders, errors: fileParseErrors }, From e339cc64c2bd9702f519c1f378f30b24c15c92ff Mon Sep 17 00:00:00 2001 From: Philippe Serhal Date: Fri, 28 Feb 2025 11:25:38 -0500 Subject: [PATCH 09/11] fix(types)!: increase strictness of optional types --- packages/headers-parser/src/all.ts | 2 +- packages/headers-parser/src/normalize.ts | 2 +- packages/headers-parser/tests/all.test.ts | 20 +++++++++++++++---- .../headers-parser/tests/for-regexp.test.ts | 4 +++- packages/headers-parser/tests/helpers/main.ts | 14 ++++++------- .../headers-parser/tests/line-parser.test.ts | 18 +++++++++++++---- packages/headers-parser/tests/merge.bench.ts | 2 ++ packages/headers-parser/tests/merge.test.ts | 8 +++++++- .../tests/netlify-config-parser.test.ts | 18 +++++++++++++---- 9 files changed, 64 insertions(+), 24 deletions(-) diff --git a/packages/headers-parser/src/all.ts b/packages/headers-parser/src/all.ts index bc793a9440..bd0ee4f66f 100644 --- a/packages/headers-parser/src/all.ts +++ b/packages/headers-parser/src/all.ts @@ -50,7 +50,7 @@ const getFileHeaders = async function (headersFiles: string[]) { return concatResults(resultsArrays) } -const getConfigHeaders = async function (netlifyConfigPath?: undefined | string) { +const getConfigHeaders = async function (netlifyConfigPath?: string) { if (netlifyConfigPath === undefined) { return splitResults([]) } diff --git a/packages/headers-parser/src/normalize.ts b/packages/headers-parser/src/normalize.ts index 8a04316bba..45aa1081dc 100644 --- a/packages/headers-parser/src/normalize.ts +++ b/packages/headers-parser/src/normalize.ts @@ -98,7 +98,7 @@ function parseHeaderObject( } // Normalize and validate the `for` field -const normalizePath = function (rawPath?: undefined | string): string { +const normalizePath = function (rawPath?: string): string { if (rawPath === undefined) { throw new TypeError('Missing "for" field') } diff --git a/packages/headers-parser/tests/all.test.ts b/packages/headers-parser/tests/all.test.ts index ab8da9a61a..a31960152f 100644 --- a/packages/headers-parser/tests/all.test.ts +++ b/packages/headers-parser/tests/all.test.ts @@ -38,17 +38,29 @@ test.each([ { for: '/path', values: { test: 'two' } }, ], ], - ['invalid_mixed', { headersFiles: ['success'], configHeaders: {} }, [{ for: '/path', values: { test: 'one' } }]], + ['invalid_mixed', { headersFiles: ['success'], configHeaders: [] }, [{ for: '/path', values: { test: 'one' } }]], ])(`Parses netlify.toml and _headers | %s`, async (_, input, output) => { - const { headers } = await parseHeaders(input) + const { headers } = await parseHeaders({ + headersFiles: undefined, + netlifyConfigPath: undefined, + configHeaders: undefined, + minimal: true, + ...input, + }) expect(headers).toStrictEqual(output) }) test.each([ ['invalid_config_headers_array', { configHeaders: {} }, /must be an array/], ['invalid_mixed', { headersFiles: ['simple'], configHeaders: {} }, /must be an array/], -])(`Validate syntax errors | %s`, async (_, input, errorMessage) => { - const { errors } = await parseHeaders(input) +])(`Validates syntax errors | %s`, async (_, input, errorMessage) => { + // @ts-expect-error -- Intentional runtime test of invalid input for some reason + const { errors } = await parseHeaders({ + headersFiles: undefined, + netlifyConfigPath: undefined, + minimal: true, + ...input, + }) expect(errors).not.toHaveLength(0) expect(errors.some((error) => errorMessage.test(error.message))).toBeTruthy() }) diff --git a/packages/headers-parser/tests/for-regexp.test.ts b/packages/headers-parser/tests/for-regexp.test.ts index 3431393273..b3f6b59e8b 100644 --- a/packages/headers-parser/tests/for-regexp.test.ts +++ b/packages/headers-parser/tests/for-regexp.test.ts @@ -23,8 +23,10 @@ test.each([ ['double_slash_multiple', '//a//b//', /^\/a\/b\/?$/iu], ])(`Add forRegExp when minimal is false | %s`, async (_, forPath, forRegExp) => { const { headers } = await parseHeaders({ + headersFiles: undefined, + netlifyConfigPath: undefined, configHeaders: [{ for: forPath, values: { test: 'one' } }], - minimal: undefined, + minimal: false, }) expect(headers).toStrictEqual([{ for: forPath.trim(), forRegExp, values: { test: 'one' } }]) }) diff --git a/packages/headers-parser/tests/helpers/main.ts b/packages/headers-parser/tests/helpers/main.ts index 639e3a18e8..125486c1ce 100644 --- a/packages/headers-parser/tests/helpers/main.ts +++ b/packages/headers-parser/tests/helpers/main.ts @@ -8,20 +8,18 @@ export const parseHeaders = async function ({ headersFiles, netlifyConfigPath, configHeaders, - ...input + minimal, }: { - headersFiles?: undefined | string[] - netlifyConfigPath?: undefined | string - configHeaders?: undefined | MinimalHeader[] - minimal?: undefined | boolean + headersFiles: undefined | string[] + netlifyConfigPath: undefined | string + configHeaders: undefined | MinimalHeader[] + minimal: boolean }) { return await parseAllHeaders({ ...(headersFiles && { headersFiles: headersFiles.map(addFileFixtureDir) }), ...(netlifyConfigPath && { netlifyConfigPath: addConfigFixtureDir(netlifyConfigPath) }), configHeaders, - // Default `minimal` to `true` but still allows passing `undefined` to - // test the default value of that option - minimal: 'minimal' in input ? input.minimal : true, + minimal, }) } diff --git a/packages/headers-parser/tests/line-parser.test.ts b/packages/headers-parser/tests/line-parser.test.ts index a101d28cd1..33d3faabf1 100644 --- a/packages/headers-parser/tests/line-parser.test.ts +++ b/packages/headers-parser/tests/line-parser.test.ts @@ -14,8 +14,13 @@ test.each([ ['trim_name', { headersFiles: ['trim_name'] }, [{ for: '/path', values: { test: 'one' } }]], ['trim_value', { headersFiles: ['trim_value'] }, [{ for: '/path', values: { test: 'one' } }]], ['multiple_values', { headersFiles: ['multiple_values'] }, [{ for: '/path', values: { test: 'one, two' } }]], -])(`Parses _headers | %s`, async (_, input, output) => { - const { headers } = await parseHeaders(input) +])(`Parses _headers | %s`, async (_, { headersFiles }, output) => { + const { headers } = await parseHeaders({ + headersFiles, + netlifyConfigPath: undefined, + configHeaders: undefined, + minimal: true, + }) expect(headers).toStrictEqual(output) }) @@ -24,8 +29,13 @@ test.each([ ['invalid_value_name', { headersFiles: ['invalid_value_name'] }, /Missing header name/], ['invalid_value_string', { headersFiles: ['invalid_value_string'] }, /Missing header value/], ['invalid_for_order', { headersFiles: ['invalid_for_order'] }, /Path should come before/], -])(`Validate syntax errors | %s`, async (_, input, errorMessage) => { - const { errors } = await parseHeaders(input) +])(`Validate syntax errors | %s`, async (_, { headersFiles }, errorMessage) => { + const { errors } = await parseHeaders({ + headersFiles, + netlifyConfigPath: undefined, + configHeaders: undefined, + minimal: true, + }) expect(errors).not.toHaveLength(0) expect(errors.some((error) => errorMessage.test(error.message))).toBeTruthy() }) diff --git a/packages/headers-parser/tests/merge.bench.ts b/packages/headers-parser/tests/merge.bench.ts index 156380fc04..b03854b2f4 100644 --- a/packages/headers-parser/tests/merge.bench.ts +++ b/packages/headers-parser/tests/merge.bench.ts @@ -5,10 +5,12 @@ import { parseHeaders } from './helpers/main.js' bench('Merges large _headers file with config headers', async () => { const input = { headersFiles: ['large_headers_file'], + netlifyConfigPath: undefined, configHeaders: [ { for: '/base/some-1', values: { test: 'some-1' } }, { for: '/unique-header', values: { test: 'unique-value' } }, ], + minimal: true, } const { headers, errors } = await parseHeaders(input) expect(errors).toHaveLength(0) diff --git a/packages/headers-parser/tests/merge.test.ts b/packages/headers-parser/tests/merge.test.ts index 57dada1c9c..908eda6bc8 100644 --- a/packages/headers-parser/tests/merge.test.ts +++ b/packages/headers-parser/tests/merge.test.ts @@ -35,6 +35,12 @@ test.each([ ], ], ])(`Merges _headers with netlify.toml headers | %s`, async (_, input, output) => { - const { headers } = await parseHeaders(input) + const { headers } = await parseHeaders({ + headersFiles: undefined, + configHeaders: undefined, + netlifyConfigPath: undefined, + minimal: true, + ...input, + }) expect(headers).toStrictEqual(output) }) diff --git a/packages/headers-parser/tests/netlify-config-parser.test.ts b/packages/headers-parser/tests/netlify-config-parser.test.ts index 926a2e911b..9de1ddcce4 100644 --- a/packages/headers-parser/tests/netlify-config-parser.test.ts +++ b/packages/headers-parser/tests/netlify-config-parser.test.ts @@ -14,8 +14,13 @@ test.each([ ['values_undefined', { netlifyConfigPath: 'values_undefined' }, []], ['value_array', { netlifyConfigPath: 'value_array' }, [{ for: '/path', values: { test: 'one, two' } }]], ['for_path_no_slash', { netlifyConfigPath: 'for_path_no_slash' }, [{ for: 'path', values: { test: 'one' } }]], -])(`Parses netlify.toml headers | %s`, async (_, input, output) => { - const { headers } = await parseHeaders(input) +])(`Parses netlify.toml headers | %s`, async (_, { netlifyConfigPath }, output) => { + const { headers } = await parseHeaders({ + headersFiles: undefined, + netlifyConfigPath, + configHeaders: undefined, + minimal: true, + }) expect(headers).toStrictEqual(output) }) @@ -29,8 +34,13 @@ test.each([ ['invalid_value_name', { netlifyConfigPath: 'invalid_value_name' }, /Empty header name/], ['invalid_value_string', { netlifyConfigPath: 'invalid_value_string' }, /must be a string/], ['invalid_value_array', { netlifyConfigPath: 'invalid_value_array' }, /must be a string/], -])(`Validate syntax errors | %s`, async (_, input, errorMessage) => { - const { errors } = await parseHeaders(input) +])(`Validate syntax errors | %s`, async (_, { netlifyConfigPath }, errorMessage) => { + const { errors } = await parseHeaders({ + headersFiles: undefined, + netlifyConfigPath, + configHeaders: undefined, + minimal: true, + }) expect(errors).not.toHaveLength(0) expect(errors.some((error) => errorMessage.test(error.message))).toBeTruthy() }) From d09d2a45a5f86b220f8d085603201e684cbdd47c Mon Sep 17 00:00:00 2001 From: Philippe Serhal Date: Fri, 28 Feb 2025 11:36:07 -0500 Subject: [PATCH 10/11] fix(types): add missing PollingStrategy.name type --- packages/build-info/src/frameworks/framework.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/build-info/src/frameworks/framework.ts b/packages/build-info/src/frameworks/framework.ts index cb4a9f7ce4..d493a6ff79 100644 --- a/packages/build-info/src/frameworks/framework.ts +++ b/packages/build-info/src/frameworks/framework.ts @@ -20,7 +20,8 @@ export enum Accuracy { } export type PollingStrategy = { - name + // TODO(serhalp) Define an enum + name: string } /** Information on how it was detected and how accurate the detection is */ From 1d2bf010a54ecf4e607c7a4f9b7226a0c43dcd07 Mon Sep 17 00:00:00 2001 From: Philippe Serhal Date: Fri, 28 Feb 2025 11:36:15 -0500 Subject: [PATCH 11/11] feat(types): export Category, PollingStrategy --- packages/build-info/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/build-info/src/index.ts b/packages/build-info/src/index.ts index d15f74cb3b..d5519cbfe3 100644 --- a/packages/build-info/src/index.ts +++ b/packages/build-info/src/index.ts @@ -1,6 +1,6 @@ export * from './file-system.js' export * from './logger.js' -export { DetectedFramework, FrameworkInfo } from './frameworks/framework.js' +export type { Category, DetectedFramework, FrameworkInfo, PollingStrategy } from './frameworks/framework.js' export * from './get-framework.js' export * from './project.js' export * from './settings/get-build-settings.js'