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",