@viamrobotics/test-widgets 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -64
- package/dist/components/queries.svelte +11 -8
- package/dist/components/table.svelte +2 -1
- package/dist/components/widgets/camera/camera.svelte +2 -3
- package/dist/components/widgets/camera/live-or-polling-video.svelte +13 -12
- package/dist/components/widgets/camera/picture-in-picture-button.svelte +7 -8
- package/dist/components/widgets/camera/picture-in-picture-button.svelte.d.ts +2 -3
- package/dist/components/widgets/vision-service/detection.svelte +0 -1
- package/dist/components/widgets/vision-service/image.svelte +8 -3
- package/dist/components/widgets/vision-service/vision-service.svelte +4 -1
- package/package.json +39 -29
package/README.md
CHANGED
|
@@ -1,65 +1,46 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
To create a production version of your showcase app:
|
|
48
|
-
|
|
49
|
-
```sh
|
|
50
|
-
npm run build
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
You can preview the production build with `npm run preview`.
|
|
54
|
-
|
|
55
|
-
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
|
56
|
-
|
|
57
|
-
## Publishing
|
|
58
|
-
|
|
59
|
-
Go into the `package.json` and give your package the desired name through the `"name"` option. Also consider adding a `"license"` field and point it to a `LICENSE` file which you can create from a template (one popular option is the [MIT license](https://opensource.org/license/mit/)).
|
|
60
|
-
|
|
61
|
-
To publish your library to [npm](https://www.npmjs.com):
|
|
62
|
-
|
|
63
|
-
```sh
|
|
64
|
-
npm publish
|
|
1
|
+
# @viamrobotics/test-widgets
|
|
2
|
+
|
|
3
|
+
A library of Svelte components for interacting with Viam-powered machines. Each widget provides a test interface for a specific resource type -- arms, bases, cameras, motors, sensors, and more -- allowing users to send commands, view live data, and control hardware directly from the browser.
|
|
4
|
+
|
|
5
|
+
Also includes reusable building blocks for visualizations, such as maps (MapLibre), SLAM mapping, 3D point clouds (Three.js), etc.
|
|
6
|
+
|
|
7
|
+
## Playground
|
|
8
|
+
|
|
9
|
+
The playground (`pnpm dev`) can be used to develop the test-cards against prod robots with prod modules.
|
|
10
|
+
|
|
11
|
+
This is useful if you need to validate the sdk against specific behavior of modules or need to replicate a bug from another robot (why replicate locally when you could just develop directly against the robot with the bug?).
|
|
12
|
+
|
|
13
|
+
To setup your playground, create a `.env.local` in the test-cards directory with the following format (no need to create two robots):
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
VITE_PLAYGROUND_ROBOTS='
|
|
17
|
+
{
|
|
18
|
+
"some prod robot": {
|
|
19
|
+
"host": "fleet-rover-01-main.ve4ba7w5qr.viam.cloud",
|
|
20
|
+
"partId": "<PART-ID>",
|
|
21
|
+
"apiKeyId": "<API-KEY-ID>",
|
|
22
|
+
"apiKeyValue": "<API-KEY-VALUE>",
|
|
23
|
+
"signalingAddress": "https://app.viam.com:443"
|
|
24
|
+
},
|
|
25
|
+
"some staging robot name": {
|
|
26
|
+
"host": "fleet-rover-02-main.ytobojb44p.viamstg.cloud",
|
|
27
|
+
"partId": "<PART-ID>",
|
|
28
|
+
"apiKeyId": "<API-KEY-ID>",
|
|
29
|
+
"apiKeyValue": "<API-KEY-VALUE>",
|
|
30
|
+
"signalingAddress": "https://app.viam.dev:443"
|
|
31
|
+
},
|
|
32
|
+
"local-machine (no fqdn)": {
|
|
33
|
+
"host": "localhost:8080",
|
|
34
|
+
"serviceHost": "http://localhost:8080",
|
|
35
|
+
"partId": "local-machine (no fqdn)",
|
|
36
|
+
"signalingAddress": ""
|
|
37
|
+
},
|
|
38
|
+
"local-machine (fqdn)": {
|
|
39
|
+
"host": "something-unique",
|
|
40
|
+
"serviceHost": "http://localhost:8080",
|
|
41
|
+
"partId": "local-machine (fqdn)",
|
|
42
|
+
"signalingAddress": ""
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
'
|
|
65
46
|
```
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { ResizeDetail } from '@svelte-put/resize'
|
|
3
2
|
import type { QueryObserverResult } from '@tanstack/svelte-query'
|
|
4
3
|
import type { Snippet } from 'svelte'
|
|
5
4
|
|
|
6
|
-
import {
|
|
5
|
+
import { useResizeObserver } from 'runed'
|
|
7
6
|
|
|
8
7
|
import ContentRect from './content-rect.svelte'
|
|
9
8
|
import ErrorDisplay from './error.svelte'
|
|
@@ -17,10 +16,15 @@
|
|
|
17
16
|
|
|
18
17
|
const { queries, contentCx = '', children }: Props = $props()
|
|
19
18
|
|
|
20
|
-
let
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
let el = $state.raw<HTMLDivElement>()
|
|
20
|
+
let contentRect = $state.raw<DOMRect>()
|
|
21
|
+
|
|
22
|
+
useResizeObserver(
|
|
23
|
+
() => el,
|
|
24
|
+
([entry]) => {
|
|
25
|
+
contentRect = entry.contentRect
|
|
26
|
+
}
|
|
27
|
+
)
|
|
24
28
|
|
|
25
29
|
let errors = $state.raw<Error[]>([])
|
|
26
30
|
let data = $state.raw<unknown[]>([])
|
|
@@ -65,8 +69,7 @@
|
|
|
65
69
|
{:else}
|
|
66
70
|
<div
|
|
67
71
|
class="w-full"
|
|
68
|
-
|
|
69
|
-
onresized={handleResize}
|
|
72
|
+
bind:this={el}
|
|
70
73
|
>
|
|
71
74
|
{@render children?.({ data })}
|
|
72
75
|
</div>
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { Button, Label, Switch, ToggleButtons } from '@viamrobotics/prime-core'
|
|
3
3
|
import { CameraClient } from '@viamrobotics/sdk'
|
|
4
4
|
import { createResourceClient, createResourceQuery } from '@viamrobotics/svelte-sdk'
|
|
5
|
-
import { writable } from 'svelte/store'
|
|
6
5
|
|
|
7
6
|
import { useAddImageToDataset } from '../../../add-image-to-dataset'
|
|
8
7
|
import ConnectionStatus from '../../connection-status.svelte'
|
|
@@ -30,7 +29,7 @@
|
|
|
30
29
|
() => resourceName,
|
|
31
30
|
'camera'
|
|
32
31
|
)
|
|
33
|
-
const isShowingPip =
|
|
32
|
+
const isShowingPip = $state(false)
|
|
34
33
|
let isShowingPointcloud = $state(false)
|
|
35
34
|
|
|
36
35
|
const { addImageToDataset } = useAddImageToDataset()
|
|
@@ -47,7 +46,7 @@
|
|
|
47
46
|
const imageQuery = createResourceQuery(client, 'getImages', () => ({
|
|
48
47
|
enabled: refetchInterval.current !== RefetchIntervals.LIVE,
|
|
49
48
|
refetchInterval: refetchInterval.current,
|
|
50
|
-
refetchIntervalInBackground:
|
|
49
|
+
refetchIntervalInBackground: isShowingPip,
|
|
51
50
|
}))
|
|
52
51
|
|
|
53
52
|
const pointcloudQuery = createResourceQuery(client, 'getPointCloud', () => ({
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { ResizeDetail } from '@svelte-put/resize'
|
|
3
|
-
|
|
4
2
|
import * as Sentry from '@sentry/svelte'
|
|
5
|
-
import { resize } from '@svelte-put/resize'
|
|
6
3
|
import { createMutation, type QueryObserverResult } from '@tanstack/svelte-query'
|
|
7
4
|
import { Button, Label, Select } from '@viamrobotics/prime-core'
|
|
8
5
|
import { CameraClient, type RobotClient, streamApi, StreamClient } from '@viamrobotics/sdk'
|
|
9
6
|
import { useRobotClient } from '@viamrobotics/svelte-sdk'
|
|
7
|
+
import { useResizeObserver } from 'runed'
|
|
10
8
|
import { untrack } from 'svelte'
|
|
11
9
|
|
|
12
10
|
import { assertExists } from '../../../assert'
|
|
@@ -187,11 +185,13 @@
|
|
|
187
185
|
})
|
|
188
186
|
|
|
189
187
|
$effect(() => {
|
|
190
|
-
return
|
|
191
|
-
|
|
192
|
-
|
|
188
|
+
return () => {
|
|
189
|
+
untrack(() => {
|
|
190
|
+
disableStream().then(() => {
|
|
191
|
+
setMediaStream(null)
|
|
192
|
+
})
|
|
193
193
|
})
|
|
194
|
-
}
|
|
194
|
+
}
|
|
195
195
|
})
|
|
196
196
|
|
|
197
197
|
$effect(() => {
|
|
@@ -228,9 +228,12 @@
|
|
|
228
228
|
})
|
|
229
229
|
|
|
230
230
|
let contentRect = $state.raw<DOMRect>()
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
231
|
+
useResizeObserver(
|
|
232
|
+
() => videoElement,
|
|
233
|
+
([entry]) => {
|
|
234
|
+
contentRect = entry.contentRect
|
|
235
|
+
}
|
|
236
|
+
)
|
|
234
237
|
|
|
235
238
|
let hoverTooltipOpen = $state(false)
|
|
236
239
|
let mouseHoverImagePosition = $state({ pctX: 0, pctY: 0, absX: 0, absY: 0 })
|
|
@@ -340,8 +343,6 @@
|
|
|
340
343
|
controls={false}
|
|
341
344
|
playsinline
|
|
342
345
|
aria-label={`${resourceName} stream`}
|
|
343
|
-
use:resize
|
|
344
|
-
onresized={handleResize}
|
|
345
346
|
>
|
|
346
347
|
</video>
|
|
347
348
|
</div>
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { Writable } from 'svelte/store'
|
|
3
|
-
|
|
4
2
|
import { Button } from '@viamrobotics/prime-core'
|
|
5
3
|
import { onMount } from 'svelte'
|
|
6
4
|
|
|
@@ -9,19 +7,20 @@
|
|
|
9
7
|
interface Props {
|
|
10
8
|
resourceName: string
|
|
11
9
|
mediaStream: MediaStream | null
|
|
12
|
-
isShowingPip:
|
|
10
|
+
isShowingPip: boolean
|
|
13
11
|
}
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
let { resourceName, mediaStream, isShowingPip = $bindable(false) }: Props = $props()
|
|
16
14
|
|
|
17
15
|
let videoElement = $state.raw<HTMLVideoElement>()
|
|
18
16
|
let lastErr = $state.raw<Error>()
|
|
19
17
|
|
|
20
18
|
const onEnter = () => {
|
|
21
|
-
|
|
19
|
+
isShowingPip = true
|
|
22
20
|
}
|
|
21
|
+
|
|
23
22
|
const onLeave = () => {
|
|
24
|
-
|
|
23
|
+
isShowingPip = false
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
onMount(() => {
|
|
@@ -31,7 +30,7 @@
|
|
|
31
30
|
return () => {
|
|
32
31
|
videoElement?.removeEventListener('enterpictureinpicture', onEnter)
|
|
33
32
|
videoElement?.removeEventListener('leavepictureinpicture', onLeave)
|
|
34
|
-
if (
|
|
33
|
+
if (isShowingPip) {
|
|
35
34
|
document.exitPictureInPicture()
|
|
36
35
|
}
|
|
37
36
|
}
|
|
@@ -47,7 +46,7 @@
|
|
|
47
46
|
lastErr = undefined
|
|
48
47
|
|
|
49
48
|
try {
|
|
50
|
-
await (
|
|
49
|
+
await (isShowingPip
|
|
51
50
|
? document.exitPictureInPicture()
|
|
52
51
|
: videoElement?.requestPictureInPicture())
|
|
53
52
|
} catch (error) {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import type { Writable } from 'svelte/store';
|
|
2
1
|
interface Props {
|
|
3
2
|
resourceName: string;
|
|
4
3
|
mediaStream: MediaStream | null;
|
|
5
|
-
isShowingPip:
|
|
4
|
+
isShowingPip: boolean;
|
|
6
5
|
}
|
|
7
|
-
declare const PictureInPictureButton: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
declare const PictureInPictureButton: import("svelte").Component<Props, {}, "isShowingPip">;
|
|
8
7
|
type PictureInPictureButton = ReturnType<typeof PictureInPictureButton>;
|
|
9
8
|
export default PictureInPictureButton;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { cameraApi, Classification, Detection } from '@viamrobotics/sdk'
|
|
3
3
|
|
|
4
|
-
import { resize } from '@svelte-put/resize'
|
|
5
4
|
import { Canvas } from '@threlte/core'
|
|
6
5
|
import { Progress } from '@viamrobotics/prime-core'
|
|
6
|
+
import { useResizeObserver } from 'runed'
|
|
7
7
|
import { provideFontFamilies } from 'threlte-uikit'
|
|
8
8
|
|
|
9
9
|
import { useMeasureFps } from '../../../fps.svelte'
|
|
@@ -69,6 +69,13 @@
|
|
|
69
69
|
let container = $state<HTMLElement>()
|
|
70
70
|
let size = $state<Size>()
|
|
71
71
|
|
|
72
|
+
useResizeObserver(
|
|
73
|
+
() => container,
|
|
74
|
+
() => {
|
|
75
|
+
setSize()
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
|
|
72
79
|
const setSize = () => {
|
|
73
80
|
if (container) {
|
|
74
81
|
size = getImageSize(img, container)
|
|
@@ -94,8 +101,6 @@
|
|
|
94
101
|
<div
|
|
95
102
|
class="flex h-full min-h-0 w-full min-w-0 gap-2 py-2 pl-2"
|
|
96
103
|
bind:this={container}
|
|
97
|
-
use:resize
|
|
98
|
-
onresize={setSize}
|
|
99
104
|
>
|
|
100
105
|
{#if !size}
|
|
101
106
|
<div class="grid w-full place-content-center">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viamrobotics/test-widgets",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -27,76 +27,86 @@
|
|
|
27
27
|
"url": "git+https://github.com/viamrobotics/test-widgets.git"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"svelte": "
|
|
30
|
+
"@sentry/svelte": ">=8",
|
|
31
|
+
"@threlte/core": ">=8",
|
|
32
|
+
"@threlte/extras": ">=9",
|
|
33
|
+
"@viamrobotics/prime-core": ">=0.1",
|
|
34
|
+
"@viamrobotics/prime-editor": ">=0.0.2",
|
|
35
|
+
"@viamrobotics/sdk": ">=0.68",
|
|
36
|
+
"@viamrobotics/svelte-sdk": ">=1.1",
|
|
37
|
+
"lodash-es": ">=4",
|
|
38
|
+
"runed": ">=0.28",
|
|
39
|
+
"svelte": ">=5",
|
|
40
|
+
"three": ">=0.178",
|
|
41
|
+
"threlte-uikit": ">=2"
|
|
31
42
|
},
|
|
32
43
|
"devDependencies": {
|
|
33
44
|
"@changesets/cli": "^2.30.0",
|
|
34
|
-
"@eslint/compat": "^2.0.
|
|
45
|
+
"@eslint/compat": "^2.0.5",
|
|
35
46
|
"@eslint/js": "^10.0.1",
|
|
36
47
|
"@fontsource-variable/public-sans": "^5.2.7",
|
|
37
48
|
"@fontsource-variable/roboto-mono": "^5.2.8",
|
|
38
49
|
"@fontsource-variable/space-grotesk": "^5.2.10",
|
|
39
50
|
"@playwright/test": "^1.59.1",
|
|
40
|
-
"@sentry/svelte": "^10.
|
|
41
|
-
"@svelte-put/resize": "^4.0.0",
|
|
51
|
+
"@sentry/svelte": "^10.48.0",
|
|
42
52
|
"@sveltejs/adapter-static": "^3.0.10",
|
|
43
|
-
"@sveltejs/kit": "^2.
|
|
53
|
+
"@sveltejs/kit": "^2.57.1",
|
|
44
54
|
"@sveltejs/package": "^2.5.7",
|
|
45
55
|
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
46
56
|
"@tailwindcss/postcss": "^4.2.2",
|
|
47
57
|
"@tailwindcss/vite": "^4.2.2",
|
|
48
|
-
"@tanstack/svelte-query": "^6.1.
|
|
49
|
-
"@tanstack/svelte-query-devtools": "^6.1.
|
|
58
|
+
"@tanstack/svelte-query": "^6.1.16",
|
|
59
|
+
"@tanstack/svelte-query-devtools": "^6.1.16",
|
|
50
60
|
"@testing-library/dom": "^10.4.1",
|
|
51
61
|
"@testing-library/jest-dom": "^6.9.1",
|
|
52
62
|
"@testing-library/svelte": "^5.3.1",
|
|
53
63
|
"@testing-library/user-event": "^14.6.1",
|
|
54
|
-
"@threlte/core": "^8.5.
|
|
55
|
-
"@threlte/extras": "^9.14.
|
|
56
|
-
"@threlte/test": "^
|
|
64
|
+
"@threlte/core": "^8.5.9",
|
|
65
|
+
"@threlte/extras": "^9.14.6",
|
|
66
|
+
"@threlte/test": "^2.0.2",
|
|
57
67
|
"@types/lodash-es": "^4.17.12",
|
|
58
68
|
"@types/node": "^25",
|
|
59
69
|
"@types/three": "^0.183.1",
|
|
60
|
-
"@viamrobotics/motion-tools": "^1.
|
|
70
|
+
"@viamrobotics/motion-tools": "^1.18.1",
|
|
61
71
|
"@viamrobotics/prime-core": "^0.1.16",
|
|
62
72
|
"@viamrobotics/prime-editor": "^0.0.2",
|
|
63
|
-
"@viamrobotics/sdk": "^0.68.
|
|
64
|
-
"@viamrobotics/svelte-sdk": "^1.1.
|
|
73
|
+
"@viamrobotics/sdk": "^0.68.2",
|
|
74
|
+
"@viamrobotics/svelte-sdk": "^1.1.6",
|
|
65
75
|
"@viamrobotics/tailwind-config": "^1.0.0",
|
|
66
76
|
"@viamrobotics/three": "^0.0.9",
|
|
67
|
-
"@vitest/browser": "^4.1.
|
|
68
|
-
"@vitest/browser-playwright": "^4.1.
|
|
69
|
-
"@vitest/coverage-v8": "^4.1.
|
|
70
|
-
"eslint": "^10.
|
|
77
|
+
"@vitest/browser": "^4.1.4",
|
|
78
|
+
"@vitest/browser-playwright": "^4.1.4",
|
|
79
|
+
"@vitest/coverage-v8": "^4.1.4",
|
|
80
|
+
"eslint": "^10.2.0",
|
|
71
81
|
"eslint-config-prettier": "^10.1.8",
|
|
72
82
|
"eslint-plugin-perfectionist": "^5.8.0",
|
|
73
|
-
"eslint-plugin-svelte": "^3.
|
|
83
|
+
"eslint-plugin-svelte": "^3.17.0",
|
|
74
84
|
"eslint-plugin-unicorn": "^64.0.0",
|
|
75
|
-
"globals": "^17.
|
|
85
|
+
"globals": "^17.5.0",
|
|
76
86
|
"lodash-es": "^4.18.1",
|
|
77
|
-
"maplibre-gl": "^5.
|
|
87
|
+
"maplibre-gl": "^5.23.0",
|
|
78
88
|
"mock-match-media": "^1.0.3",
|
|
79
89
|
"playwright": "^1.59.1",
|
|
80
|
-
"prettier": "^3.8.
|
|
90
|
+
"prettier": "^3.8.3",
|
|
81
91
|
"prettier-plugin-svelte": "^3.5.1",
|
|
82
92
|
"prettier-plugin-tailwindcss": "^0.7.2",
|
|
83
93
|
"publint": "^0.3.18",
|
|
84
94
|
"runed": "^0.37.1",
|
|
85
|
-
"svelte": "^5.55.
|
|
95
|
+
"svelte": "^5.55.4",
|
|
86
96
|
"svelte-check": "^4.4.6",
|
|
87
97
|
"svelte-splitpanes": "^8.0.12",
|
|
88
98
|
"tailwind-merge": "^3.5.0",
|
|
89
99
|
"tailwindcss": "^4.2.2",
|
|
90
100
|
"three": "^0.183.2",
|
|
91
|
-
"threlte-uikit": "^
|
|
101
|
+
"threlte-uikit": "^2.0.0",
|
|
92
102
|
"type-fest": "^5.5.0",
|
|
93
103
|
"typescript": "^6.0.2",
|
|
94
|
-
"typescript-eslint": "^8.58.
|
|
95
|
-
"vite": "^8.0.
|
|
104
|
+
"typescript-eslint": "^8.58.2",
|
|
105
|
+
"vite": "^8.0.8",
|
|
96
106
|
"vite-plugin-devtools-json": "^1.0.0",
|
|
97
|
-
"vite-plugin-glsl": "^1.
|
|
98
|
-
"vitest": "^4.1.
|
|
99
|
-
"vitest-browser-svelte": "^2.1.
|
|
107
|
+
"vite-plugin-glsl": "^1.6.0",
|
|
108
|
+
"vitest": "^4.1.4",
|
|
109
|
+
"vitest-browser-svelte": "^2.1.1"
|
|
100
110
|
},
|
|
101
111
|
"engines": {
|
|
102
112
|
"node": ">=22.12.0"
|