diff --git a/apps/somai.me/app/essay/[slug]/audio.tsx b/apps/somai.me/app/essay/[slug]/audio.tsx new file mode 100644 index 0000000..0a47767 --- /dev/null +++ b/apps/somai.me/app/essay/[slug]/audio.tsx @@ -0,0 +1,114 @@ +"use client"; + +import { DotsThree, PauseCircle, PlayCircle } from "@phosphor-icons/react"; +import React, { useEffect, useRef } from "react"; +import { useAudioPlayer } from "react-use-audio-player"; + +import { Button, Stack, Text, Box, Progress } from "@thugga/ui"; + +// Enum Statuses for the audio player +enum AudioPlayerStatus { + Loading = "Loading", + Pause = "Pause", + Play = "Play", +} + +function PlayIcon({ status }: { status: AudioPlayerStatus }) { + switch (status) { + case AudioPlayerStatus.Loading: + return ; + case AudioPlayerStatus.Pause: + return ; + case AudioPlayerStatus.Play: + return ; + } +} + +export default function AudioPlayer({ audio }: { audio: string }) { + const { load, play, pause, playing, isReady, duration, getPosition } = + useAudioPlayer(); + const [pos, setPos] = React.useState(0); + const [status, setStatus] = React.useState( + AudioPlayerStatus.Loading, + ); + + const frameRef = useRef(); + + useEffect(() => { + const animate = () => { + setPos(getPosition()); + frameRef.current = requestAnimationFrame(animate); + }; + + frameRef.current = window.requestAnimationFrame(animate); + + return () => { + if (frameRef.current) { + cancelAnimationFrame(frameRef.current); + } + }; + }, []); + + useEffect(() => { + load(audio, { + autoplay: false, + format: "mp3", + }); + }, [audio, load]); + + useEffect(() => { + if (!isReady) { + setStatus(AudioPlayerStatus.Loading); + } else if (playing) { + setStatus(AudioPlayerStatus.Pause); + } else { + setStatus(AudioPlayerStatus.Play); + } + }, [isReady, playing]); + + const handlePlay = () => { + if (!isReady) { + return; + } + + if (!playing) { + play(); + } else { + pause(); + } + }; + + return ( + + + {status !== AudioPlayerStatus.Loading && ( + <> + + + {new Date(pos * 1000).toISOString().substring(14, 19)} + + / + + {new Date(duration * 1000).toISOString().substring(14, 19)} + + + + )} + + + ); +} diff --git a/apps/somai.me/app/essay/[slug]/page.tsx b/apps/somai.me/app/essay/[slug]/page.tsx index 6e6256d..40c2136 100644 --- a/apps/somai.me/app/essay/[slug]/page.tsx +++ b/apps/somai.me/app/essay/[slug]/page.tsx @@ -3,11 +3,12 @@ import type { Metadata } from "next"; import React from "react"; import { config, components } from "@thugga/markdoc"; -import { Stack } from "@thugga/ui"; +import { Stack, Box } from "@thugga/ui"; import { getAllPosts, getPostBySlug } from "@/lib/essays"; import { Seo } from "@/lib/seo"; +import AudioPlayer from "./audio"; import DetailsSection from "./section.details"; import Excerpt from "./section.excerpt"; import Title from "./section.title"; @@ -25,9 +26,10 @@ export default function EssayPage({ params }: Props) { const rendered = Markdoc.renderers.react(content, React, { components }); return ( - + <DetailsSection post={post} /> + {post.meta.audio && <AudioPlayer audio={post.meta.audio} />} <Excerpt post={post} /> <Stack>{rendered}</Stack> </Stack> diff --git a/apps/somai.me/content/posts/digital_health.md b/apps/somai.me/content/posts/digital_health.md index b2a1a08..c4cf090 100644 --- a/apps/somai.me/content/posts/digital_health.md +++ b/apps/somai.me/content/posts/digital_health.md @@ -2,6 +2,7 @@ title: Digital Healthcare Innovation subtitle: Challenges and Lessons for Digital Health Startups featured: true +audio: /audio/essays/digital_health.mp3 image: /images/posts/universal_healthcare/hero.png authors: - melek-somai diff --git a/apps/somai.me/lib/essays.ts b/apps/somai.me/lib/essays.ts index 9323c0b..e2dbf91 100644 --- a/apps/somai.me/lib/essays.ts +++ b/apps/somai.me/lib/essays.ts @@ -15,6 +15,7 @@ export type Post = { content: string; meta: { [key: string]: any; + audio?: string | undefined; excerpt: string; image: string; publishedAt: { diff --git a/apps/somai.me/package.json b/apps/somai.me/package.json index b4a40cb..3ac28ef 100644 --- a/apps/somai.me/package.json +++ b/apps/somai.me/package.json @@ -29,6 +29,7 @@ "react": "18.3.0", "react-dom": "18.3.0", "react-icons": "5.3.0", + "react-use-audio-player": "2.2.0", "reading-time": "1.5.0", "swr": "2.2.5" }, diff --git a/packages/ui/package.json b/packages/ui/package.json index 16ea9e2..5fe00f4 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -19,6 +19,7 @@ "@radix-ui/react-avatar": "1.0.2", "@radix-ui/react-dialog": "1.0.4", "@radix-ui/react-popover": "1.0.5", + "@radix-ui/react-progress": "^1.1.0", "@radix-ui/react-scroll-area": "1.0.3", "@radix-ui/react-select": "1.2.1", "@radix-ui/react-separator": "1.0.2", diff --git a/packages/ui/src/components/Box/Box.tsx b/packages/ui/src/components/Box/Box.tsx index 799717f..a5a0a1f 100644 --- a/packages/ui/src/components/Box/Box.tsx +++ b/packages/ui/src/components/Box/Box.tsx @@ -13,7 +13,7 @@ export interface BoxProps extends Omit<Atoms, "reset"> { export const Box = forwardRef<HTMLElement, BoxProps>( ( { as: Component = "div", asChild = false, width = "full", ...other }, - ref + ref, ) => { const [atomsProps, propsToForward] = extractAtoms(other); const className = atoms({ @@ -26,7 +26,7 @@ export const Box = forwardRef<HTMLElement, BoxProps>( const Comp = asChild ? Slot : Component; return <Comp {...propsToForward} className={className} ref={ref} />; - } + }, ); Box.displayName = "Box"; diff --git a/packages/ui/src/components/Button/Button.tsx b/packages/ui/src/components/Button/Button.tsx index 186b607..fdc7d84 100644 --- a/packages/ui/src/components/Button/Button.tsx +++ b/packages/ui/src/components/Button/Button.tsx @@ -59,7 +59,7 @@ export const Button = React.forwardRef( minWidth, ...boxProps }: ButtonProps, - ref: React.Ref<HTMLButtonElement> + ref: React.Ref<HTMLButtonElement>, ) => { // if (shape) { // childContent = loading ? <Spinner color="current" /> : labelContent; @@ -104,7 +104,7 @@ export const Button = React.forwardRef( {childContent} </Box> ); - } + }, ); Button.displayName = "Button"; diff --git a/packages/ui/src/components/Progress/Progress.css.ts b/packages/ui/src/components/Progress/Progress.css.ts new file mode 100644 index 0000000..e32bdd7 --- /dev/null +++ b/packages/ui/src/components/Progress/Progress.css.ts @@ -0,0 +1,49 @@ +import { style } from "@vanilla-extract/css"; +import { RecipeVariants, recipe } from "@vanilla-extract/recipes"; + +import { atoms } from "../../atoms"; + +const size = {}; + +export type Size = keyof typeof size; + +export const rootVariants = recipe({ + base: [ + atoms({ + position: "relative", + width: "full", + overflow: "hidden", + borderRadius: "full", + backgroundColor: "slate7", + height: "300", + marginLeft: "100", + }), + style({ + transform: "translateZ(0)", + }), + ], + variants: { + size, + }, +}); + +export type RootVariants = RecipeVariants<typeof rootVariants>; + +export const indicatorVariants = recipe({ + base: [ + atoms({ + // position: "absolute", + // top: 0, + // left: 0, + // bottom: 0, + backgroundColor: "slate10", + width: "full", + height: "full", + }), + ], + variants: { + size, + }, +}); + +export type IndicatorVariants = RecipeVariants<typeof indicatorVariants>; diff --git a/packages/ui/src/components/Progress/Progress.tsx b/packages/ui/src/components/Progress/Progress.tsx new file mode 100644 index 0000000..2d8e1f4 --- /dev/null +++ b/packages/ui/src/components/Progress/Progress.tsx @@ -0,0 +1,28 @@ +import * as ProgressPrimitive from "@radix-ui/react-progress"; +import * as React from "react"; + +import * as styles from "./Progress.css"; + +export type ProgressProps = { + id?: string; + progress: number; + size?: styles.Size; +} & styles.RootVariants; + +export const Progress = ({ id, progress = 0 }: ProgressProps) => { + return ( + <ProgressPrimitive.Root + id={id} + className={styles.rootVariants({ + // size, + })} + > + <ProgressPrimitive.Indicator + className={styles.indicatorVariants({ + // size, + })} + style={{ transform: `translateX(-${100 - progress}%)` }} + /> + </ProgressPrimitive.Root> + ); +}; diff --git a/packages/ui/src/components/Progress/index.ts b/packages/ui/src/components/Progress/index.ts new file mode 100644 index 0000000..54127c7 --- /dev/null +++ b/packages/ui/src/components/Progress/index.ts @@ -0,0 +1,2 @@ +export { Progress } from "./Progress"; +export type { ProgressProps } from "./Progress"; diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index 4a0c464..1b25b4a 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -4,14 +4,15 @@ export * from "./Box"; export * from "./Breadcrumbs"; export * from "./Button"; export * from "./Callout"; -export * from "./Code"; export * from "./CmdK"; +export * from "./Code"; export * from "./Dialog"; export * from "./Grid"; export * from "./Heading"; export * from "./Image"; export * from "./Link"; export * from "./Logo"; +export * from "./Progress"; export * from "./ScrollArea"; export * from "./Stack"; export * from "./Table"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc6b0d1..fc010d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -98,7 +98,7 @@ importers: version: 29.7.0(@types/node@22.2.0)(ts-node@10.9.2(@types/node@22.2.0)(typescript@5.2.2)) ts-jest: specifier: 29.1.4 - version: 29.1.4(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.2.0))(typescript@5.2.2) + version: 29.1.4(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.2.0)(ts-node@10.9.2(@types/node@22.2.0)(typescript@5.2.2)))(typescript@5.2.2) ts-node: specifier: 10.9.2 version: 10.9.2(@types/node@22.2.0)(typescript@5.2.2) @@ -164,7 +164,7 @@ importers: version: 6.5.0(next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.0(react@18.3.0))(react@18.3.0))(react-dom@18.3.0(react@18.3.0))(react@18.3.0) next-themes: specifier: 0.2.1 - version: 0.2.1(next@14.2.5(react-dom@18.3.0(react@18.3.0))(react@18.3.0))(react-dom@18.3.0(react@18.3.0))(react@18.3.0) + version: 0.2.1(next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.0(react@18.3.0))(react@18.3.0))(react-dom@18.3.0(react@18.3.0))(react@18.3.0) react: specifier: 18.3.0 version: 18.3.0 @@ -174,6 +174,9 @@ importers: react-icons: specifier: 5.3.0 version: 5.3.0(react@18.3.0) + react-use-audio-player: + specifier: 2.2.0 + version: 2.2.0(react@18.3.0) reading-time: specifier: 1.5.0 version: 1.5.0 @@ -323,7 +326,7 @@ importers: version: 29.7.0(@types/node@22.2.0)(ts-node@10.9.2(@types/node@22.2.0)(typescript@5.2.2)) ts-jest: specifier: 29.1.4 - version: 29.1.4(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.2.0))(typescript@5.2.2) + version: 29.1.4(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.2.0)(ts-node@10.9.2(@types/node@22.2.0)(typescript@5.2.2)))(typescript@5.2.2) typescript: specifier: 5.2.2 version: 5.2.2 @@ -387,6 +390,9 @@ importers: '@radix-ui/react-popover': specifier: 1.0.5 version: 1.0.5(@types/react@18.3.0)(react-dom@18.3.0(react@18.3.0))(react@18.3.0) + '@radix-ui/react-progress': + specifier: ^1.1.0 + version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.0)(react-dom@18.3.0(react@18.3.0))(react@18.3.0) '@radix-ui/react-scroll-area': specifier: 1.0.3 version: 1.0.3(react-dom@18.3.0(react@18.3.0))(react@18.3.0) @@ -431,7 +437,7 @@ importers: version: 4.17.21 next-themes: specifier: 0.2.1 - version: 0.2.1(next@14.2.5(react-dom@18.3.0(react@18.3.0))(react@18.3.0))(react-dom@18.3.0(react@18.3.0))(react@18.3.0) + version: 0.2.1(next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.0(react@18.3.0))(react@18.3.0))(react-dom@18.3.0(react@18.3.0))(react@18.3.0) prism-react-renderer: specifier: 1.3.3 version: 1.3.3(react@18.3.0) @@ -1418,6 +1424,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-compose-refs@1.1.0': + resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-context@1.0.0': resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==} peerDependencies: @@ -1432,6 +1447,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.1.0': + resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-dialog@1.0.0': resolution: {integrity: sha512-Yn9YU+QlHYLWwV1XfKiqnGVpWYWk6MeBVM6x/bcoyPvxgjQGoeT35482viLPctTMWoMw0PoHgqfSox7Ig+957Q==} peerDependencies: @@ -1633,6 +1657,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-primitive@2.0.0': + resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-progress@1.1.0': + resolution: {integrity: sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-scroll-area@1.0.3': resolution: {integrity: sha512-sBX9j8Q+0/jReNObEAveKIGXJtk3xUoSIx4cMKygGtO128QJyVDn01XNOFsyvihKDCTcu7SINzQ2jPAZEhIQtw==} peerDependencies: @@ -1670,6 +1720,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-slot@1.1.0': + resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-tooltip@1.0.6': resolution: {integrity: sha512-DmNFOiwEc2UDigsYj6clJENma58OelxD24O4IODoZ+3sQc3Zb+L8w1EP+y9laTuKCLAysPw4fD6/v0j4KNV8rg==} peerDependencies: @@ -3847,6 +3906,9 @@ packages: highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + howler@2.2.4: + resolution: {integrity: sha512-iARIBPgcQrwtEr+tALF+rapJ8qSc+Set2GJQl7xT1MQzWaVkFebdJhR3alVlSiUf5U7nAANKuj3aWpwerocD5w==} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -5039,6 +5101,11 @@ packages: peerDependencies: react: '>= 0.14.0' + react-use-audio-player@2.2.0: + resolution: {integrity: sha512-IMM+WiZAq1KUvTwq0sAdRdjEzyG4jzz1tK/JtC/+LLt3z/uJk3AFRLL3pgbOgS/2SnfVzFKCxR7pnBsu6nUFvg==} + peerDependencies: + react: '>=16.8' + react-wrap-balancer@0.5.0: resolution: {integrity: sha512-5vwe5QDczQ9zwAtv3iEVj8hdMbNwQtM/QlSNLJfDUzRE9noPtxevb+Kon916Mu2RUorCrAtashQ1F9BVBjdeZg==} peerDependencies: @@ -7019,6 +7086,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.0 + '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.0)(react@18.3.0)': + dependencies: + react: 18.3.0 + optionalDependencies: + '@types/react': 18.3.0 + '@radix-ui/react-context@1.0.0(react@18.3.0)': dependencies: '@babel/runtime': 7.18.9 @@ -7031,6 +7104,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.0 + '@radix-ui/react-context@1.1.0(@types/react@18.3.0)(react@18.3.0)': + dependencies: + react: 18.3.0 + optionalDependencies: + '@types/react': 18.3.0 + '@radix-ui/react-dialog@1.0.0(@types/react@18.3.0)(react-dom@18.3.0(react@18.3.0))(react@18.3.0)': dependencies: '@babel/runtime': 7.18.9 @@ -7304,6 +7383,25 @@ snapshots: '@types/react': 18.3.0 '@types/react-dom': 18.3.0 + '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.0)(react-dom@18.3.0(react@18.3.0))(react@18.3.0)': + dependencies: + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.0)(react@18.3.0) + react: 18.3.0 + react-dom: 18.3.0(react@18.3.0) + optionalDependencies: + '@types/react': 18.3.0 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-progress@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.0)(react-dom@18.3.0(react@18.3.0))(react@18.3.0)': + dependencies: + '@radix-ui/react-context': 1.1.0(@types/react@18.3.0)(react@18.3.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.0)(react-dom@18.3.0(react@18.3.0))(react@18.3.0) + react: 18.3.0 + react-dom: 18.3.0(react@18.3.0) + optionalDependencies: + '@types/react': 18.3.0 + '@types/react-dom': 18.3.0 + '@radix-ui/react-scroll-area@1.0.3(react-dom@18.3.0(react@18.3.0))(react@18.3.0)': dependencies: '@babel/runtime': 7.18.9 @@ -7375,6 +7473,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.0 + '@radix-ui/react-slot@1.1.0(@types/react@18.3.0)(react@18.3.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.0)(react@18.3.0) + react: 18.3.0 + optionalDependencies: + '@types/react': 18.3.0 + '@radix-ui/react-tooltip@1.0.6(@types/react-dom@18.3.0)(@types/react@18.3.0)(react-dom@18.3.0(react@18.3.0))(react@18.3.0)': dependencies: '@babel/runtime': 7.18.9 @@ -9990,6 +10095,8 @@ snapshots: highlight.js@10.7.3: {} + howler@2.2.4: {} + html-escaper@2.0.2: {} html-to-text@9.0.5: @@ -11035,7 +11142,7 @@ snapshots: react: 18.3.0 react-dom: 18.3.0(react@18.3.0) - next-themes@0.2.1(next@14.2.5(react-dom@18.3.0(react@18.3.0))(react@18.3.0))(react-dom@18.3.0(react@18.3.0))(react@18.3.0): + next-themes@0.2.1(next@14.2.5(@babel/core@7.25.2)(react-dom@18.3.0(react@18.3.0))(react@18.3.0))(react-dom@18.3.0(react@18.3.0))(react@18.3.0): dependencies: next: 14.2.5(@babel/core@7.25.2)(react-dom@18.3.0(react@18.3.0))(react@18.3.0) react: 18.3.0 @@ -11454,6 +11561,11 @@ snapshots: react: 18.3.0 refractor: 3.6.0 + react-use-audio-player@2.2.0(react@18.3.0): + dependencies: + howler: 2.2.4 + react: 18.3.0 + react-wrap-balancer@0.5.0(react@18.3.0): dependencies: react: 18.3.0 @@ -12060,7 +12172,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.1.4(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.2.0))(typescript@5.2.2): + ts-jest@29.1.4(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.2.0)(ts-node@10.9.2(@types/node@22.2.0)(typescript@5.2.2)))(typescript@5.2.2): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0