regressionbot 0.0.2 → 0.0.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 +124 -124
- package/dist/cli.js +47 -38
- package/dist/index.js +2 -2
- package/package.json +38 -38
package/README.md
CHANGED
|
@@ -1,125 +1,125 @@
|
|
|
1
|
-
# RegressionBot SDK
|
|
2
|
-
|
|
3
|
-
The declarative visual regression testing SDK.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Fluent Manifest Builder**: Chainable methods to define your test scope.
|
|
8
|
-
- **Matrix Testing**: Test multiple devices and viewports in a single job.
|
|
9
|
-
- **Auto-Discovery**: Scan sitemaps with glob patterns and limits.
|
|
10
|
-
- **Project-Based Baselines**: Share visual history across different environments (Preview vs Prod).
|
|
11
|
-
|
|
12
|
-
## Installation
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
npm install regressionbot
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## Usage
|
|
19
|
-
|
|
20
|
-
### Basic Example
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
import { Visual } from 'regressionbot';
|
|
24
|
-
|
|
25
|
-
const visual = new Visual();
|
|
26
|
-
|
|
27
|
-
const job = await visual
|
|
28
|
-
.test('https://preview.myapp.com')
|
|
29
|
-
.forProject('my-app-web')
|
|
30
|
-
.run();
|
|
31
|
-
|
|
32
|
-
const status = await job.waitForCompletion();
|
|
33
|
-
console.log(`Stability Score: ${status.overallScore}/100`);
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
### Full Matrix Example
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
import { Visual } from 'regressionbot';
|
|
40
|
-
|
|
41
|
-
const visual = new Visual(process.env.API_KEY);
|
|
42
|
-
|
|
43
|
-
const job = await visual
|
|
44
|
-
.test(process.env.VERCEL_PREVIEW_URL) // The Candidate (Test Origin)
|
|
45
|
-
.against('https://production-app.com') // The Source of Truth (Base Origin)
|
|
46
|
-
.forProject('marketing-site-v2') // Context: Links to Baselines & History
|
|
47
|
-
|
|
48
|
-
// Matrix Configuration: Run all checks on both Desktop and Mobile
|
|
49
|
-
.on(['Desktop Chrome', 'iPhone 13'])
|
|
50
|
-
|
|
51
|
-
// Sitemap: Explicitly provide a sitemap location (optional)
|
|
52
|
-
.sitemap('https://production-app.com/sitemap_index.xml')
|
|
53
|
-
|
|
54
|
-
// Scope: Explicitly check critical paths
|
|
55
|
-
.check('/', 'Homepage')
|
|
56
|
-
.check('/pricing', 'Pricing Table')
|
|
57
|
-
|
|
58
|
-
// Discovery: Auto-discover up to 20 blog posts
|
|
59
|
-
.scan('/blog/**', { limit: 20 })
|
|
60
|
-
|
|
61
|
-
// Concurrency: Max parallel browser instances
|
|
62
|
-
.concurrency(10)
|
|
63
|
-
|
|
64
|
-
// Masking: Automatic and manual masking
|
|
65
|
-
.mask(['.ads', '#modal']) // Manual selectors
|
|
66
|
-
// Tip: Adding 'data-vr-mask' to your HTML elements masks them automatically!
|
|
67
|
-
|
|
68
|
-
// Execute: Compiles manifest and triggers the API
|
|
69
|
-
.run();
|
|
70
|
-
|
|
71
|
-
const result = await job.waitForCompletion();
|
|
72
|
-
const summary = await job.getSummary();
|
|
73
|
-
|
|
74
|
-
console.log(`Job ${job.jobId} finished. Overall Score: ${summary.overallScore}`);
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## CLI Usage
|
|
78
|
-
|
|
79
|
-
The `regressionbot` CLI is the easiest way to interact with the API from your terminal or CI scripts.
|
|
80
|
-
|
|
81
|
-
### Authentication
|
|
82
|
-
|
|
83
|
-
The CLI looks for the following environment variables:
|
|
84
|
-
- `REGRESSIONBOT_API_KEY`: Your project API key.
|
|
85
|
-
- `REGRESSIONBOT_API_URL`: (Optional) Override the default API endpoint.
|
|
86
|
-
|
|
87
|
-
### Commands
|
|
88
|
-
|
|
89
|
-
#### 1. Quick Check
|
|
90
|
-
Test a single URL against its established baseline.
|
|
91
|
-
```bash
|
|
92
|
-
npx regressionbot https://example.com --project my-site --on "Desktop Chrome, iPhone 12"
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
#### 2. Sitemap Scan
|
|
96
|
-
Test an entire site using glob patterns.
|
|
97
|
-
```bash
|
|
98
|
-
npx regressionbot https://example.com --scan "/**" --exclude "/admin/**" --concurrency 20
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
#### 3. Job Summary
|
|
102
|
-
Get detailed results and diff URLs for a completed job.
|
|
103
|
-
```bash
|
|
104
|
-
npx regressionbot summary <jobId>
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
Add the `--download` flag to save regression images locally:
|
|
108
|
-
```bash
|
|
109
|
-
npx regressionbot summary <jobId> --download
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
#### 4. Approve Changes
|
|
113
|
-
Promote the current screenshots of a job to be the new baselines.
|
|
114
|
-
```bash
|
|
115
|
-
npx regressionbot approve <jobId>
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Development
|
|
119
|
-
|
|
120
|
-
### Versioning
|
|
121
|
-
|
|
122
|
-
```bash
|
|
123
|
-
npm version patch # or minor/major
|
|
124
|
-
npm publish
|
|
1
|
+
# RegressionBot SDK
|
|
2
|
+
|
|
3
|
+
The declarative visual regression testing SDK.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Fluent Manifest Builder**: Chainable methods to define your test scope.
|
|
8
|
+
- **Matrix Testing**: Test multiple devices and viewports in a single job.
|
|
9
|
+
- **Auto-Discovery**: Scan sitemaps with glob patterns and limits.
|
|
10
|
+
- **Project-Based Baselines**: Share visual history across different environments (Preview vs Prod).
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install regressionbot
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### Basic Example
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { Visual } from 'regressionbot';
|
|
24
|
+
|
|
25
|
+
const visual = new Visual();
|
|
26
|
+
|
|
27
|
+
const job = await visual
|
|
28
|
+
.test('https://preview.myapp.com')
|
|
29
|
+
.forProject('my-app-web')
|
|
30
|
+
.run();
|
|
31
|
+
|
|
32
|
+
const status = await job.waitForCompletion();
|
|
33
|
+
console.log(`Stability Score: ${status.overallScore}/100`);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Full Matrix Example
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { Visual } from 'regressionbot';
|
|
40
|
+
|
|
41
|
+
const visual = new Visual(process.env.API_KEY);
|
|
42
|
+
|
|
43
|
+
const job = await visual
|
|
44
|
+
.test(process.env.VERCEL_PREVIEW_URL) // The Candidate (Test Origin)
|
|
45
|
+
.against('https://production-app.com') // The Source of Truth (Base Origin)
|
|
46
|
+
.forProject('marketing-site-v2') // Context: Links to Baselines & History
|
|
47
|
+
|
|
48
|
+
// Matrix Configuration: Run all checks on both Desktop and Mobile
|
|
49
|
+
.on(['Desktop Chrome', 'iPhone 13'])
|
|
50
|
+
|
|
51
|
+
// Sitemap: Explicitly provide a sitemap location (optional)
|
|
52
|
+
.sitemap('https://production-app.com/sitemap_index.xml')
|
|
53
|
+
|
|
54
|
+
// Scope: Explicitly check critical paths
|
|
55
|
+
.check('/', 'Homepage')
|
|
56
|
+
.check('/pricing', 'Pricing Table')
|
|
57
|
+
|
|
58
|
+
// Discovery: Auto-discover up to 20 blog posts
|
|
59
|
+
.scan('/blog/**', { limit: 20 })
|
|
60
|
+
|
|
61
|
+
// Concurrency: Max parallel browser instances
|
|
62
|
+
.concurrency(10)
|
|
63
|
+
|
|
64
|
+
// Masking: Automatic and manual masking
|
|
65
|
+
.mask(['.ads', '#modal']) // Manual selectors
|
|
66
|
+
// Tip: Adding 'data-vr-mask' to your HTML elements masks them automatically!
|
|
67
|
+
|
|
68
|
+
// Execute: Compiles manifest and triggers the API
|
|
69
|
+
.run();
|
|
70
|
+
|
|
71
|
+
const result = await job.waitForCompletion();
|
|
72
|
+
const summary = await job.getSummary();
|
|
73
|
+
|
|
74
|
+
console.log(`Job ${job.jobId} finished. Overall Score: ${summary.overallScore}`);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## CLI Usage
|
|
78
|
+
|
|
79
|
+
The `regressionbot` CLI is the easiest way to interact with the API from your terminal or CI scripts.
|
|
80
|
+
|
|
81
|
+
### Authentication
|
|
82
|
+
|
|
83
|
+
The CLI looks for the following environment variables:
|
|
84
|
+
- `REGRESSIONBOT_API_KEY`: Your project API key.
|
|
85
|
+
- `REGRESSIONBOT_API_URL`: (Optional) Override the default API endpoint.
|
|
86
|
+
|
|
87
|
+
### Commands
|
|
88
|
+
|
|
89
|
+
#### 1. Quick Check
|
|
90
|
+
Test a single URL against its established baseline.
|
|
91
|
+
```bash
|
|
92
|
+
npx regressionbot https://example.com --project my-site --on "Desktop Chrome, iPhone 12"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### 2. Sitemap Scan
|
|
96
|
+
Test an entire site using glob patterns.
|
|
97
|
+
```bash
|
|
98
|
+
npx regressionbot https://example.com --scan "/**" --exclude "/admin/**" --concurrency 20
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### 3. Job Summary
|
|
102
|
+
Get detailed results and diff URLs for a completed job.
|
|
103
|
+
```bash
|
|
104
|
+
npx regressionbot summary <jobId>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Add the `--download` flag to save regression images locally:
|
|
108
|
+
```bash
|
|
109
|
+
npx regressionbot summary <jobId> --download
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### 4. Approve Changes
|
|
113
|
+
Promote the current screenshots of a job to be the new baselines.
|
|
114
|
+
```bash
|
|
115
|
+
npx regressionbot approve <jobId>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
### Versioning
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npm version patch # or minor/major
|
|
124
|
+
npm publish
|
|
125
125
|
```
|
package/dist/cli.js
CHANGED
|
@@ -59,36 +59,36 @@ async function main() {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
catch (error) {
|
|
62
|
-
console.error(`
|
|
62
|
+
console.error(`
|
|
63
63
|
Error: ${error.message}`);
|
|
64
64
|
process.exit(1);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
function showHelp() {
|
|
68
|
-
console.log(`
|
|
69
|
-
RegressionBot CLI
|
|
70
|
-
|
|
71
|
-
Usage:
|
|
72
|
-
npx regressionbot <url> Quick test a URL.
|
|
73
|
-
npx regressionbot status <jobId> Check the status of a specific job.
|
|
74
|
-
npx regressionbot summary <jobId> Get detailed results and diff URLs.
|
|
75
|
-
Use --download to save images locally.
|
|
76
|
-
npx regressionbot approve <jobId> Approve a job's results as new baselines.
|
|
77
|
-
|
|
78
|
-
Options for <url>:
|
|
79
|
-
--project <id> Required project ID.
|
|
80
|
-
--against <url> Base origin to compare against.
|
|
81
|
-
--sitemap <url> Explicit sitemap.xml location.
|
|
82
|
-
--on <devices> Comma-separated device names (e.g. "Desktop Chrome,iPhone 12").
|
|
83
|
-
--scan <pattern> Glob pattern to scan in sitemap (e.g. "/blog/**").
|
|
84
|
-
--exclude <patterns> Comma-separated glob patterns to exclude.
|
|
85
|
-
--concurrency <n> Max concurrent workers (default 10).
|
|
86
|
-
--auto-approve Automatically approve results as new baselines.
|
|
87
|
-
--mask <selectors> Comma-separated CSS selectors to hide (e.g. ".ad,#popup").
|
|
88
|
-
|
|
89
|
-
Environment Variables:
|
|
90
|
-
REGRESSIONBOT_API_KEY Override the API Key.
|
|
91
|
-
REGRESSIONBOT_API_URL Override the API URL.
|
|
68
|
+
console.log(`
|
|
69
|
+
RegressionBot CLI
|
|
70
|
+
|
|
71
|
+
Usage:
|
|
72
|
+
npx regressionbot <url> Quick test a URL.
|
|
73
|
+
npx regressionbot status <jobId> Check the status of a specific job.
|
|
74
|
+
npx regressionbot summary <jobId> Get detailed results and diff URLs.
|
|
75
|
+
Use --download to save images locally.
|
|
76
|
+
npx regressionbot approve <jobId> Approve a job's results as new baselines.
|
|
77
|
+
|
|
78
|
+
Options for <url>:
|
|
79
|
+
--project <id> Required project ID.
|
|
80
|
+
--against <url> Base origin to compare against.
|
|
81
|
+
--sitemap <url> Explicit sitemap.xml location.
|
|
82
|
+
--on <devices> Comma-separated device names (e.g. "Desktop Chrome,iPhone 12").
|
|
83
|
+
--scan <pattern> Glob pattern to scan in sitemap (e.g. "/blog/**").
|
|
84
|
+
--exclude <patterns> Comma-separated glob patterns to exclude.
|
|
85
|
+
--concurrency <n> Max concurrent workers (default 10).
|
|
86
|
+
--auto-approve Automatically approve results as new baselines.
|
|
87
|
+
--mask <selectors> Comma-separated CSS selectors to hide (e.g. ".ad,#popup").
|
|
88
|
+
|
|
89
|
+
Environment Variables:
|
|
90
|
+
REGRESSIONBOT_API_KEY Override the API Key.
|
|
91
|
+
REGRESSIONBOT_API_URL Override the API URL.
|
|
92
92
|
`);
|
|
93
93
|
}
|
|
94
94
|
async function startJob(url, options) {
|
|
@@ -123,8 +123,8 @@ async function startJob(url, options) {
|
|
|
123
123
|
if (options.scan) {
|
|
124
124
|
console.log(`🔍 Scan: ${options.scan} (Exclude: ${options.exclude || 'none'})`);
|
|
125
125
|
}
|
|
126
|
-
console.log(`
|
|
127
|
-
Waiting for completion...
|
|
126
|
+
console.log(`
|
|
127
|
+
Waiting for completion...
|
|
128
128
|
`);
|
|
129
129
|
const result = await job.waitForCompletion(2000, (status) => {
|
|
130
130
|
const progress = status.progress || { percent: '0' };
|
|
@@ -168,25 +168,32 @@ async function checkStatus(jobId) {
|
|
|
168
168
|
const status = await job.getStatus();
|
|
169
169
|
console.log(JSON.stringify(status, null, 2));
|
|
170
170
|
}
|
|
171
|
+
function sanitizeFilename(name) {
|
|
172
|
+
if (!name)
|
|
173
|
+
return 'unknown';
|
|
174
|
+
// Allow alphanumeric, underscore, hyphen, space.
|
|
175
|
+
return name.replace(/[^a-zA-Z0-9_\- ]/g, '_');
|
|
176
|
+
}
|
|
171
177
|
async function showSummary(jobId, options = {}) {
|
|
172
178
|
const job = sdk.job(jobId);
|
|
173
179
|
const summary = await job.getSummary();
|
|
174
|
-
console.log(`
|
|
175
|
-
Job Summary: ${jobId}
|
|
176
|
-
Status: ${summary.status}
|
|
177
|
-
Overall Score: ${summary.overallScore}/100
|
|
178
|
-
Execution Time: ${summary.executionTime}s
|
|
179
|
-
Total Tasks: ${summary.totalUrls}
|
|
180
|
-
Regressions: ${summary.regressionCount}
|
|
181
|
-
Matches: ${summary.matchCount}
|
|
182
|
-
Errors: ${summary.errorCount}
|
|
180
|
+
console.log(`
|
|
181
|
+
Job Summary: ${jobId}
|
|
182
|
+
Status: ${summary.status}
|
|
183
|
+
Overall Score: ${summary.overallScore}/100
|
|
184
|
+
Execution Time: ${summary.executionTime}s
|
|
185
|
+
Total Tasks: ${summary.totalUrls}
|
|
186
|
+
Regressions: ${summary.regressionCount}
|
|
187
|
+
Matches: ${summary.matchCount}
|
|
188
|
+
Errors: ${summary.errorCount}
|
|
183
189
|
`);
|
|
184
190
|
if (summary.collageUrl) {
|
|
185
191
|
console.log(`Collage: ${summary.collageUrl}`);
|
|
186
192
|
if (options.download) {
|
|
187
193
|
const fs = require('fs');
|
|
188
194
|
const path = require('path');
|
|
189
|
-
const
|
|
195
|
+
const safeJobId = sanitizeFilename(jobId);
|
|
196
|
+
const dir = path.join(process.cwd(), 'regressions', safeJobId);
|
|
190
197
|
if (!fs.existsSync(dir))
|
|
191
198
|
fs.mkdirSync(dir, { recursive: true });
|
|
192
199
|
const res = await fetch(summary.collageUrl);
|
|
@@ -204,7 +211,9 @@ Errors: ${summary.errorCount}
|
|
|
204
211
|
if (options.download) {
|
|
205
212
|
const fs = require('fs');
|
|
206
213
|
const path = require('path');
|
|
207
|
-
const
|
|
214
|
+
const safeJobId = sanitizeFilename(jobId);
|
|
215
|
+
const safeVariantName = sanitizeFilename(r.variantName);
|
|
216
|
+
const dir = path.join(process.cwd(), 'regressions', safeJobId, safeVariantName);
|
|
208
217
|
if (!fs.existsSync(dir))
|
|
209
218
|
fs.mkdirSync(dir, { recursive: true });
|
|
210
219
|
const download = async (url, name) => {
|
package/dist/index.js
CHANGED
|
@@ -139,10 +139,10 @@ class JobHandle {
|
|
|
139
139
|
this.jobId = jobId;
|
|
140
140
|
}
|
|
141
141
|
async getStatus() {
|
|
142
|
-
return this.sdk._request(`/job/${this.jobId}`);
|
|
142
|
+
return this.sdk._request(`/job/${encodeURIComponent(this.jobId)}`);
|
|
143
143
|
}
|
|
144
144
|
async getSummary() {
|
|
145
|
-
return this.sdk._request(`/job/${this.jobId}/summary`);
|
|
145
|
+
return this.sdk._request(`/job/${encodeURIComponent(this.jobId)}/summary`);
|
|
146
146
|
}
|
|
147
147
|
async approve() {
|
|
148
148
|
return this.sdk._request('/approve', 'POST', { jobId: this.jobId });
|
package/package.json
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "regressionbot",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "The official SDK for regressionbot.com - the simplest way to automate visual regression testing.",
|
|
5
|
-
"homepage": "https://regressionbot.com",
|
|
6
|
-
"repository": {
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/cbestall/regressionbot-sdk.git"
|
|
9
|
-
},
|
|
10
|
-
"bugs": {
|
|
11
|
-
"url": "https://github.com/cbestall/regressionbot-sdk/issues"
|
|
12
|
-
},
|
|
13
|
-
"main": "dist/index.js",
|
|
14
|
-
"types": "dist/index.d.ts",
|
|
15
|
-
"bin": {
|
|
16
|
-
"regressionbot": "dist/cli.js"
|
|
17
|
-
},
|
|
18
|
-
"files": [
|
|
19
|
-
"dist"
|
|
20
|
-
],
|
|
21
|
-
"scripts": {
|
|
22
|
-
"build": "tsc",
|
|
23
|
-
"dev": "tsc -w",
|
|
24
|
-
"prepublishOnly": "npm run build"
|
|
25
|
-
},
|
|
26
|
-
"keywords": [
|
|
27
|
-
"visual-regression",
|
|
28
|
-
"testing",
|
|
29
|
-
"sdk",
|
|
30
|
-
"cli"
|
|
31
|
-
],
|
|
32
|
-
"author": "RegressionBot (https://regressionbot.com)",
|
|
33
|
-
"license": "ISC",
|
|
34
|
-
"devDependencies": {
|
|
35
|
-
"@types/node": "^20.0.0",
|
|
36
|
-
"typescript": "^5.0.0"
|
|
37
|
-
}
|
|
38
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "regressionbot",
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"description": "The official SDK for regressionbot.com - the simplest way to automate visual regression testing.",
|
|
5
|
+
"homepage": "https://regressionbot.com",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/cbestall/regressionbot-sdk.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/cbestall/regressionbot-sdk/issues"
|
|
12
|
+
},
|
|
13
|
+
"main": "dist/index.js",
|
|
14
|
+
"types": "dist/index.d.ts",
|
|
15
|
+
"bin": {
|
|
16
|
+
"regressionbot": "dist/cli.js"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"dev": "tsc -w",
|
|
24
|
+
"prepublishOnly": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"visual-regression",
|
|
28
|
+
"testing",
|
|
29
|
+
"sdk",
|
|
30
|
+
"cli"
|
|
31
|
+
],
|
|
32
|
+
"author": "RegressionBot (https://regressionbot.com)",
|
|
33
|
+
"license": "ISC",
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.0.0",
|
|
36
|
+
"typescript": "^5.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|