diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d063616..7653e4b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ concurrency: env: COMMON_INSTALL_COMMAND: pnpm install --frozen-lockfile --prefer-offline - COMMON_NODE_VERSION: 18.15.0 + COMMON_NODE_VERSION: 20.9.0 jobs: ################################################################################################## diff --git a/packages/eslint-config-nerve/package.json b/packages/eslint-config-nerve/package.json index f500cfc..d498267 100644 --- a/packages/eslint-config-nerve/package.json +++ b/packages/eslint-config-nerve/package.json @@ -21,8 +21,6 @@ "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-html": "^7.1.0", "eslint-plugin-import": "2.27.5", - "eslint-plugin-jest": "^27.2.2", - "eslint-plugin-jest-dom": "^5.0.1", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-react": "^7.32.2", diff --git a/packages/kit/package.json b/packages/kit/package.json index 5822047..91b13cd 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -10,6 +10,11 @@ "fix:lint": "eslint . --fix", "test": "echo \"Bypassing: no test specified\" && exit 0" }, + "dependencies": { + "date-fns": "^3.3.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, "devDependencies": { "@nerve/tsconfig": "workspace:*", "@types/node": "20.4.2", @@ -24,10 +29,18 @@ "src" ], "exports": { + "./env": { + "import": "./src/env/index.ts", + "require": "./dist/env.js" + }, "./node": { "import": "./src/node/index.ts", "require": "./dist/node.js" }, + "./react": { + "import": "./src/react/index.ts", + "require": "./dist/react.js" + }, "./utils": { "import": "./src/utils/index.ts", "require": "./dist/utils.js" @@ -35,18 +48,22 @@ }, "typesVersions": { "*": { + "env": [ + "src/env/index.ts", + "dist/env.d.ts" + ], "node": [ "src/node/index.ts", "dist/node.d.ts" ], + "react": [ + "src/react/index.ts", + "dist/react.d.ts" + ], "utils": [ "src/utils/index.ts", "dist/utils.d.ts" ] } - }, - "dependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0" } } diff --git a/packages/kit/src/env/env-vars/index.ts b/packages/kit/src/env/env-vars/index.ts new file mode 100644 index 0000000..8dbc734 --- /dev/null +++ b/packages/kit/src/env/env-vars/index.ts @@ -0,0 +1,66 @@ +/** + * Resolves an environment variable with an optional default value. + * + * @param value The value of the environment variable. + * @param defaultValue The default value to use if the environment variable is falsy. + */ +export function resolveEnvVar(value: string | undefined, defaultValue: string): string; +export function resolveEnvVar(value?: string, defaultValue?: string): string | undefined; +export function resolveEnvVar(value?: string, defaultValue?: string): string | undefined { + return value || defaultValue; +} + +/** + * Resolves an environment variable that is required. Throws an error if the value is falsy. + * + * * We don't allow for a default value here because we want to ensure that the environment variable is set. + * * If we want to allow a default value, we should use `resolveEnvVar` instead, as the env var isn't truly required. + * + * @param name The name of the required environment variable (only used in the error message) + * @param value The value of the required environment variable. + */ +export const resolveRequiredEnvVar = (name: string, value?: string): string => { + if (!value) { + // Allow missing env vars in CI since otherwise the BundleMon audit would fail + if (process.env.CI === 'true') { + return `missing-${name}-in-ci`; + } + throw new Error(`Missing required environment variable: '${name}'.`); + } + return value; +}; + +/** + * Converts a given string to a boolean based on commonly recognized boolean string representations. + * * If the value is falsy, we will return false. + * + * @param value The value to convert to a boolean. + */ +export const boolEnv = (value?: string): boolean => { + if (!value) { + return false; + } + return ['true', 'yes', 'on', '1'].includes(value.toLowerCase()); +}; + +/** + * Converts a given string to a number. + * * If the value is falsy, we will return 0. + * + * @param value The value to convert to a number. + */ +export const numberEnv = (value?: string): number => { + return Number(value) || 0; +}; + +/** + * Converts a comma-separated string to a list. + * + * @param value The value to convert to a string array. + */ +export const listEnv = (value: string | undefined): string[] => { + if (!value) { + return []; + } + return value.split(',').map((s) => s.trim()); +}; diff --git a/packages/kit/src/env/index.ts b/packages/kit/src/env/index.ts new file mode 100644 index 0000000..966dc32 --- /dev/null +++ b/packages/kit/src/env/index.ts @@ -0,0 +1,2 @@ +export * from './server'; +export * from './env-vars'; diff --git a/packages/kit/src/env/server/index.ts b/packages/kit/src/env/server/index.ts new file mode 100644 index 0000000..bd344a9 --- /dev/null +++ b/packages/kit/src/env/server/index.ts @@ -0,0 +1,6 @@ +/** + * A simple utility function to check if we are on the server + */ +export const isServer = () => { + return typeof window === 'undefined'; +}; diff --git a/packages/kit/src/react/useDebouncedCallback/useDebouncedCallback.tsx b/packages/kit/src/react/useDebouncedCallback/useDebouncedCallback.tsx new file mode 100644 index 0000000..cbcd05f --- /dev/null +++ b/packages/kit/src/react/useDebouncedCallback/useDebouncedCallback.tsx @@ -0,0 +1,62 @@ +'use client'; + +import { useCallback, useEffect, useRef } from 'react'; + +/** + * Debounce a callback function, preventing it from being called until a certain amount of time has passed since the last invocation. + * This is useful for preventing a function from being called too frequently. + * + * Note: This hook is not meant to be used as a one-time delay. If you need a one-time delay, use `useTimeout` instead. + * + * Example Scenario: Prevent an API request from firing every time an input changes as a user types + * + * Usage Example: + * ```tsx + * const debouncedSearch = useDebouncedCallback(search, 300); + * + * debouncedSearch(e.target.value)} /> + * ``` + * + * @param callback - The callback to debounce + * @param delay - The amount of time to wait after the latest invocation before calling the function + * @returns A debounced version of the callback function that can be called safely + */ +export const useDebouncedCallback = ) => void>( + callback: Callback, + delay: number +): ((...args: Parameters) => void) => { + const callbackRef = useRef(callback); + const timeoutRef = useRef>(); + + useEffect(() => { + callbackRef.current = callback; + }, [callback]); + + /** + * Debounced version of original callback for the consumer to safely call as many times as they please! + * Note: Props will match and be passed through the original callback. + */ + const debouncedCallback = useCallback( + (...args: Parameters) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + timeoutRef.current = setTimeout(() => { + callbackRef.current(...args); + }, delay); + }, + [delay] + ); + + // Clear timeout on unmount + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + }, []); + + return debouncedCallback; +}; diff --git a/packages/kit/src/react/useInterval/useInterval.tsx b/packages/kit/src/react/useInterval/useInterval.tsx new file mode 100644 index 0000000..c1d0268 --- /dev/null +++ b/packages/kit/src/react/useInterval/useInterval.tsx @@ -0,0 +1,29 @@ +import { useEffect, useRef } from 'react'; + +/** + * useInterval repeatedly calls the callback with a period of delay (aka: throttle) + * + * @param callback + * @param delay + */ +export const useInterval = (callback: () => void, delay: number | null) => { + const savedCallback = useRef(callback); + const intervalRef = useRef>(); + + // Remember the latest callback. + useEffect(() => { + savedCallback.current = callback; + }, [callback]); + + // Set up the interval. + useEffect(() => { + function tick() { + savedCallback.current(); + } + + if (delay !== null) { + intervalRef.current = setInterval(tick, delay); + return clearInterval(intervalRef.current); + } + }, [delay]); +}; diff --git a/packages/kit/src/react/useSingletonEffect/useSingletonEffect.tsx b/packages/kit/src/react/useSingletonEffect/useSingletonEffect.tsx new file mode 100644 index 0000000..fbcfece --- /dev/null +++ b/packages/kit/src/react/useSingletonEffect/useSingletonEffect.tsx @@ -0,0 +1,40 @@ +import { useEffect, useRef } from 'react'; + +import { isServer } from '../../env/server'; + +/** + * A hook that is guaranteed to execute a callback only once per render (dependent on provided condition). + * ! The callback will not be executed on the server. + * + * @param callback - The callback to execute + * @param condition - The boolean or function condition under which to execute the callback + */ +export const useSingletonEffect = (callback: Callback, condition: Condition = true) => { + const isCalledOnce = useRef(false); + + useEffect(() => { + if (isServer() || isCalledOnce.current) { + return; + } + + if (shouldExecuteCallback(condition)) { + callback(); + + isCalledOnce.current = true; + } + }, [callback, condition]); +}; + +/** + * Determine if conditions are met to execute a callback + */ +const shouldExecuteCallback = (condition: Condition) => { + if (typeof condition === 'boolean') { + return condition; + } + + return condition(); +}; + +type Callback = () => void; +type Condition = boolean | (() => boolean); diff --git a/packages/kit/src/react/useTimeout/useTimeout.tsx b/packages/kit/src/react/useTimeout/useTimeout.tsx new file mode 100644 index 0000000..a49f3fc --- /dev/null +++ b/packages/kit/src/react/useTimeout/useTimeout.tsx @@ -0,0 +1,30 @@ +import { useEffect, useRef } from 'react'; + +/** + * Delay, and then call callback. + * + * Note: Not meant to be used as a debounced callback, but rather as a one-time delay. + * If you need a debounced callback, use `useDebouncedCallback` instead. + * + * @param callback + * @param delay delay in milliseconds + */ +export const useTimeout = (callback: () => void, delay?: number | null) => { + const callbackRef = useRef(callback); + const timeoutRef = useRef>(); + + useEffect(() => { + callbackRef.current = callback; + }, [callback]); + + useEffect(() => { + if (delay !== null) { + timeoutRef.current = setTimeout(() => callbackRef.current(), delay); + } + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + }, [delay]); +}; diff --git a/packages/kit/src/react/useUpdateEffect/useUpdateEffect.ts b/packages/kit/src/react/useUpdateEffect/useUpdateEffect.ts new file mode 100644 index 0000000..15a1dbe --- /dev/null +++ b/packages/kit/src/react/useUpdateEffect/useUpdateEffect.ts @@ -0,0 +1,30 @@ +import { type DependencyList, type EffectCallback, useEffect, useRef } from 'react'; + +import { isServer } from '../../env/server'; + +/** + * A hook that runs an callback only after the first render has ocurred. This is useful for when you want to effectively + * "skip" the first render, and only have an effect run on subsequent "update" renders. + * + * ! The callback will not be executed on the server. + * + * @param callback The effect to run after the first render + * @param deps The dependencies to watch for changes + */ +export const useUpdateEffect = (callback: EffectCallback, deps?: DependencyList) => { + const hasMounted = useRef(false); + + useEffect(() => { + if (isServer()) { + return; + } + + if (hasMounted.current) { + callback(); + } else { + hasMounted.current = true; + } + // ! The eslint rule override below is intentional. We don't want to include `hasMounted` in the dependency list + // eslint-disable-next-line react-hooks/exhaustive-deps + }, deps); +}; diff --git a/packages/kit/src/utils/arrays.ts b/packages/kit/src/utils/arrays.ts new file mode 100644 index 0000000..be3276f --- /dev/null +++ b/packages/kit/src/utils/arrays.ts @@ -0,0 +1,12 @@ +/** + * Removes all 'invalid' values from an array (undefined, null, NaN) + * + * @param array The array to remove all 'invalid' values from. + * @returns A new array with all 'invalid' values removed. + */ +export const removeInvalidValues = (array: (T | undefined | null)[]): T[] => { + const isT = (t: T | undefined | null): t is T => { + return t !== undefined && t !== null && !Number.isNaN(t); + }; + return array.filter(isT); +}; diff --git a/packages/kit/src/utils/console.ts b/packages/kit/src/utils/console.ts new file mode 100644 index 0000000..33bd261 --- /dev/null +++ b/packages/kit/src/utils/console.ts @@ -0,0 +1,17 @@ +/** + * A wrapper around console.debug that only logs if the env log level is set to DEBUG. + * + * This is a useful utility to use when we want to do a debug log but we are outside of the scope of any kind + * of internal logger service. In such cases, this util prevents unintended logging. + * + * Note: This is not a replacement for a true logger service. This is only meant to be used in cases where we are + * outside of the scope of the logger service. ALWAYS use a logger service when possible. + */ +export const consoleDebug = (message: unknown, ...args: unknown[]) => { + const logLevel = process.env.NEXT_PUBLIC_LOG_LEVEL || process.env.LOG_LEVEL; + + if (logLevel !== 'DEBUG') return; + + // eslint-disable-next-line no-console + console.debug(message, ...args); +}; diff --git a/packages/kit/src/utils/dates.ts b/packages/kit/src/utils/dates.ts new file mode 100644 index 0000000..f63d656 --- /dev/null +++ b/packages/kit/src/utils/dates.ts @@ -0,0 +1,20 @@ +import { addHours, isWithinInterval } from 'date-fns'; + +/** + * A date-fns formatter string that formats dates in a compatible format for GQL Date Scalars + * + * @see https://the-guild.dev/graphql/scalars/docs/scalars/date + * @see https://date-fns.org/v2.16.1/docs/format + */ +export const GQL_DATE_STRING_FORMAT = 'yyyy-MM-dd'; + +export const isWithin24Hours = (date: Date) => { + return isWithinInterval(new Date(date), { + start: new Date(), + end: addHours(new Date(), 24), + }); +}; + +export const isBeforeNow = (date: Date) => { + return new Date(date) <= new Date(); +}; diff --git a/packages/kit/src/utils/errors.ts b/packages/kit/src/utils/errors.ts new file mode 100644 index 0000000..ecc3bd2 --- /dev/null +++ b/packages/kit/src/utils/errors.ts @@ -0,0 +1,40 @@ +/** + * A simple utility method for checking if an error is an instance of the Error class. + * + * @param error - The error to check + * @returns {boolean} - True if the error is an instance of the Error class + */ +export const isErrorInstance = (error: unknown): error is Error => { + return error instanceof Error; +}; + +/** + * A simple utility method for checking if an error has the shape of an Error. + * + * * This is helpful in cases where the error was thrown in a different context (window/frame/iframe) than where + * * it is being handled. In those cases, the error will not be an instance of the Error class, but it will have the + * * same shape. + * + * ! The `cause` property is optional so we aren't going to use it to validate error shape. + * + * @param error - The error to check + * @returns {boolean} - True if the error has the shape of an Error + */ +export const hasErrorShape = ( + error: unknown +): error is { message: string; name: string; stack: string; cause?: unknown } => { + return ( + typeof error === 'object' && + error !== null && + isValidErrorProperty(error, 'message') && + isValidErrorProperty(error, 'name') && + isValidErrorProperty(error, 'stack') + ); +}; + +/** + * Checks for the existence of property on an error object and confirms that it is a string. + */ +const isValidErrorProperty = (error: object, property: string): boolean => { + return property in error && typeof error[property as keyof typeof error] === 'string'; +}; diff --git a/packages/kit/src/utils/index.ts b/packages/kit/src/utils/index.ts index 937b73f..a591078 100644 --- a/packages/kit/src/utils/index.ts +++ b/packages/kit/src/utils/index.ts @@ -1 +1,6 @@ -export * from './value'; +export * from './arrays'; +export * from './console'; +export * from './dates'; +export * from './errors'; +export * from './strings'; +export * from './urls'; diff --git a/packages/kit/src/utils/strings.ts b/packages/kit/src/utils/strings.ts new file mode 100644 index 0000000..1d18c86 --- /dev/null +++ b/packages/kit/src/utils/strings.ts @@ -0,0 +1,29 @@ +const truthyValues = new Set(['true', 'yes', 'on', '1']); + +export const strToBool = (value: string | null | undefined) => { + if (!value) { + return false; + } + return truthyValues.has(value.toLowerCase()); +}; + +/** + * Checks if the provided string is a number + * + * @param value The value to check + * @returns true if the value can be converted into a number + */ +export const isStrNumber = (value: string | null | undefined) => { + if (!value) { + return false; + } + return !Number.isNaN(Number(value)); +}; + +export const capitalizeFirstChar = (value: string) => { + try { + return value.charAt(0).toUpperCase() + value.slice(1); + } catch { + return value; + } +}; diff --git a/packages/kit/src/utils/urls.ts b/packages/kit/src/utils/urls.ts new file mode 100644 index 0000000..eb74705 --- /dev/null +++ b/packages/kit/src/utils/urls.ts @@ -0,0 +1,17 @@ +/** + * @see https://regex101.com/r/YTa65k/2 + */ +// eslint-disable-next-line prefer-regex-literals +const urlPathWithQueryStringsRegex = new RegExp('^/(?!.*//)([\\d\\w/?=%&-]+)$'); + +/** + * Checks if a given string is a valid URL path (query strings are allowed) + * TODO: This is fine for the immediate use case, but we should expand the regex to make sure query strings are valid and formatted correctly. + */ +export const isValidUrlPath = (path?: string) => { + if (!path) { + return false; + } + + return urlPathWithQueryStringsRegex.test(path) || path === '/'; +}; diff --git a/packages/kit/tsup.config.ts b/packages/kit/tsup.config.ts index 44fd4db..b52df76 100644 --- a/packages/kit/tsup.config.ts +++ b/packages/kit/tsup.config.ts @@ -2,13 +2,12 @@ import { defineConfig } from 'tsup'; export default defineConfig({ entry: { - // env: 'src/env/index.ts', - // next: 'src/next/index.ts', + env: 'src/env/index.ts', node: 'src/node/index.ts', - // react: 'src/react/index.ts', + react: 'src/react/index.ts', utils: 'src/utils/index.ts', }, - external: ['react', 'react-dom'], + external: ['vitest', 'react', 'react-dom'], format: ['cjs'], splitting: false, sourcemap: true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fa9706f..287ca26 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -264,12 +264,6 @@ importers: eslint-plugin-import: specifier: 2.27.5 version: 2.27.5(@typescript-eslint/parser@6.0.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.44.0) - eslint-plugin-jest: - specifier: ^27.2.2 - version: 27.2.2(@typescript-eslint/eslint-plugin@6.0.0)(eslint@8.44.0)(typescript@5.1.6) - eslint-plugin-jest-dom: - specifier: ^5.0.1 - version: 5.0.1(@testing-library/dom@9.3.1)(eslint@8.44.0) eslint-plugin-jsx-a11y: specifier: ^6.7.1 version: 6.7.1(eslint@8.44.0) @@ -309,6 +303,9 @@ importers: packages/kit: dependencies: + date-fns: + specifier: ^3.3.1 + version: 3.3.1 react: specifier: ^18.2.0 version: 18.2.0 @@ -465,6 +462,7 @@ packages: dependencies: '@babel/highlight': 7.23.4 chalk: 2.4.2 + dev: false /@babel/compat-data@7.22.9: resolution: {integrity: sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==} @@ -755,6 +753,7 @@ packages: /@babel/helper-validator-identifier@7.22.20: resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} + dev: false /@babel/helper-validator-identifier@7.22.5: resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==} @@ -813,6 +812,7 @@ packages: '@babel/helper-validator-identifier': 7.22.20 chalk: 2.4.2 js-tokens: 4.0.0 + dev: false /@babel/parser@7.22.7: resolution: {integrity: sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==} @@ -3665,7 +3665,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.6 + '@babel/runtime': 7.23.9 '@types/react': 18.2.18 react: 18.2.0 dev: false @@ -5401,20 +5401,6 @@ packages: resolution: {integrity: sha512-jtkwqdP2rY2iCCDVAFuaNBH3fiEi29aTn2RhtIoky8DTTiCdc48plpHHreLwmv1PICJ4AJUUESaq3xa8fZH8+g==} dev: false - /@testing-library/dom@9.3.1: - resolution: {integrity: sha512-0DGPd9AR3+iDTjGoMpxIkAsUihHZ3Ai6CneU6bRRrffXMgzCdlNk43jTrD2/5LT6CBb3MWTP8v510JzYtahD2w==} - engines: {node: '>=14'} - dependencies: - '@babel/code-frame': 7.23.5 - '@babel/runtime': 7.23.9 - '@types/aria-query': 5.0.1 - aria-query: 5.1.3 - chalk: 4.1.2 - dom-accessibility-api: 0.5.16 - lz-string: 1.5.0 - pretty-format: 27.5.1 - dev: true - /@tootallnate/once@2.0.0: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} @@ -5425,10 +5411,6 @@ packages: engines: {node: '>=10.13.0'} dev: true - /@types/aria-query@5.0.1: - resolution: {integrity: sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==} - dev: true - /@types/babel__core@7.20.5: resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} dependencies: @@ -6222,11 +6204,6 @@ packages: dependencies: color-convert: 2.0.1 - /ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - dev: true - /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -6289,12 +6266,6 @@ packages: tslib: 2.6.1 dev: false - /aria-query@5.1.3: - resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} - dependencies: - deep-equal: 2.2.2 - dev: true - /aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} dependencies: @@ -6546,7 +6517,7 @@ packages: /broadcast-channel@3.7.0: resolution: {integrity: sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==} dependencies: - '@babel/runtime': 7.22.6 + '@babel/runtime': 7.23.9 detect-node: 2.1.0 js-sha3: 0.8.0 microseconds: 0.2.0 @@ -7286,7 +7257,11 @@ packages: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} dependencies: - '@babel/runtime': 7.22.6 + '@babel/runtime': 7.23.9 + dev: false + + /date-fns@3.3.1: + resolution: {integrity: sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==} dev: false /date-now@1.0.1: @@ -7367,29 +7342,6 @@ packages: mimic-response: 3.1.0 dev: false - /deep-equal@2.2.2: - resolution: {integrity: sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==} - dependencies: - array-buffer-byte-length: 1.0.0 - call-bind: 1.0.2 - es-get-iterator: 1.1.3 - get-intrinsic: 1.2.1 - is-arguments: 1.1.1 - is-array-buffer: 3.0.2 - is-date-object: 1.0.5 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 - isarray: 2.0.5 - object-is: 1.1.5 - object-keys: 1.1.1 - object.assign: 4.1.4 - regexp.prototype.flags: 1.5.0 - side-channel: 1.0.4 - which-boxed-primitive: 1.0.2 - which-collection: 1.0.1 - which-typed-array: 1.1.10 - dev: true - /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -7491,10 +7443,6 @@ packages: dependencies: esutils: 2.0.3 - /dom-accessibility-api@0.5.16: - resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} - dev: true - /dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} dependencies: @@ -7693,20 +7641,6 @@ packages: which-typed-array: 1.1.10 dev: true - /es-get-iterator@1.1.3: - resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - has-symbols: 1.0.3 - is-arguments: 1.1.1 - is-map: 2.0.2 - is-set: 2.0.2 - is-string: 1.0.7 - isarray: 2.0.5 - stop-iteration-iterator: 1.0.0 - dev: true - /es-module-lexer@1.4.1: resolution: {integrity: sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==} dev: false @@ -8106,40 +8040,6 @@ packages: - supports-color dev: true - /eslint-plugin-jest-dom@5.0.1(@testing-library/dom@9.3.1)(eslint@8.44.0): - resolution: {integrity: sha512-zD/BjNk12R5R9cxIu8oa2HfNeDSknI3ewtN8nygIUMQuieWDnTY9Np//6a1Z3G7Y3dx3l45hCUR4EphsgRmUtA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0, npm: '>=6', yarn: '>=1'} - peerDependencies: - '@testing-library/dom': ^8.0.0 || ^9.0.0 - eslint: ^6.8.0 || ^7.0.0 || ^8.0.0 - dependencies: - '@babel/runtime': 7.22.6 - '@testing-library/dom': 9.3.1 - eslint: 8.44.0 - requireindex: 1.2.0 - dev: true - - /eslint-plugin-jest@27.2.2(@typescript-eslint/eslint-plugin@6.0.0)(eslint@8.44.0)(typescript@5.1.6): - resolution: {integrity: sha512-euzbp06F934Z7UDl5ZUaRPLAc9MKjh0rMPERrHT7UhlCEwgb25kBj37TvMgWeHZVkR5I9CayswrpoaqZU1RImw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@typescript-eslint/eslint-plugin': ^5.0.0 - eslint: ^7.0.0 || ^8.0.0 - jest: '*' - peerDependenciesMeta: - '@typescript-eslint/eslint-plugin': - optional: true - jest: - optional: true - dependencies: - '@typescript-eslint/eslint-plugin': 6.0.0(@typescript-eslint/parser@6.0.0)(eslint@8.44.0)(typescript@5.1.6) - '@typescript-eslint/utils': 5.62.0(eslint@8.44.0)(typescript@5.1.6) - eslint: 8.44.0 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - /eslint-plugin-jsx-a11y@6.7.1(eslint@8.44.0): resolution: {integrity: sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==} engines: {node: '>=4.0'} @@ -9081,7 +8981,7 @@ packages: /history@5.3.0: resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==} dependencies: - '@babel/runtime': 7.22.6 + '@babel/runtime': 7.23.9 dev: false /hoist-non-react-statics@3.3.2: @@ -9293,14 +9193,6 @@ packages: is-decimal: 1.0.4 dev: false - /is-arguments@1.1.1: - resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - has-tostringtag: 1.0.0 - dev: true - /is-array-buffer@3.0.2: resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} dependencies: @@ -9417,10 +9309,6 @@ packages: engines: {node: '>=8'} dev: false - /is-map@2.0.2: - resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} - dev: true - /is-negative-zero@2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} engines: {node: '>= 0.4'} @@ -9473,10 +9361,6 @@ packages: engines: {node: '>=10'} dev: false - /is-set@2.0.2: - resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} - dev: true - /is-shared-array-buffer@1.0.2: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} dependencies: @@ -9531,23 +9415,12 @@ packages: engines: {node: '>=10'} dev: false - /is-weakmap@2.0.1: - resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} - dev: true - /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: call-bind: 1.0.2 dev: true - /is-weakset@2.0.2: - resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - dev: true - /is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -9562,10 +9435,6 @@ packages: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} dev: false - /isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - dev: true - /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -10030,11 +9899,6 @@ packages: dependencies: yallist: 4.0.0 - /lz-string@1.5.0: - resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} - hasBin: true - dev: true - /magic-string@0.27.0: resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} engines: {node: '>=12'} @@ -10070,7 +9934,7 @@ packages: /match-sorter@6.3.1: resolution: {integrity: sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==} dependencies: - '@babel/runtime': 7.22.6 + '@babel/runtime': 7.23.9 remove-accents: 0.4.2 dev: false @@ -10573,14 +10437,6 @@ packages: /object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} - /object-is@1.1.5: - resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - dev: true - /object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -10952,7 +10808,7 @@ packages: resolution: {integrity: sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==} engines: {node: '>=10'} dependencies: - '@babel/runtime': 7.22.6 + '@babel/runtime': 7.23.9 dev: false /postcss-calc@9.0.1(postcss@8.4.25): @@ -11461,15 +11317,6 @@ packages: hasBin: true dev: true - /pretty-format@27.5.1: - resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - dependencies: - ansi-regex: 5.0.1 - ansi-styles: 5.2.0 - react-is: 17.0.2 - dev: true - /pretty-ms@7.0.1: resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==} engines: {node: '>=10'} @@ -11598,7 +11445,7 @@ packages: peerDependencies: react: ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 dependencies: - '@babel/runtime': 7.22.6 + '@babel/runtime': 7.23.9 react: 18.2.0 dev: false @@ -11706,7 +11553,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.6 + '@babel/runtime': 7.23.9 '@types/react': 18.2.18 focus-lock: 0.11.6 prop-types: 15.8.1 @@ -11748,10 +11595,6 @@ packages: /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - /react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - dev: true - /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: false @@ -11980,7 +11823,7 @@ packages: /regenerator-transform@0.15.1: resolution: {integrity: sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==} dependencies: - '@babel/runtime': 7.22.6 + '@babel/runtime': 7.23.9 /regexp.prototype.flags@1.5.0: resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} @@ -12022,11 +11865,6 @@ packages: engines: {node: '>=0.10.0'} dev: false - /requireindex@1.2.0: - resolution: {integrity: sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==} - engines: {node: '>=0.10.5'} - dev: true - /requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} dev: false @@ -12801,13 +12639,6 @@ packages: type-fest: 0.7.1 dev: false - /stop-iteration-iterator@1.0.0: - resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} - engines: {node: '>= 0.4'} - dependencies: - internal-slot: 1.0.5 - dev: true - /stream-each@1.2.3: resolution: {integrity: sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==} dependencies: @@ -13666,7 +13497,7 @@ packages: /unload@2.2.0: resolution: {integrity: sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==} dependencies: - '@babel/runtime': 7.22.6 + '@babel/runtime': 7.23.9 detect-node: 2.1.0 dev: false @@ -14020,15 +13851,6 @@ packages: is-symbol: 1.0.4 dev: true - /which-collection@1.0.1: - resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} - dependencies: - is-map: 2.0.2 - is-set: 2.0.2 - is-weakmap: 2.0.1 - is-weakset: 2.0.2 - dev: true - /which-typed-array@1.1.10: resolution: {integrity: sha512-uxoA5vLUfRPdjCuJ1h5LlYdmTLbYfums398v3WLkM+i/Wltl2/XyZpQWKbN++ck5L64SR/grOHqtXCUKmlZPNA==} engines: {node: '>= 0.4'} diff --git a/turbo.json b/turbo.json index 449c94c..bc0d12b 100644 --- a/turbo.json +++ b/turbo.json @@ -12,6 +12,9 @@ "outputs": ["dist/**", ".next/**", "!.next/cache/**"], "inputs": ["*.config.js", "*.config.ts", "src/**", "tsconfig.json"], "env": [ + "CI", + "LOG_LEVEL", + "NEXT_PUBLIC_LOG_LEVEL", "NEXT_PUBLIC_SANITY_API_VERSION", "NEXT_PUBLIC_SANITY_DATASET", "NEXT_PUBLIC_SANITY_EDITOR_TOKEN",