kiroo 0.7.4 โ 0.8.0
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 +39 -4
- package/bin/kiroo.js +23 -2
- package/package.json +3 -1
- package/src/edit.js +85 -0
- package/src/export.js +93 -0
- package/src/lingo.js +36 -0
- package/src/snapshot.js +82 -17
package/README.md
CHANGED
|
@@ -51,12 +51,13 @@ Coming from a browser? Don't type a single header.
|
|
|
51
51
|
|
|
52
52
|
## โจ Features that WOW
|
|
53
53
|
|
|
54
|
-
### ๐ข **Git-Native
|
|
55
|
-
Capture a **Snapshot** of your entire API state and compare versions
|
|
54
|
+
### ๐ข **Git-Native Diffing & Translating**
|
|
55
|
+
Capture a **Snapshot** of your entire API state and compare versions.
|
|
56
|
+
- **Deep Structural Diffs**: Recursively tracks nested schema changes and silent datatype overrides.
|
|
57
|
+
- **Lingo.dev Translation**: Instantly localize breaking change alerts natively in your terminal.
|
|
56
58
|
```bash
|
|
57
59
|
kiroo snapshot save v1-stable
|
|
58
|
-
|
|
59
|
-
kiroo snapshot compare v1-stable current
|
|
60
|
+
kiroo --lang hi snapshot compare v1-stable current
|
|
60
61
|
```
|
|
61
62
|
|
|
62
63
|
### ๐ **Variable Chaining**
|
|
@@ -74,6 +75,20 @@ kiroo post /api/user -d "name=Yash email=yash@kiroo.io role=admin"
|
|
|
74
75
|
|
|
75
76
|
---
|
|
76
77
|
|
|
78
|
+
### ๐งช **Zero-Code Testing Framework**
|
|
79
|
+
Turn your terminal into an automated test runner. Validate responses on the fly without writing a single line of JS.
|
|
80
|
+
```bash
|
|
81
|
+
kiroo check /api/login -m POST -d "user=yash pass=123" --status 200 --has token
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### ๐ **Local Load Benchmarking**
|
|
85
|
+
Stress test endpoints instantly. Simulates massive concurrency and environment-variable-injected workloads to locate latency limits.
|
|
86
|
+
```bash
|
|
87
|
+
kiroo bench /api/reports -n 1000 -c 50 -v
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
77
92
|
## ๐ Quick Start
|
|
78
93
|
|
|
79
94
|
### 1. Installation
|
|
@@ -141,6 +156,16 @@ Re-run a specific interaction.
|
|
|
141
156
|
kiroo replay 2026-03-10T14-30-05-123Z
|
|
142
157
|
```
|
|
143
158
|
|
|
159
|
+
### `kiroo edit <id>`
|
|
160
|
+
Quick Refinement. Edit an interaction on the fly and replay it.
|
|
161
|
+
- **Description**: Opens the stored interaction JSON in your default system editor (VS Code, Nano, Vim, etc.). Edit the headers, body, or URL, save, and close. Kiroo immediately replays the updated request.
|
|
162
|
+
- **Arguments**:
|
|
163
|
+
- `id`: The timestamp ID of the interaction.
|
|
164
|
+
- **Example**:
|
|
165
|
+
```bash
|
|
166
|
+
kiroo edit 2026-03-10T14-30-05-123Z
|
|
167
|
+
```
|
|
168
|
+
|
|
144
169
|
### `kiroo check <url>`
|
|
145
170
|
Zero-Code Testing engine.
|
|
146
171
|
- **Description**: Executes a request and runs assertions on the response. Exits with code 1 on failure.
|
|
@@ -217,6 +242,16 @@ Snapshot management.
|
|
|
217
242
|
kiroo snapshot compare v1.stable current
|
|
218
243
|
```
|
|
219
244
|
|
|
245
|
+
### `kiroo export`
|
|
246
|
+
Team Compatibility. Export to Postman.
|
|
247
|
+
- **Description**: Converts all stored Kiroo interactions and responses into a standard Postman Collection v2.1.0 format (`.json`) for seamless GUI import.
|
|
248
|
+
- **Options**:
|
|
249
|
+
- `-o, --out <filename>`: The output filename (Default: `kiroo-collection.json`).
|
|
250
|
+
- **Example**:
|
|
251
|
+
```bash
|
|
252
|
+
kiroo export --out my_api_collection.json
|
|
253
|
+
```
|
|
254
|
+
|
|
220
255
|
### `kiroo env`
|
|
221
256
|
Environment & Variable management.
|
|
222
257
|
- **Commands**:
|
package/bin/kiroo.js
CHANGED
|
@@ -13,13 +13,16 @@ import { showStats } from '../src/stats.js';
|
|
|
13
13
|
import { handleImport } from '../src/import.js';
|
|
14
14
|
import { clearAllInteractions } from '../src/storage.js';
|
|
15
15
|
import { runBenchmark } from '../src/bench.js';
|
|
16
|
+
import { editInteraction } from '../src/edit.js';
|
|
17
|
+
import { exportToPostman } from '../src/export.js';
|
|
16
18
|
|
|
17
19
|
const program = new Command();
|
|
18
20
|
|
|
19
21
|
program
|
|
20
22
|
.name('kiroo')
|
|
21
23
|
.description('Git for API interactions. Record, replay, snapshot, and diff your APIs.')
|
|
22
|
-
.version('0.
|
|
24
|
+
.version('0.8.0')
|
|
25
|
+
.option('--lang <language>', 'Translate output to specified language (e.g., hi, es, fr)');
|
|
23
26
|
|
|
24
27
|
// Init command
|
|
25
28
|
program
|
|
@@ -115,6 +118,23 @@ program
|
|
|
115
118
|
await replayInteraction(id);
|
|
116
119
|
});
|
|
117
120
|
|
|
121
|
+
// Edit interaction
|
|
122
|
+
program
|
|
123
|
+
.command('edit <id>')
|
|
124
|
+
.description('Edit an interaction in your text editor and quickly replay it')
|
|
125
|
+
.action(async (id) => {
|
|
126
|
+
await editInteraction(id);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Export interactions
|
|
130
|
+
program
|
|
131
|
+
.command('export')
|
|
132
|
+
.description('Export all stored interactions to a Postman Collection')
|
|
133
|
+
.option('-o, --out <filename>', 'Output JSON filename', 'kiroo-collection.json')
|
|
134
|
+
.action((options) => {
|
|
135
|
+
exportToPostman(options.out);
|
|
136
|
+
});
|
|
137
|
+
|
|
118
138
|
// Bench command (Load Testing)
|
|
119
139
|
program
|
|
120
140
|
.command('bench <url>')
|
|
@@ -195,7 +215,8 @@ snapshot
|
|
|
195
215
|
.command('compare <tag1> <tag2>')
|
|
196
216
|
.description('Compare two snapshots')
|
|
197
217
|
.action(async (tag1, tag2) => {
|
|
198
|
-
|
|
218
|
+
const opts = program.opts();
|
|
219
|
+
await compareSnapshots(tag1, tag2, opts.lang);
|
|
199
220
|
});
|
|
200
221
|
|
|
201
222
|
// Graph command
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kiroo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Git for API interactions. Record, replay, snapshot, and diff your APIs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,8 +41,10 @@
|
|
|
41
41
|
"chalk": "^5.3.0",
|
|
42
42
|
"cli-table3": "^0.6.3",
|
|
43
43
|
"commander": "^12.0.0",
|
|
44
|
+
"dotenv": "^17.3.1",
|
|
44
45
|
"inquirer": "^9.2.15",
|
|
45
46
|
"js-yaml": "^4.1.0",
|
|
47
|
+
"lingo.dev": "^0.133.1",
|
|
46
48
|
"ora": "^8.0.1"
|
|
47
49
|
},
|
|
48
50
|
"engines": {
|
package/src/edit.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { loadInteraction } from './storage.js';
|
|
6
|
+
import { replayInteraction } from './replay.js';
|
|
7
|
+
|
|
8
|
+
export async function editInteraction(id) {
|
|
9
|
+
try {
|
|
10
|
+
const interaction = loadInteraction(id);
|
|
11
|
+
if (!interaction) {
|
|
12
|
+
console.log(chalk.red(`\n โ Interaction not found: ${id}\n`));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Step 2: Generate temporary editable JSON
|
|
17
|
+
const tmpFileName = `.kiroo/tmp_edit_${id}.json`;
|
|
18
|
+
const editableData = {
|
|
19
|
+
method: interaction.request.method,
|
|
20
|
+
url: interaction.request.url,
|
|
21
|
+
headers: interaction.request.headers || {},
|
|
22
|
+
data: interaction.request.body || null
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
writeFileSync(tmpFileName, JSON.stringify(editableData, null, 2));
|
|
26
|
+
|
|
27
|
+
// Step 3: Launch Editor
|
|
28
|
+
const editor = process.env.EDITOR || 'code'; // Fallbacks: code, nano, vim, notepad...
|
|
29
|
+
|
|
30
|
+
console.log(chalk.cyan(`\n ๐ Opening interaction in your editor (${editor})...`));
|
|
31
|
+
console.log(chalk.gray(` Save and close the file to automatically replay the request.`));
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// In windows, 'code -w' waits for VS Code to close.
|
|
35
|
+
// If notepad, just 'notepad' blocks until closed.
|
|
36
|
+
// If using fallback, we try standard blocking call.
|
|
37
|
+
const launchCmd = editor === 'code' ? 'code -w' : editor;
|
|
38
|
+
execSync(`${launchCmd} ${tmpFileName}`, { stdio: 'inherit' });
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.log(chalk.red(`\n โ Failed to open editor '${editor}'.`));
|
|
41
|
+
console.log(chalk.gray(` Please specify a valid editor via EDITOR environment variable (e.g. EDITOR=nano kiroo edit).`));
|
|
42
|
+
try { unlinkSync(tmpFileName); } catch(err){}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Step 4: Editor closed, read updated data
|
|
47
|
+
let updatedDataStr;
|
|
48
|
+
try {
|
|
49
|
+
updatedDataStr = readFileSync(tmpFileName, 'utf-8');
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.log(chalk.red('\n โ Could not read the edited file. Editing cancelled.\n'));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let updatedData;
|
|
56
|
+
try {
|
|
57
|
+
updatedData = JSON.parse(updatedDataStr);
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.log(chalk.red('\n โ Invalid JSON syntax in edited file. Editing cancelled.\n'));
|
|
60
|
+
try { unlinkSync(tmpFileName); } catch(err){}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Step 5: Merge and Rewrite
|
|
65
|
+
interaction.request.method = updatedData.method;
|
|
66
|
+
interaction.request.url = updatedData.url;
|
|
67
|
+
interaction.request.headers = updatedData.headers;
|
|
68
|
+
interaction.request.body = updatedData.data;
|
|
69
|
+
|
|
70
|
+
// Save it over the original file
|
|
71
|
+
const originalPath = join('.kiroo', 'interactions', `${id}.json`);
|
|
72
|
+
writeFileSync(originalPath, JSON.stringify(interaction, null, 2));
|
|
73
|
+
|
|
74
|
+
// Cleanup tmp file
|
|
75
|
+
try { unlinkSync(tmpFileName); } catch(err){}
|
|
76
|
+
|
|
77
|
+
console.log(chalk.green(`\n โ
Interaction updated. Replaying now...\n`));
|
|
78
|
+
|
|
79
|
+
// Step 6: Trigger replay
|
|
80
|
+
await replayInteraction(id);
|
|
81
|
+
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error(chalk.red(`\n โ Edit Failed: ${error.message}\n`));
|
|
84
|
+
}
|
|
85
|
+
}
|
package/src/export.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { getAllInteractions } from './storage.js';
|
|
5
|
+
|
|
6
|
+
export function exportToPostman(outFileName) {
|
|
7
|
+
try {
|
|
8
|
+
const interactions = getAllInteractions();
|
|
9
|
+
|
|
10
|
+
if (interactions.length === 0) {
|
|
11
|
+
console.log(chalk.yellow('\n โ ๏ธ No interactions found to export.'));
|
|
12
|
+
console.log(chalk.gray(' Run some requests first before exporting.\n'));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const postmanCollection = {
|
|
17
|
+
info: {
|
|
18
|
+
name: `Kiroo Export - ${new Date().toISOString().split('T')[0]}`,
|
|
19
|
+
schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
|
20
|
+
},
|
|
21
|
+
item: interactions.map(int => {
|
|
22
|
+
// Map Headers
|
|
23
|
+
const headerList = Object.entries(int.request.headers || {}).map(([key, value]) => ({
|
|
24
|
+
key,
|
|
25
|
+
value: value.toString(),
|
|
26
|
+
type: "text"
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
// Format body
|
|
30
|
+
let rawBody = '';
|
|
31
|
+
if (int.request.body) {
|
|
32
|
+
rawBody = typeof int.request.body === 'object'
|
|
33
|
+
? JSON.stringify(int.request.body, null, 2)
|
|
34
|
+
: int.request.body.toString();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Response Body
|
|
38
|
+
let resBodyStr = '';
|
|
39
|
+
if (int.response.body) {
|
|
40
|
+
resBodyStr = typeof int.response.body === 'object'
|
|
41
|
+
? JSON.stringify(int.response.body, null, 2)
|
|
42
|
+
: int.response.body.toString();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
name: `[${int.request.method}] ${int.request.url}`,
|
|
47
|
+
request: {
|
|
48
|
+
method: int.request.method.toUpperCase(),
|
|
49
|
+
header: headerList,
|
|
50
|
+
url: {
|
|
51
|
+
raw: int.request.url
|
|
52
|
+
},
|
|
53
|
+
...(rawBody ? {
|
|
54
|
+
body: {
|
|
55
|
+
mode: "raw",
|
|
56
|
+
raw: rawBody,
|
|
57
|
+
options: {
|
|
58
|
+
raw: { language: "json" }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} : {})
|
|
62
|
+
},
|
|
63
|
+
response: [
|
|
64
|
+
{
|
|
65
|
+
name: "Saved Example from Kiroo",
|
|
66
|
+
originalRequest: {
|
|
67
|
+
method: int.request.method.toUpperCase(),
|
|
68
|
+
header: headerList,
|
|
69
|
+
url: { raw: int.request.url }
|
|
70
|
+
},
|
|
71
|
+
status: "Saved Response",
|
|
72
|
+
code: int.response.status,
|
|
73
|
+
_postman_previewlanguage: "json",
|
|
74
|
+
header: [],
|
|
75
|
+
cookie: [],
|
|
76
|
+
body: resBodyStr
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
};
|
|
80
|
+
})
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const outputPath = join(process.cwd(), outFileName);
|
|
84
|
+
writeFileSync(outputPath, JSON.stringify(postmanCollection, null, 2));
|
|
85
|
+
|
|
86
|
+
console.log(chalk.green(`\n โ
Collection exported successfully!`));
|
|
87
|
+
console.log(chalk.gray(` Saved to: ${outputPath}`));
|
|
88
|
+
console.log(chalk.magenta(` You can now import this file directly into Postman/Insomnia.\n`));
|
|
89
|
+
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error(chalk.red('\n โ Export failed:'), error.message, '\n');
|
|
92
|
+
}
|
|
93
|
+
}
|
package/src/lingo.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { LingoDotDevEngine } from "lingo.dev/sdk";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { loadEnv } from "./storage.js";
|
|
4
|
+
|
|
5
|
+
function getLingoEngine() {
|
|
6
|
+
const envData = loadEnv();
|
|
7
|
+
const currentEnvVars = envData.environments[envData.current] || {};
|
|
8
|
+
|
|
9
|
+
// Prioritize process.env, fallback to kiroo environments
|
|
10
|
+
const apiKey = currentEnvVars.LINGODOTDEV_API_KEY;
|
|
11
|
+
|
|
12
|
+
if (!apiKey) {
|
|
13
|
+
console.log(chalk.yellow(`\n โ ๏ธ LINGODOTDEV_API_KEY not found.`));
|
|
14
|
+
console.log(chalk.gray(`run 'kiroo env set LINGODOTDEV_API_KEY <your_key>'\n`));
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return new LingoDotDevEngine({ apiKey });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function translateText(text, targetLang) {
|
|
22
|
+
const engine = getLingoEngine();
|
|
23
|
+
if (!engine) return text;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const result = await engine.localizeText(text, {
|
|
27
|
+
sourceLocale: 'en',
|
|
28
|
+
targetLocale: targetLang,
|
|
29
|
+
fast: true
|
|
30
|
+
});
|
|
31
|
+
return result;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.log(chalk.red(`\n โ ๏ธ Translation failed: ${error.message}`));
|
|
34
|
+
return text;
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/snapshot.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import Table from 'cli-table3';
|
|
3
3
|
import { getAllInteractions, saveSnapshotData, getAllSnapshots, loadSnapshotData } from './storage.js';
|
|
4
|
+
import { translateText } from './lingo.js';
|
|
4
5
|
|
|
5
6
|
export async function saveSnapshot(tag) {
|
|
6
7
|
const interactions = getAllInteractions();
|
|
@@ -49,12 +50,15 @@ export async function listSnapshots() {
|
|
|
49
50
|
console.log('');
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
export async function compareSnapshots(tag1, tag2) {
|
|
53
|
+
export async function compareSnapshots(tag1, tag2, lang) {
|
|
53
54
|
try {
|
|
54
55
|
const s1 = loadSnapshotData(tag1);
|
|
55
56
|
const s2 = loadSnapshotData(tag2);
|
|
56
57
|
|
|
57
58
|
console.log(chalk.cyan(`\n ๐ Comparing Snapshots:`), chalk.white(tag1), chalk.gray('vs'), chalk.white(tag2));
|
|
59
|
+
if (lang) {
|
|
60
|
+
console.log(chalk.magenta(` ๐ Translating output to: ${chalk.white(lang.toUpperCase())} using Lingo.dev...`));
|
|
61
|
+
}
|
|
58
62
|
|
|
59
63
|
const results = [];
|
|
60
64
|
let breakingChanges = 0;
|
|
@@ -93,18 +97,62 @@ export async function compareSnapshots(tag1, tag2) {
|
|
|
93
97
|
breakingChanges++;
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
const keys2 = Object.keys(int2.response.body);
|
|
100
|
+
// Helper for deep structural comparison
|
|
101
|
+
const deepCompare = (val1, val2, path = '') => {
|
|
102
|
+
const changes = [];
|
|
100
103
|
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
// Handle nulls
|
|
105
|
+
if (val1 === null && val2 !== null) return [{ path, msg: `type changed from null to ${typeof val2}`, breaking: false }];
|
|
106
|
+
if (val1 !== null && val2 === null) return [{ path, msg: `type changed from ${typeof val1} to null`, breaking: false }];
|
|
107
|
+
if (val1 === null && val2 === null) return changes;
|
|
108
|
+
|
|
109
|
+
const type1 = Array.isArray(val1) ? 'array' : typeof val1;
|
|
110
|
+
const type2 = Array.isArray(val2) ? 'array' : typeof val2;
|
|
111
|
+
|
|
112
|
+
if (type1 !== type2) {
|
|
113
|
+
changes.push({ path, msg: `type changed from ${chalk.yellow(type1)} to ${chalk.yellow(type2)}`, breaking: true });
|
|
114
|
+
return changes;
|
|
105
115
|
}
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
|
|
117
|
+
if (type1 === 'object') {
|
|
118
|
+
const keys1 = Object.keys(val1);
|
|
119
|
+
const keys2 = Object.keys(val2);
|
|
120
|
+
|
|
121
|
+
// Check for removed keys (Breaking)
|
|
122
|
+
for (const k of keys1) {
|
|
123
|
+
const currentPath = path ? `${path}.${k}` : k;
|
|
124
|
+
if (!keys2.includes(k)) {
|
|
125
|
+
changes.push({ path: currentPath, msg: `was ${chalk.red('removed')}`, breaking: true });
|
|
126
|
+
} else {
|
|
127
|
+
changes.push(...deepCompare(val1[k], val2[k], currentPath));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check for added keys (Non-breaking)
|
|
132
|
+
for (const k of keys2) {
|
|
133
|
+
const currentPath = path ? `${path}.${k}` : k;
|
|
134
|
+
if (!keys1.includes(k)) {
|
|
135
|
+
changes.push({ path: currentPath, msg: `was ${chalk.green('added')}`, breaking: false });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} else if (type1 === 'array') {
|
|
139
|
+
// Array structure validation (check first item schema only if exists)
|
|
140
|
+
if (val1.length > 0 && val2.length > 0) {
|
|
141
|
+
const itemPath = path ? `${path}[0]` : '[0]';
|
|
142
|
+
changes.push(...deepCompare(val1[0], val2[0], itemPath));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return changes;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
if (int1.response.body !== undefined && int2.response.body !== undefined) {
|
|
150
|
+
const structuralChanges = deepCompare(int1.response.body, int2.response.body);
|
|
151
|
+
for (const change of structuralChanges) {
|
|
152
|
+
diffs.push(`${chalk.cyan(change.path || 'root')} ${change.msg}`);
|
|
153
|
+
if (change.breaking) breakingChanges++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
108
156
|
if (diffs.length > 0) {
|
|
109
157
|
results.push({
|
|
110
158
|
type: 'CHANGE',
|
|
@@ -116,19 +164,36 @@ export async function compareSnapshots(tag1, tag2) {
|
|
|
116
164
|
});
|
|
117
165
|
|
|
118
166
|
if (results.length === 0) {
|
|
119
|
-
|
|
167
|
+
let finalMsg = 'No differences detected. Your API is stable!';
|
|
168
|
+
if (lang) finalMsg = await translateText(finalMsg, lang);
|
|
169
|
+
console.log(chalk.green(`\n โ
${finalMsg}\n`));
|
|
120
170
|
} else {
|
|
121
171
|
console.log('');
|
|
122
|
-
|
|
172
|
+
|
|
173
|
+
for (const res of results) {
|
|
174
|
+
let printMsg = res.msg;
|
|
175
|
+
if (lang) {
|
|
176
|
+
// Basic translation hook for individual diff items (stripping ansi)
|
|
177
|
+
const cleanMsg = printMsg.replace(/\x1B\[[0-9;]*m/g, '');
|
|
178
|
+
const translatedMsg = await translateText(cleanMsg, lang);
|
|
179
|
+
printMsg = chalk.yellow('[Translated] ') + translatedMsg;
|
|
180
|
+
}
|
|
181
|
+
|
|
123
182
|
const symbol = res.type === 'NEW' ? chalk.blue('+') : chalk.yellow('โ ๏ธ');
|
|
124
183
|
console.log(` ${symbol} ${chalk.white(res.method)} ${chalk.gray(res.url)}`);
|
|
125
|
-
console.log(` ${
|
|
126
|
-
}
|
|
184
|
+
console.log(` ${printMsg}`);
|
|
185
|
+
}
|
|
127
186
|
|
|
187
|
+
let alertMsg = breakingChanges > 0
|
|
188
|
+
? `Detected ${breakingChanges} potential breaking changes!`
|
|
189
|
+
: `Non-breaking changes detected.`;
|
|
190
|
+
|
|
191
|
+
if (lang) alertMsg = await translateText(alertMsg, lang);
|
|
192
|
+
|
|
128
193
|
if (breakingChanges > 0) {
|
|
129
|
-
console.log(chalk.red(`\n ๐จ
|
|
194
|
+
console.log(chalk.red(`\n ๐จ ${alertMsg}\n`));
|
|
130
195
|
} else {
|
|
131
|
-
console.log(chalk.blue(
|
|
196
|
+
console.log(chalk.blue(`\n โน๏ธ ${alertMsg}\n`));
|
|
132
197
|
}
|
|
133
198
|
}
|
|
134
199
|
|