diff --git a/package-lock.json b/package-lock.json index bc0a7a7b50..e15cd15fec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4818,13 +4818,11 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "android" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-android-arm64": { "version": "4.44.2", @@ -4833,13 +4831,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "android" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.44.2", @@ -4848,13 +4844,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-darwin-x64": { "version": "4.44.2", @@ -4863,13 +4857,11 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-freebsd-arm64": { "version": "4.44.2", @@ -4878,13 +4870,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-freebsd-x64": { "version": "4.44.2", @@ -4893,13 +4883,11 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { "version": "4.44.2", @@ -4908,13 +4896,11 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { "version": "4.44.2", @@ -4923,13 +4909,11 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { "version": "4.44.2", @@ -4938,13 +4922,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { "version": "4.44.2", @@ -4953,13 +4935,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { "version": "4.44.2", @@ -4968,13 +4948,11 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { "version": "4.44.2", @@ -4983,13 +4961,11 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { "version": "4.44.2", @@ -4998,13 +4974,11 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { "version": "4.44.2", @@ -5013,13 +4987,11 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { "version": "4.44.2", @@ -5028,13 +5000,11 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.44.2", @@ -5043,13 +5013,11 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-x64-musl": { "version": "4.44.2", @@ -5058,13 +5026,11 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { "version": "4.44.2", @@ -5073,13 +5039,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "win32" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { "version": "4.44.2", @@ -5088,13 +5052,11 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "win32" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.44.2", @@ -5103,13 +5065,11 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "win32" - ], - "peer": true + ] }, "node_modules/@rtsao/scc": { "version": "1.1.0", @@ -10871,7 +10831,6 @@ "os": [ "aix" ], - "peer": true, "engines": { "node": ">=18" } @@ -10889,7 +10848,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -10907,7 +10865,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -10925,7 +10882,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -10943,7 +10899,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -10961,7 +10916,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -10979,7 +10933,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -10997,7 +10950,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -11015,7 +10967,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -11033,7 +10984,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -11051,7 +11001,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -11069,7 +11018,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -11087,7 +11035,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -11105,7 +11052,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -11123,7 +11069,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -11141,7 +11086,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -11159,7 +11103,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -11177,7 +11120,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -11195,7 +11137,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -11213,7 +11154,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -11231,7 +11171,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -11249,7 +11188,6 @@ "os": [ "openharmony" ], - "peer": true, "engines": { "node": ">=18" } @@ -11267,7 +11205,6 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=18" } @@ -11285,7 +11222,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -11303,7 +11239,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -11321,7 +11256,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -12996,7 +12930,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, diff --git a/packages/config/src/error.ts b/packages/config/src/error.ts index 343dc84a95..b4514f3ac4 100644 --- a/packages/config/src/error.ts +++ b/packages/config/src/error.ts @@ -1,13 +1,25 @@ // We distinguish between errors thrown intentionally and uncaught exceptions // (such as bugs) with a `customErrorInfo.type` property. -export const throwUserError = function (messageOrError: string | Error, error?: Error) { - const errorA = getError(messageOrError, error) + +const CUSTOM_ERROR_KEY = 'customErrorInfo' +const USER_ERROR_TYPE = 'resolveConfig' + +interface CustomErrorInfo { + type: string +} + +interface CustomError extends Error { + [CUSTOM_ERROR_KEY]?: CustomErrorInfo +} + +export const throwUserError = function (messageOrError: string | Error, error?: Error): never { + const errorA: CustomError = getError(messageOrError, error) errorA[CUSTOM_ERROR_KEY] = { type: USER_ERROR_TYPE } throw errorA } // Can pass either `message`, `error` or `message, error` -const getError = function (messageOrError: string | Error, error?: Error) { +const getError = function (messageOrError: string | Error, error?: Error): Error { if (messageOrError instanceof Error) { return messageOrError } @@ -20,7 +32,7 @@ const getError = function (messageOrError: string | Error, error?: Error) { return error } -export const isUserError = function (error) { +export const isUserError = function (error: any): error is CustomError { return ( canHaveErrorInfo(error) && error[CUSTOM_ERROR_KEY] !== undefined && error[CUSTOM_ERROR_KEY].type === USER_ERROR_TYPE ) @@ -28,9 +40,6 @@ export const isUserError = function (error) { // Exceptions that are not objects (including `Error` instances) cannot have an // `CUSTOM_ERROR_KEY` property -const canHaveErrorInfo = function (error) { - return error != null +const canHaveErrorInfo = function (error: any): error is object { + return error != null && typeof error === 'object' } - -const CUSTOM_ERROR_KEY = 'customErrorInfo' -const USER_ERROR_TYPE = 'resolveConfig' diff --git a/packages/config/src/origin.js b/packages/config/src/origin.js deleted file mode 100644 index 2ae2391061..0000000000 --- a/packages/config/src/origin.js +++ /dev/null @@ -1,38 +0,0 @@ -import { isTruthy } from './utils/remove_falsy.js' - -// `build.commandOrigin`, `build.publishOrigin` and `plugins[*].origin` constants -export const UI_ORIGIN = 'ui' -export const CONFIG_ORIGIN = 'config' -export const DEFAULT_ORIGIN = 'default' -export const INLINE_ORIGIN = 'inline' - -// Add `build.commandOrigin`, `build.publishOrigin` and `plugins[*].origin`. -// This shows whether those properties came from the `ui` or from the `config`. -export const addOrigins = function (config, origin) { - const configA = addBuildCommandOrigin({ config, origin }) - const configB = addBuildPublishOrigin({ config: configA, origin }) - const configC = addConfigPluginOrigin({ config: configB, origin }) - const configD = addHeadersOrigin({ config: configC, origin }) - const configE = addRedirectsOrigin({ config: configD, origin }) - return configE -} - -const addBuildCommandOrigin = function ({ config, config: { build = {} }, origin }) { - return isTruthy(build.command) ? { ...config, build: { ...build, commandOrigin: origin } } : config -} - -const addBuildPublishOrigin = function ({ config, config: { build = {} }, origin }) { - return isTruthy(build.publish) ? { ...config, build: { ...build, publishOrigin: origin } } : config -} - -const addConfigPluginOrigin = function ({ config, config: { plugins }, origin }) { - return Array.isArray(plugins) ? { ...config, plugins: plugins.map((plugin) => ({ origin, ...plugin })) } : config -} - -const addHeadersOrigin = function ({ config, config: { headers }, origin }) { - return isTruthy(headers) ? { ...config, headersOrigin: origin } : config -} - -const addRedirectsOrigin = function ({ config, config: { redirects }, origin }) { - return isTruthy(redirects) ? { ...config, redirectsOrigin: origin } : config -} diff --git a/packages/config/src/origin.ts b/packages/config/src/origin.ts new file mode 100644 index 0000000000..5b2b3b9842 --- /dev/null +++ b/packages/config/src/origin.ts @@ -0,0 +1,90 @@ +import { isTruthy } from './utils/remove_falsy.js' + +export type Origin = 'ui' | 'config' | 'default' | 'inline' + +interface Plugin { + package: string + origin?: Origin + [key: string]: any +} + +interface Config { + build?: { + command?: string + commandOrigin?: Origin + publish?: string + publishOrigin?: Origin + [key: string]: any + } + plugins?: Plugin[] + headers?: any + headersOrigin?: Origin + redirects?: any + redirectsOrigin?: Origin + [key: string]: any +} + +// `build.commandOrigin`, `build.publishOrigin` and `plugins[*].origin` constants +export const UI_ORIGIN: Origin = 'ui' +export const CONFIG_ORIGIN: Origin = 'config' +export const DEFAULT_ORIGIN: Origin = 'default' +export const INLINE_ORIGIN: Origin = 'inline' + +// Add `build.commandOrigin`, `build.publishOrigin` and `plugins[*].origin`. +// This shows whether those properties came from the `ui` or from the `config`. +export const addOrigins = function (config: Config, origin: Origin): Config { + const configA = addBuildCommandOrigin({ config, origin }) + const configB = addBuildPublishOrigin({ config: configA, origin }) + const configC = addConfigPluginOrigin({ config: configB, origin }) + const configD = addHeadersOrigin({ config: configC, origin }) + const configE = addRedirectsOrigin({ config: configD, origin }) + return configE +} + +const addBuildCommandOrigin = function ({ + config, + config: { build = {} }, + origin, +}: { + config: Config + origin: Origin +}): Config { + return isTruthy(build.command) ? { ...config, build: { ...build, commandOrigin: origin } } : config +} + +const addBuildPublishOrigin = function ({ + config, + config: { build = {} }, + origin, +}: { + config: Config + origin: Origin +}): Config { + return isTruthy(build.publish) ? { ...config, build: { ...build, publishOrigin: origin } } : config +} + +const addConfigPluginOrigin = function ({ + config, + config: { plugins }, + origin, +}: { + config: Config + origin: Origin +}): Config { + return Array.isArray(plugins) ? { ...config, plugins: plugins.map((plugin) => ({ origin, ...plugin })) } : config +} + +const addHeadersOrigin = function ({ config, config: { headers }, origin }: { config: Config; origin: Origin }): Config { + return isTruthy(headers) ? { ...config, headersOrigin: origin } : config +} + +const addRedirectsOrigin = function ({ + config, + config: { redirects }, + origin, +}: { + config: Config + origin: Origin +}): Config { + return isTruthy(redirects) ? { ...config, redirectsOrigin: origin } : config +} diff --git a/packages/config/src/parse.ts b/packages/config/src/parse.ts index 7d85cfb7db..50181a5a5e 100644 --- a/packages/config/src/parse.ts +++ b/packages/config/src/parse.ts @@ -25,7 +25,7 @@ export const parseConfig = async function (configPath?: string) { * Same but `configPath` is required and `configPath` might point to a * non-existing file. */ -export const parseOptionalConfig = async function (configPath) { +export const parseOptionalConfig = async function (configPath: string) { if (!existsSync(configPath)) { return {} } @@ -33,7 +33,7 @@ export const parseOptionalConfig = async function (configPath) { return await readConfigPath(configPath) } -const readConfigPath = async function (configPath) { +const readConfigPath = async function (configPath: string) { const configString = await readConfig(configPath) validateTomlBlackslashes(configString) @@ -41,22 +41,22 @@ const readConfigPath = async function (configPath) { try { return parseToml(configString) } catch (error) { - throwUserError('Could not parse configuration file', error) + throwUserError('Could not parse configuration file', error as Error) } } /** * Reach the configuration file's raw content */ -const readConfig = async function (configPath) { +const readConfig = async function (configPath: string): Promise { try { return await fs.readFile(configPath, 'utf8') } catch (error) { - throwUserError('Could not read configuration file', error) + return throwUserError('Could not read configuration file', error as Error) } } -const validateTomlBlackslashes = function (configString) { +const validateTomlBlackslashes = function (configString: string) { const result = INVALID_TOML_BLACKSLASH.exec(configString) if (result === null) { return diff --git a/packages/config/src/utils/group.js b/packages/config/src/utils/group.js deleted file mode 100644 index 1a5dccd38d..0000000000 --- a/packages/config/src/utils/group.js +++ /dev/null @@ -1,10 +0,0 @@ -// Group objects according to a key attribute. -// The key must exist in each object and be a string. -export const groupBy = function (objects, keyName) { - const keys = [...new Set(objects.map((object) => object[keyName]))] - return keys.map((key) => groupObjects(objects, keyName, key)) -} - -const groupObjects = function (objects, keyName, key) { - return objects.filter((object) => object[keyName] === key) -} diff --git a/packages/config/src/utils/group.ts b/packages/config/src/utils/group.ts new file mode 100644 index 0000000000..7a4d4eb7d9 --- /dev/null +++ b/packages/config/src/utils/group.ts @@ -0,0 +1,12 @@ +/** + * Group objects according to a key attribute. + * The key must exist in each object and be a string. + */ +export const groupBy = function (objects: T[], keyName: K): T[][] { + const keys = [...new Set(objects.map((object) => object[keyName]))] + return keys.map((key) => groupObjects(objects, keyName, key)) +} + +const groupObjects = function (objects: T[], keyName: K, key: T[K]): T[] { + return objects.filter((object) => object[keyName] === key) +} diff --git a/packages/config/src/utils/remove_falsy.ts b/packages/config/src/utils/remove_falsy.ts index 0b2ddb1b28..3be9d58177 100644 --- a/packages/config/src/utils/remove_falsy.ts +++ b/packages/config/src/utils/remove_falsy.ts @@ -3,8 +3,8 @@ import { includeKeys } from 'filter-obj' /** * Remove falsy values from object */ -export const removeFalsy = function (obj) { - return includeKeys(obj, (_key, value) => isTruthy(value)) +export const removeFalsy = (obj: T): Partial => { + return includeKeys(obj, (_key, value) => isTruthy(value)) as Partial } type NoUndefinedField = { [P in keyof T]: Exclude } diff --git a/packages/config/src/utils/set.js b/packages/config/src/utils/set.js deleted file mode 100644 index 4392a573ae..0000000000 --- a/packages/config/src/utils/set.js +++ /dev/null @@ -1,32 +0,0 @@ -import isPlainObj from 'is-plain-obj' - -// Set a property deeply using an array of `keys` which can be either strings -// (object properties) or integers (array indices). -// Adds default values when intermediary properties are undefined or have the -// wrong type. Also extends arrays when they are too small for a given index. -// Does not mutate. -export const setProp = function (parent, keys, value) { - if (keys.length === 0) { - return value - } - - if (Number.isInteger(keys[0])) { - return setArrayProp(parent, keys, value) - } - - return setObjectProp(parent, keys, value) -} - -const setArrayProp = function (parent, [index, ...keys], value) { - const arrayParent = Array.isArray(parent) ? parent : [] - const missingItems = index - arrayParent.length + 1 - const normalizedParent = missingItems > 0 ? [...arrayParent, ...new Array(missingItems)] : arrayParent - const newValue = setProp(normalizedParent[index], keys, value) - return [...normalizedParent.slice(0, index), newValue, ...normalizedParent.slice(index + 1)] -} - -const setObjectProp = function (parent, [key, ...keys], value) { - const objectParent = isPlainObj(parent) ? parent : {} - const newValue = setProp(objectParent[key], keys, value) - return { ...objectParent, [key]: newValue } -} diff --git a/packages/config/src/utils/set.ts b/packages/config/src/utils/set.ts new file mode 100644 index 0000000000..3b820d6125 --- /dev/null +++ b/packages/config/src/utils/set.ts @@ -0,0 +1,38 @@ +import isPlainObj from 'is-plain-obj' + +type Key = string | number + +/** + * Set a property deeply using an array of `keys` which can be either strings + * (object properties) or integers (array indices). + * Adds default values when intermediary properties are undefined or have the + * wrong type. Also extends arrays when they are too small for a given index. + * Does not mutate. + */ +export const setProp = function (parent: unknown, keys: Key[], value: unknown): any { + if (keys.length === 0) { + return value + } + + const [firstKey, ...restKeys] = keys + + if (typeof firstKey === 'number') { + return setArrayProp(parent, firstKey, restKeys, value) + } + + return setObjectProp(parent, firstKey as string, restKeys, value) +} + +const setArrayProp = function (parent: unknown, index: number, keys: Key[], value: unknown): any[] { + const arrayParent = Array.isArray(parent) ? parent : [] + const missingItems = index - arrayParent.length + 1 + const normalizedParent = missingItems > 0 ? [...arrayParent, ...new Array(missingItems)] : arrayParent + const newValue = setProp(normalizedParent[index], keys, value) + return [...normalizedParent.slice(0, index), newValue, ...normalizedParent.slice(index + 1)] +} + +const setObjectProp = function (parent: unknown, key: string, keys: Key[], value: unknown): object { + const objectParent = isPlainObj(parent) ? (parent as Record) : {} + const newValue = setProp(objectParent[key], keys, value) + return { ...objectParent, [key]: newValue } +} diff --git a/packages/config/src/utils/toml.js b/packages/config/src/utils/toml.ts similarity index 58% rename from packages/config/src/utils/toml.js rename to packages/config/src/utils/toml.ts index 96b9ca2ba0..0b9ff3bd6b 100644 --- a/packages/config/src/utils/toml.js +++ b/packages/config/src/utils/toml.ts @@ -1,8 +1,10 @@ import { parse as loadToml } from '@iarna/toml' import tomlify from 'tomlify-j0.4' -// Parse from TOML to JavaScript -export const parseToml = function (configString) { +/** + * Parse from TOML to JavaScript + */ +export const parseToml = function (configString: string): any { const config = loadToml(configString) // `toml.parse()` returns an object with `null` prototype deeply, which can // sometimes create problems with some utilities. We convert it. @@ -11,13 +13,17 @@ export const parseToml = function (configString) { return JSON.parse(JSON.stringify(config)) } -// Serialize JavaScript object to TOML -export const serializeToml = function (object) { +/** + * Serialize JavaScript object to TOML + */ +export const serializeToml = function (object: any): string { return tomlify.toToml(object, { space: 2, replace: replaceTomlValue }) } -// `tomlify-j0.4` serializes integers as floats, e.g. `200.0`. -// This is a problem with `redirects[*].status`. -const replaceTomlValue = function (key, value) { +/** + * `tomlify-j0.4` serializes integers as floats, e.g. `200.0`. + * This is a problem with `redirects[*].status`. + */ +const replaceTomlValue = function (key: string, value: any): string | boolean { return Number.isInteger(value) ? String(value) : false }