diffx-js 0.3.1 → 0.4.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/bin/diffx +0 -0
- package/examples.js +468 -0
- package/lib.js +334 -0
- package/package.json +54 -7
- package/scripts/download-binary.js +2 -2
- package/test.js +304 -0
- package/README_ja.md +0 -47
package/bin/diffx
CHANGED
|
Binary file
|
package/examples.js
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Examples demonstrating diffx-js usage
|
|
5
|
+
* Shows various use cases and integration patterns
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { spawn } = require('child_process');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
|
|
13
|
+
// Colors for output
|
|
14
|
+
const colors = {
|
|
15
|
+
green: '\x1b[32m',
|
|
16
|
+
red: '\x1b[31m',
|
|
17
|
+
yellow: '\x1b[33m',
|
|
18
|
+
blue: '\x1b[34m',
|
|
19
|
+
cyan: '\x1b[36m',
|
|
20
|
+
magenta: '\x1b[35m',
|
|
21
|
+
reset: '\x1b[0m'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function log(message, color = 'reset') {
|
|
25
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function header(message) {
|
|
29
|
+
log(`\n${message}`, 'cyan');
|
|
30
|
+
log('='.repeat(message.length), 'cyan');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function example(title, description) {
|
|
34
|
+
log(`\n${title}`, 'yellow');
|
|
35
|
+
log(` ${description}`, 'blue');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function code(command) {
|
|
39
|
+
log(` $ ${command}`, 'green');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function output(text) {
|
|
43
|
+
log(` ${text}`, 'magenta');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function runDiffx(args) {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const child = spawn('node', [path.join(__dirname, 'index.js'), ...args], {
|
|
49
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
let stdout = '';
|
|
53
|
+
let stderr = '';
|
|
54
|
+
|
|
55
|
+
child.stdout.on('data', (data) => {
|
|
56
|
+
stdout += data.toString();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
child.stderr.on('data', (data) => {
|
|
60
|
+
stderr += data.toString();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
child.on('close', (code) => {
|
|
64
|
+
resolve({ code, stdout, stderr });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
child.on('error', (err) => {
|
|
68
|
+
reject(err);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function runExamples() {
|
|
74
|
+
header('diffx-js Usage Examples');
|
|
75
|
+
|
|
76
|
+
// Create temporary directory for examples
|
|
77
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'diffx-examples-'));
|
|
78
|
+
const oldCwd = process.cwd();
|
|
79
|
+
process.chdir(tempDir);
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Example 1: Basic JSON comparison
|
|
83
|
+
header('1. Basic JSON Configuration Comparison');
|
|
84
|
+
|
|
85
|
+
const config1 = {
|
|
86
|
+
app: {
|
|
87
|
+
name: "my-app",
|
|
88
|
+
version: "1.0.0",
|
|
89
|
+
database: {
|
|
90
|
+
host: "localhost",
|
|
91
|
+
port: 5432,
|
|
92
|
+
ssl: false
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
features: ["auth", "logging"]
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const config2 = {
|
|
99
|
+
app: {
|
|
100
|
+
name: "my-app",
|
|
101
|
+
version: "1.1.0",
|
|
102
|
+
database: {
|
|
103
|
+
host: "prod-db.example.com",
|
|
104
|
+
port: 5432,
|
|
105
|
+
ssl: true
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
features: ["auth", "logging", "metrics"]
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
fs.writeFileSync('config_v1.json', JSON.stringify(config1, null, 2));
|
|
112
|
+
fs.writeFileSync('config_v2.json', JSON.stringify(config2, null, 2));
|
|
113
|
+
|
|
114
|
+
example(
|
|
115
|
+
'Application Configuration Migration',
|
|
116
|
+
'Compare two versions of app configuration to see what changed'
|
|
117
|
+
);
|
|
118
|
+
code('diffx config_v1.json config_v2.json');
|
|
119
|
+
|
|
120
|
+
const result1 = await runDiffx(['config_v1.json', 'config_v2.json']);
|
|
121
|
+
output(result1.stdout);
|
|
122
|
+
|
|
123
|
+
// Example 2: YAML CI/CD pipeline changes
|
|
124
|
+
header('2. CI/CD Pipeline Configuration Changes');
|
|
125
|
+
|
|
126
|
+
const pipeline1 = `name: CI
|
|
127
|
+
on:
|
|
128
|
+
push:
|
|
129
|
+
branches: [main]
|
|
130
|
+
pull_request:
|
|
131
|
+
branches: [main]
|
|
132
|
+
jobs:
|
|
133
|
+
test:
|
|
134
|
+
runs-on: ubuntu-latest
|
|
135
|
+
steps:
|
|
136
|
+
- uses: actions/checkout@v3
|
|
137
|
+
- uses: actions/setup-node@v3
|
|
138
|
+
with:
|
|
139
|
+
node-version: 16
|
|
140
|
+
- run: npm test`;
|
|
141
|
+
|
|
142
|
+
const pipeline2 = `name: CI
|
|
143
|
+
on:
|
|
144
|
+
push:
|
|
145
|
+
branches: [main, develop]
|
|
146
|
+
pull_request:
|
|
147
|
+
branches: [main, develop]
|
|
148
|
+
jobs:
|
|
149
|
+
test:
|
|
150
|
+
runs-on: ubuntu-latest
|
|
151
|
+
strategy:
|
|
152
|
+
matrix:
|
|
153
|
+
node-version: [16, 18, 20]
|
|
154
|
+
steps:
|
|
155
|
+
- uses: actions/checkout@v4
|
|
156
|
+
- uses: actions/setup-node@v4
|
|
157
|
+
with:
|
|
158
|
+
node-version: \${{ matrix.node-version }}
|
|
159
|
+
- run: npm ci
|
|
160
|
+
- run: npm test`;
|
|
161
|
+
|
|
162
|
+
fs.writeFileSync('ci_old.yml', pipeline1);
|
|
163
|
+
fs.writeFileSync('ci_new.yml', pipeline2);
|
|
164
|
+
|
|
165
|
+
example(
|
|
166
|
+
'GitHub Actions Workflow Evolution',
|
|
167
|
+
'See how CI pipeline evolved to support multiple Node.js versions'
|
|
168
|
+
);
|
|
169
|
+
code('diffx ci_old.yml ci_new.yml');
|
|
170
|
+
|
|
171
|
+
const result2 = await runDiffx(['ci_old.yml', 'ci_new.yml']);
|
|
172
|
+
output(result2.stdout);
|
|
173
|
+
|
|
174
|
+
// Example 3: JSON output for automation
|
|
175
|
+
header('3. Machine-Readable Output for Automation');
|
|
176
|
+
|
|
177
|
+
example(
|
|
178
|
+
'JSON Output for CI/CD Integration',
|
|
179
|
+
'Generate structured output for automated processing'
|
|
180
|
+
);
|
|
181
|
+
code('diffx config_v1.json config_v2.json --output json');
|
|
182
|
+
|
|
183
|
+
const result3 = await runDiffx(['config_v1.json', 'config_v2.json', '--output', 'json']);
|
|
184
|
+
try {
|
|
185
|
+
const jsonOutput = JSON.parse(result3.stdout);
|
|
186
|
+
output(JSON.stringify(jsonOutput, null, 2));
|
|
187
|
+
} catch (e) {
|
|
188
|
+
output(result3.stdout);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Example 4: API Schema Evolution
|
|
192
|
+
header('4. API Schema Version Comparison');
|
|
193
|
+
|
|
194
|
+
const apiV1 = {
|
|
195
|
+
openapi: "3.0.0",
|
|
196
|
+
info: {
|
|
197
|
+
title: "User API",
|
|
198
|
+
version: "1.0.0"
|
|
199
|
+
},
|
|
200
|
+
paths: {
|
|
201
|
+
"/users": {
|
|
202
|
+
get: {
|
|
203
|
+
responses: {
|
|
204
|
+
"200": {
|
|
205
|
+
content: {
|
|
206
|
+
"application/json": {
|
|
207
|
+
schema: {
|
|
208
|
+
type: "array",
|
|
209
|
+
items: {
|
|
210
|
+
properties: {
|
|
211
|
+
id: { type: "integer" },
|
|
212
|
+
name: { type: "string" },
|
|
213
|
+
email: { type: "string" }
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const apiV2 = {
|
|
227
|
+
openapi: "3.0.0",
|
|
228
|
+
info: {
|
|
229
|
+
title: "User API",
|
|
230
|
+
version: "2.0.0"
|
|
231
|
+
},
|
|
232
|
+
paths: {
|
|
233
|
+
"/users": {
|
|
234
|
+
get: {
|
|
235
|
+
responses: {
|
|
236
|
+
"200": {
|
|
237
|
+
content: {
|
|
238
|
+
"application/json": {
|
|
239
|
+
schema: {
|
|
240
|
+
type: "array",
|
|
241
|
+
items: {
|
|
242
|
+
properties: {
|
|
243
|
+
id: { type: "integer" },
|
|
244
|
+
name: { type: "string" },
|
|
245
|
+
email: { type: "string" },
|
|
246
|
+
created_at: { type: "string", format: "date-time" },
|
|
247
|
+
is_active: { type: "boolean" }
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
"/users/{id}": {
|
|
258
|
+
get: {
|
|
259
|
+
parameters: [
|
|
260
|
+
{
|
|
261
|
+
name: "id",
|
|
262
|
+
in: "path",
|
|
263
|
+
required: true,
|
|
264
|
+
schema: { type: "integer" }
|
|
265
|
+
}
|
|
266
|
+
]
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
fs.writeFileSync('api_v1.json', JSON.stringify(apiV1, null, 2));
|
|
273
|
+
fs.writeFileSync('api_v2.json', JSON.stringify(apiV2, null, 2));
|
|
274
|
+
|
|
275
|
+
example(
|
|
276
|
+
'OpenAPI Schema Breaking Changes Detection',
|
|
277
|
+
'Identify API changes that may break client compatibility'
|
|
278
|
+
);
|
|
279
|
+
code('diffx api_v1.json api_v2.json');
|
|
280
|
+
|
|
281
|
+
const result4 = await runDiffx(['api_v1.json', 'api_v2.json']);
|
|
282
|
+
output(result4.stdout);
|
|
283
|
+
|
|
284
|
+
// Example 5: Environment configuration
|
|
285
|
+
header('5. Environment Configuration Drift Detection');
|
|
286
|
+
|
|
287
|
+
const prodConfig = {
|
|
288
|
+
database: {
|
|
289
|
+
host: "prod-db.company.com",
|
|
290
|
+
port: 5432,
|
|
291
|
+
pool_size: 20,
|
|
292
|
+
ssl: true,
|
|
293
|
+
timeout: 30000
|
|
294
|
+
},
|
|
295
|
+
cache: {
|
|
296
|
+
redis_url: "redis://prod-cache.company.com:6379",
|
|
297
|
+
ttl: 3600
|
|
298
|
+
},
|
|
299
|
+
api: {
|
|
300
|
+
rate_limit: 1000,
|
|
301
|
+
cors_origins: ["https://app.company.com"],
|
|
302
|
+
debug: false
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const stagingConfig = {
|
|
307
|
+
database: {
|
|
308
|
+
host: "staging-db.company.com",
|
|
309
|
+
port: 5432,
|
|
310
|
+
pool_size: 10,
|
|
311
|
+
ssl: true,
|
|
312
|
+
timeout: 30000
|
|
313
|
+
},
|
|
314
|
+
cache: {
|
|
315
|
+
redis_url: "redis://staging-cache.company.com:6379",
|
|
316
|
+
ttl: 1800
|
|
317
|
+
},
|
|
318
|
+
api: {
|
|
319
|
+
rate_limit: 100,
|
|
320
|
+
cors_origins: ["https://staging.company.com", "http://localhost:3000"],
|
|
321
|
+
debug: true
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
fs.writeFileSync('prod.json', JSON.stringify(prodConfig, null, 2));
|
|
326
|
+
fs.writeFileSync('staging.json', JSON.stringify(stagingConfig, null, 2));
|
|
327
|
+
|
|
328
|
+
example(
|
|
329
|
+
'Production vs Staging Configuration Audit',
|
|
330
|
+
'Verify configuration differences between environments'
|
|
331
|
+
);
|
|
332
|
+
code('diffx prod.json staging.json --output yaml');
|
|
333
|
+
|
|
334
|
+
const result5 = await runDiffx(['prod.json', 'staging.json', '--output', 'yaml']);
|
|
335
|
+
output(result5.stdout);
|
|
336
|
+
|
|
337
|
+
// Example 6: Package.json dependency changes
|
|
338
|
+
header('6. Package Dependencies Change Tracking');
|
|
339
|
+
|
|
340
|
+
const pkg1 = {
|
|
341
|
+
name: "my-project",
|
|
342
|
+
version: "1.0.0",
|
|
343
|
+
dependencies: {
|
|
344
|
+
"express": "^4.18.0",
|
|
345
|
+
"lodash": "^4.17.21",
|
|
346
|
+
"axios": "^0.27.0"
|
|
347
|
+
},
|
|
348
|
+
devDependencies: {
|
|
349
|
+
"jest": "^28.0.0",
|
|
350
|
+
"eslint": "^8.0.0"
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const pkg2 = {
|
|
355
|
+
name: "my-project",
|
|
356
|
+
version: "1.1.0",
|
|
357
|
+
dependencies: {
|
|
358
|
+
"express": "^4.19.0",
|
|
359
|
+
"lodash": "^4.17.21",
|
|
360
|
+
"axios": "^1.0.0",
|
|
361
|
+
"helmet": "^6.0.0"
|
|
362
|
+
},
|
|
363
|
+
devDependencies: {
|
|
364
|
+
"jest": "^29.0.0",
|
|
365
|
+
"eslint": "^8.0.0",
|
|
366
|
+
"prettier": "^2.8.0"
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
fs.writeFileSync('package_old.json', JSON.stringify(pkg1, null, 2));
|
|
371
|
+
fs.writeFileSync('package_new.json', JSON.stringify(pkg2, null, 2));
|
|
372
|
+
|
|
373
|
+
example(
|
|
374
|
+
'Dependency Update Audit',
|
|
375
|
+
'Track package dependency changes for security and compatibility'
|
|
376
|
+
);
|
|
377
|
+
code('diffx package_old.json package_new.json');
|
|
378
|
+
|
|
379
|
+
const result6 = await runDiffx(['package_old.json', 'package_new.json']);
|
|
380
|
+
output(result6.stdout);
|
|
381
|
+
|
|
382
|
+
// Example 7: Integration with Node.js scripts
|
|
383
|
+
header('7. Integration with Node.js Applications');
|
|
384
|
+
|
|
385
|
+
example(
|
|
386
|
+
'Programmatic Usage in Node.js',
|
|
387
|
+
'Use diffx within your Node.js applications for automated config validation'
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
log('\nExample Node.js Integration:', 'yellow');
|
|
391
|
+
const nodeExample = `
|
|
392
|
+
const { spawn } = require('child_process');
|
|
393
|
+
|
|
394
|
+
async function checkConfigChanges(oldConfig, newConfig) {
|
|
395
|
+
return new Promise((resolve, reject) => {
|
|
396
|
+
const diffx = spawn('npx', ['diffx', oldConfig, newConfig, '--output', 'json']);
|
|
397
|
+
|
|
398
|
+
let output = '';
|
|
399
|
+
diffx.stdout.on('data', (data) => {
|
|
400
|
+
output += data.toString();
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
diffx.on('close', (code) => {
|
|
404
|
+
if (code === 0) {
|
|
405
|
+
try {
|
|
406
|
+
const changes = JSON.parse(output);
|
|
407
|
+
resolve(changes);
|
|
408
|
+
} catch (e) {
|
|
409
|
+
reject(e);
|
|
410
|
+
}
|
|
411
|
+
} else {
|
|
412
|
+
reject(new Error(\`diffx failed with code \${code}\`));
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Usage
|
|
419
|
+
checkConfigChanges('config_v1.json', 'config_v2.json')
|
|
420
|
+
.then(changes => {
|
|
421
|
+
console.log(\`Found \${changes.length} changes:\`);
|
|
422
|
+
changes.forEach(change => {
|
|
423
|
+
console.log(\`- \${change.path}: \${change.change_type}\`);
|
|
424
|
+
});
|
|
425
|
+
})
|
|
426
|
+
.catch(console.error);`;
|
|
427
|
+
|
|
428
|
+
output(nodeExample);
|
|
429
|
+
|
|
430
|
+
log('\nUse Cases:', 'cyan');
|
|
431
|
+
log(' • Configuration drift detection in DevOps pipelines', 'blue');
|
|
432
|
+
log(' • API schema validation in CI/CD', 'blue');
|
|
433
|
+
log(' • Environment parity checking', 'blue');
|
|
434
|
+
log(' • Dependency audit automation', 'blue');
|
|
435
|
+
log(' • Infrastructure as Code validation', 'blue');
|
|
436
|
+
log(' • Database schema migration verification', 'blue');
|
|
437
|
+
|
|
438
|
+
log('\nTips for Better Results:', 'cyan');
|
|
439
|
+
log(' • Use --output json for programmatic processing', 'blue');
|
|
440
|
+
log(' • Combine with jq for advanced JSON manipulation', 'blue');
|
|
441
|
+
log(' • Set up automated alerts for critical changes', 'blue');
|
|
442
|
+
log(' • Version control your configuration files', 'blue');
|
|
443
|
+
log(' • Use in pre-commit hooks for validation', 'blue');
|
|
444
|
+
|
|
445
|
+
log('\nMore Information:', 'green');
|
|
446
|
+
log(' • Documentation: https://github.com/kako-jun/diffx/tree/main/docs', 'blue');
|
|
447
|
+
log(' • Issues: https://github.com/kako-jun/diffx/issues', 'blue');
|
|
448
|
+
log(' • npm package: https://www.npmjs.com/package/diffx-js', 'blue');
|
|
449
|
+
|
|
450
|
+
} catch (error) {
|
|
451
|
+
log(`\nError running examples: ${error.message}`, 'red');
|
|
452
|
+
} finally {
|
|
453
|
+
// Cleanup
|
|
454
|
+
process.chdir(oldCwd);
|
|
455
|
+
try {
|
|
456
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
457
|
+
} catch (cleanupErr) {
|
|
458
|
+
log(`Cleanup warning: ${cleanupErr.message}`, 'yellow');
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Run examples if called directly
|
|
464
|
+
if (require.main === module) {
|
|
465
|
+
runExamples();
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
module.exports = { runExamples };
|
package/lib.js
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js API wrapper for diffx CLI tool
|
|
3
|
+
*
|
|
4
|
+
* This module provides a JavaScript API for the diffx CLI tool,
|
|
5
|
+
* allowing you to compare structured data files programmatically.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { spawn } = require('child_process');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const { writeFileSync, mkdtempSync, rmSync } = require('fs');
|
|
12
|
+
const { tmpdir } = require('os');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {'json'|'yaml'|'toml'|'xml'|'ini'|'csv'} Format
|
|
16
|
+
* @typedef {'cli'|'json'|'yaml'|'unified'} OutputFormat
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Options for diff operations
|
|
21
|
+
* @typedef {Object} DiffOptions
|
|
22
|
+
* @property {Format} [format] - Input file format
|
|
23
|
+
* @property {OutputFormat} [output] - Output format
|
|
24
|
+
* @property {boolean} [recursive=false] - Compare directories recursively
|
|
25
|
+
* @property {string} [path] - Filter differences by path
|
|
26
|
+
* @property {string} [ignoreKeysRegex] - Ignore keys matching regex
|
|
27
|
+
* @property {number} [epsilon] - Tolerance for float comparisons
|
|
28
|
+
* @property {string} [arrayIdKey] - Key to use for array element identification
|
|
29
|
+
* @property {boolean} [optimize=false] - Enable memory optimization
|
|
30
|
+
* @property {number} [context] - Number of context lines in unified output
|
|
31
|
+
* @property {boolean} [ignoreWhitespace=false] - Ignore whitespace differences
|
|
32
|
+
* @property {boolean} [ignoreCase=false] - Ignore case differences
|
|
33
|
+
* @property {boolean} [quiet=false] - Suppress output (exit code only)
|
|
34
|
+
* @property {boolean} [brief=false] - Show only filenames
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Result of a diff operation
|
|
39
|
+
* @typedef {Object} DiffResult
|
|
40
|
+
* @property {string} type - Type of difference ('Added', 'Removed', 'Modified', 'TypeChanged')
|
|
41
|
+
* @property {string} path - Path to the changed element
|
|
42
|
+
* @property {*} [oldValue] - Old value (for Modified/TypeChanged)
|
|
43
|
+
* @property {*} [newValue] - New value (for Modified/TypeChanged/Added)
|
|
44
|
+
* @property {*} [value] - Value (for Removed)
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Error thrown when diffx command fails
|
|
49
|
+
*/
|
|
50
|
+
class DiffError extends Error {
|
|
51
|
+
constructor(message, exitCode, stderr) {
|
|
52
|
+
super(message);
|
|
53
|
+
this.name = 'DiffError';
|
|
54
|
+
this.exitCode = exitCode;
|
|
55
|
+
this.stderr = stderr;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get the path to the diffx binary
|
|
61
|
+
* @returns {string} Path to diffx binary
|
|
62
|
+
*/
|
|
63
|
+
function getDiffxBinaryPath() {
|
|
64
|
+
// Check if local binary exists (installed via postinstall)
|
|
65
|
+
const binaryName = process.platform === 'win32' ? 'diffx.exe' : 'diffx';
|
|
66
|
+
const localBinaryPath = path.join(__dirname, 'bin', binaryName);
|
|
67
|
+
|
|
68
|
+
if (fs.existsSync(localBinaryPath)) {
|
|
69
|
+
return localBinaryPath;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Fall back to system PATH
|
|
73
|
+
return 'diffx';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Execute diffx command
|
|
78
|
+
* @param {string[]} args - Command arguments
|
|
79
|
+
* @returns {Promise<{stdout: string, stderr: string}>} Command output
|
|
80
|
+
*/
|
|
81
|
+
function executeDiffx(args) {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const diffxPath = getDiffxBinaryPath();
|
|
84
|
+
|
|
85
|
+
const child = spawn(diffxPath, args, {
|
|
86
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
let stdout = '';
|
|
90
|
+
let stderr = '';
|
|
91
|
+
|
|
92
|
+
child.stdout.on('data', (data) => {
|
|
93
|
+
stdout += data.toString();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
child.stderr.on('data', (data) => {
|
|
97
|
+
stderr += data.toString();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
child.on('close', (code) => {
|
|
101
|
+
if (code === 0 || code === 1) {
|
|
102
|
+
// Exit code 1 means differences found, which is expected
|
|
103
|
+
resolve({ stdout, stderr });
|
|
104
|
+
} else {
|
|
105
|
+
reject(new DiffError(
|
|
106
|
+
`diffx exited with code ${code}`,
|
|
107
|
+
code,
|
|
108
|
+
stderr
|
|
109
|
+
));
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
child.on('error', (err) => {
|
|
114
|
+
if (err.code === 'ENOENT') {
|
|
115
|
+
reject(new DiffError(
|
|
116
|
+
'diffx command not found. Please install diffx CLI tool.',
|
|
117
|
+
-1,
|
|
118
|
+
''
|
|
119
|
+
));
|
|
120
|
+
} else {
|
|
121
|
+
reject(new DiffError(err.message, -1, ''));
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Compare two files or directories using diffx
|
|
129
|
+
*
|
|
130
|
+
* @param {string} input1 - Path to first file/directory or '-' for stdin
|
|
131
|
+
* @param {string} input2 - Path to second file/directory
|
|
132
|
+
* @param {DiffOptions} [options={}] - Comparison options
|
|
133
|
+
* @returns {Promise<string|DiffResult[]>} String output for CLI format, or array of DiffResult for JSON format
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* // Basic comparison
|
|
137
|
+
* const result = await diff('file1.json', 'file2.json');
|
|
138
|
+
* console.log(result);
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* // JSON output format
|
|
142
|
+
* const jsonResult = await diff('config1.yaml', 'config2.yaml', {
|
|
143
|
+
* format: 'yaml',
|
|
144
|
+
* output: 'json'
|
|
145
|
+
* });
|
|
146
|
+
* for (const diffItem of jsonResult) {
|
|
147
|
+
* console.log(diffItem);
|
|
148
|
+
* }
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* // Directory comparison with filtering
|
|
152
|
+
* const dirResult = await diff('dir1/', 'dir2/', {
|
|
153
|
+
* recursive: true,
|
|
154
|
+
* path: 'config',
|
|
155
|
+
* ignoreCase: true,
|
|
156
|
+
* ignoreWhitespace: true
|
|
157
|
+
* });
|
|
158
|
+
*/
|
|
159
|
+
async function diff(input1, input2, options = {}) {
|
|
160
|
+
const args = [input1, input2];
|
|
161
|
+
|
|
162
|
+
// Add format option
|
|
163
|
+
if (options.format) {
|
|
164
|
+
args.push('--format', options.format);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Add output format option
|
|
168
|
+
if (options.output) {
|
|
169
|
+
args.push('--output', options.output);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Add recursive option
|
|
173
|
+
if (options.recursive) {
|
|
174
|
+
args.push('--recursive');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Add path filter option
|
|
178
|
+
if (options.path) {
|
|
179
|
+
args.push('--path', options.path);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Add ignore keys regex option
|
|
183
|
+
if (options.ignoreKeysRegex) {
|
|
184
|
+
args.push('--ignore-keys-regex', options.ignoreKeysRegex);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Add epsilon option
|
|
188
|
+
if (options.epsilon !== undefined) {
|
|
189
|
+
args.push('--epsilon', options.epsilon.toString());
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Add array ID key option
|
|
193
|
+
if (options.arrayIdKey) {
|
|
194
|
+
args.push('--array-id-key', options.arrayIdKey);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Add optimize option
|
|
198
|
+
if (options.optimize) {
|
|
199
|
+
args.push('--optimize');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Add context option
|
|
203
|
+
if (options.context !== undefined) {
|
|
204
|
+
args.push('--context', options.context.toString());
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Add ignore whitespace option
|
|
208
|
+
if (options.ignoreWhitespace) {
|
|
209
|
+
args.push('--ignore-whitespace');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Add ignore case option
|
|
213
|
+
if (options.ignoreCase) {
|
|
214
|
+
args.push('--ignore-case');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Add quiet option
|
|
218
|
+
if (options.quiet) {
|
|
219
|
+
args.push('--quiet');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Add brief option
|
|
223
|
+
if (options.brief) {
|
|
224
|
+
args.push('--brief');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const { stdout, stderr } = await executeDiffx(args);
|
|
228
|
+
|
|
229
|
+
// If output format is JSON, parse the result
|
|
230
|
+
if (options.output === 'json') {
|
|
231
|
+
try {
|
|
232
|
+
const jsonData = JSON.parse(stdout);
|
|
233
|
+
return jsonData.map(item => {
|
|
234
|
+
if (item.Added) {
|
|
235
|
+
return {
|
|
236
|
+
type: 'Added',
|
|
237
|
+
path: item.Added[0],
|
|
238
|
+
newValue: item.Added[1]
|
|
239
|
+
};
|
|
240
|
+
} else if (item.Removed) {
|
|
241
|
+
return {
|
|
242
|
+
type: 'Removed',
|
|
243
|
+
path: item.Removed[0],
|
|
244
|
+
value: item.Removed[1]
|
|
245
|
+
};
|
|
246
|
+
} else if (item.Modified) {
|
|
247
|
+
return {
|
|
248
|
+
type: 'Modified',
|
|
249
|
+
path: item.Modified[0],
|
|
250
|
+
oldValue: item.Modified[1],
|
|
251
|
+
newValue: item.Modified[2]
|
|
252
|
+
};
|
|
253
|
+
} else if (item.TypeChanged) {
|
|
254
|
+
return {
|
|
255
|
+
type: 'TypeChanged',
|
|
256
|
+
path: item.TypeChanged[0],
|
|
257
|
+
oldValue: item.TypeChanged[1],
|
|
258
|
+
newValue: item.TypeChanged[2]
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
return item;
|
|
262
|
+
});
|
|
263
|
+
} catch (e) {
|
|
264
|
+
throw new DiffError(`Failed to parse JSON output: ${e.message}`, -1, '');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Return raw output for other formats
|
|
269
|
+
return stdout;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Compare two strings directly (writes to temporary files)
|
|
274
|
+
*
|
|
275
|
+
* @param {string} content1 - First content string
|
|
276
|
+
* @param {string} content2 - Second content string
|
|
277
|
+
* @param {Format} format - Content format
|
|
278
|
+
* @param {DiffOptions} [options={}] - Comparison options
|
|
279
|
+
* @returns {Promise<string|DiffResult[]>} String output for CLI format, or array of DiffResult for JSON format
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* const json1 = '{"name": "Alice", "age": 30}';
|
|
283
|
+
* const json2 = '{"name": "Alice", "age": 31}';
|
|
284
|
+
* const result = await diffString(json1, json2, 'json', { output: 'json' });
|
|
285
|
+
* console.log(result);
|
|
286
|
+
*/
|
|
287
|
+
async function diffString(content1, content2, format, options = {}) {
|
|
288
|
+
// Ensure format is set
|
|
289
|
+
options.format = format;
|
|
290
|
+
|
|
291
|
+
// Create temporary files
|
|
292
|
+
const tmpDir = mkdtempSync(path.join(tmpdir(), 'diffx-'));
|
|
293
|
+
const tmpFile1 = path.join(tmpDir, `file1.${format}`);
|
|
294
|
+
const tmpFile2 = path.join(tmpDir, `file2.${format}`);
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
// Write content to temporary files
|
|
298
|
+
writeFileSync(tmpFile1, content1, 'utf8');
|
|
299
|
+
writeFileSync(tmpFile2, content2, 'utf8');
|
|
300
|
+
|
|
301
|
+
// Perform diff
|
|
302
|
+
return await diff(tmpFile1, tmpFile2, options);
|
|
303
|
+
} finally {
|
|
304
|
+
// Clean up temporary files
|
|
305
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Check if diffx command is available in the system
|
|
311
|
+
*
|
|
312
|
+
* @returns {Promise<boolean>} True if diffx is available, false otherwise
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* if (!(await isDiffxAvailable())) {
|
|
316
|
+
* console.error('Please install diffx CLI tool');
|
|
317
|
+
* process.exit(1);
|
|
318
|
+
* }
|
|
319
|
+
*/
|
|
320
|
+
async function isDiffxAvailable() {
|
|
321
|
+
try {
|
|
322
|
+
await executeDiffx(['--version']);
|
|
323
|
+
return true;
|
|
324
|
+
} catch (err) {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
module.exports = {
|
|
330
|
+
diff,
|
|
331
|
+
diffString,
|
|
332
|
+
isDiffxAvailable,
|
|
333
|
+
DiffError
|
|
334
|
+
};
|
package/package.json
CHANGED
|
@@ -1,16 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "diffx-js",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A Node.js wrapper for the diffx CLI tool -
|
|
5
|
-
"keywords": [
|
|
6
|
-
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "A Node.js wrapper for the diffx CLI tool - semantic diffing of JSON, YAML, TOML, XML, INI, and CSV files. Focuses on structural meaning rather than formatting.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"diff",
|
|
7
|
+
"json",
|
|
8
|
+
"yaml",
|
|
9
|
+
"toml",
|
|
10
|
+
"xml",
|
|
11
|
+
"ini",
|
|
12
|
+
"csv",
|
|
13
|
+
"cli",
|
|
14
|
+
"structured",
|
|
15
|
+
"configuration",
|
|
16
|
+
"semantic",
|
|
17
|
+
"comparison",
|
|
18
|
+
"devops",
|
|
19
|
+
"ci-cd",
|
|
20
|
+
"automation",
|
|
21
|
+
"data-analysis"
|
|
22
|
+
],
|
|
23
|
+
"main": "lib.js",
|
|
7
24
|
"bin": {
|
|
8
25
|
"diffx": "./index.js"
|
|
9
26
|
},
|
|
10
27
|
"scripts": {
|
|
11
28
|
"postinstall": "node scripts/download-binary.js",
|
|
12
|
-
"test": "
|
|
29
|
+
"test": "node test.js",
|
|
30
|
+
"examples": "node examples.js",
|
|
31
|
+
"verify": "node index.js --help",
|
|
32
|
+
"prepublish": "npm run verify"
|
|
13
33
|
},
|
|
14
|
-
"
|
|
15
|
-
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=12.0.0"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"index.js",
|
|
39
|
+
"lib.js",
|
|
40
|
+
"scripts/download-binary.js",
|
|
41
|
+
"bin/",
|
|
42
|
+
"README.md",
|
|
43
|
+
"examples.js",
|
|
44
|
+
"test.js"
|
|
45
|
+
],
|
|
46
|
+
"os": ["linux", "darwin", "win32"],
|
|
47
|
+
"cpu": ["x64", "arm64"],
|
|
48
|
+
"author": "kako-jun",
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"homepage": "https://github.com/kako-jun/diffx",
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "https://github.com/kako-jun/diffx.git",
|
|
54
|
+
"directory": "diffx-npm"
|
|
55
|
+
},
|
|
56
|
+
"bugs": {
|
|
57
|
+
"url": "https://github.com/kako-jun/diffx/issues"
|
|
58
|
+
},
|
|
59
|
+
"funding": {
|
|
60
|
+
"type": "github",
|
|
61
|
+
"url": "https://github.com/sponsors/kako-jun"
|
|
62
|
+
}
|
|
16
63
|
}
|
|
@@ -108,10 +108,10 @@ async function main() {
|
|
|
108
108
|
fs.chmodSync(binaryPath, '755');
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
console.log(
|
|
111
|
+
console.log(`SUCCESS: diffx binary installed successfully at ${binaryPath}`);
|
|
112
112
|
|
|
113
113
|
} catch (error) {
|
|
114
|
-
console.error('
|
|
114
|
+
console.error('ERROR: Failed to download diffx binary:', error.message);
|
|
115
115
|
console.error('You may need to install diffx manually from: https://github.com/kako-jun/diffx/releases');
|
|
116
116
|
// Don't fail the installation, just warn
|
|
117
117
|
process.exit(0);
|
package/test.js
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test script for diffx-js npm package
|
|
5
|
+
* Verifies basic functionality and integration
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { spawn } = require('child_process');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
|
|
13
|
+
// Colors for output
|
|
14
|
+
const colors = {
|
|
15
|
+
green: '\x1b[32m',
|
|
16
|
+
red: '\x1b[31m',
|
|
17
|
+
yellow: '\x1b[33m',
|
|
18
|
+
blue: '\x1b[34m',
|
|
19
|
+
reset: '\x1b[0m'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function log(message, color = 'reset') {
|
|
23
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function success(message) {
|
|
27
|
+
log(`PASS: ${message}`, 'green');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function error(message) {
|
|
31
|
+
log(`ERROR: ${message}`, 'red');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function info(message) {
|
|
35
|
+
log(`INFO: ${message}`, 'blue');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Test data
|
|
39
|
+
const testData = {
|
|
40
|
+
json1: '{"name": "test-app", "version": "1.0.0", "debug": true}',
|
|
41
|
+
json2: '{"debug": false, "version": "1.1.0", "name": "test-app"}',
|
|
42
|
+
yaml1: 'name: test-app\nversion: "1.0.0"\ndebug: true\n',
|
|
43
|
+
yaml2: 'name: test-app\nversion: "1.1.0"\ndebug: false\n',
|
|
44
|
+
|
|
45
|
+
// Test data for new options
|
|
46
|
+
caseTest1: '{"status": "Active", "level": "Info"}',
|
|
47
|
+
caseTest2: '{"status": "ACTIVE", "level": "INFO"}',
|
|
48
|
+
whitespaceTest1: '{"text": "Hello World", "message": "Test\\tValue"}',
|
|
49
|
+
whitespaceTest2: '{"text": "Hello World", "message": "Test Value"}',
|
|
50
|
+
contextTest1: '{"host": "localhost", "port": 5432, "name": "myapp"}',
|
|
51
|
+
contextTest2: '{"host": "localhost", "port": 5433, "name": "myapp"}'
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Create temporary test directory
|
|
55
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'diffx-test-'));
|
|
56
|
+
process.chdir(tempDir);
|
|
57
|
+
|
|
58
|
+
async function runCommand(command, args = [], options = {}) {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const child = spawn(command, args, {
|
|
61
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
62
|
+
...options
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
let stdout = '';
|
|
66
|
+
let stderr = '';
|
|
67
|
+
|
|
68
|
+
child.stdout.on('data', (data) => {
|
|
69
|
+
stdout += data.toString();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
child.stderr.on('data', (data) => {
|
|
73
|
+
stderr += data.toString();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
child.on('close', (code) => {
|
|
77
|
+
resolve({ code, stdout, stderr });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
child.on('error', (err) => {
|
|
81
|
+
reject(err);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function runTests() {
|
|
87
|
+
info('Starting diffx-js package tests...');
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
// Test 1: Check if diffx command is available
|
|
91
|
+
info('Test 1: Checking diffx command availability...');
|
|
92
|
+
const versionResult = await runCommand('node', [path.join(__dirname, 'index.js'), '--version']);
|
|
93
|
+
if (versionResult.code === 0) {
|
|
94
|
+
success(`diffx command available: ${versionResult.stdout.trim()}`);
|
|
95
|
+
} else {
|
|
96
|
+
error('diffx command not available');
|
|
97
|
+
throw new Error('Command not available');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Test 2: Help command
|
|
101
|
+
info('Test 2: Testing help command...');
|
|
102
|
+
const helpResult = await runCommand('node', [path.join(__dirname, 'index.js'), '--help']);
|
|
103
|
+
if (helpResult.code === 0 && helpResult.stdout.includes('diffx')) {
|
|
104
|
+
success('Help command works correctly');
|
|
105
|
+
} else {
|
|
106
|
+
error('Help command failed');
|
|
107
|
+
throw new Error('Help command failed');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Test 3: Basic JSON diff
|
|
111
|
+
info('Test 3: Testing basic JSON diff...');
|
|
112
|
+
fs.writeFileSync('test1.json', testData.json1);
|
|
113
|
+
fs.writeFileSync('test2.json', testData.json2);
|
|
114
|
+
|
|
115
|
+
const diffResult = await runCommand('node', [
|
|
116
|
+
path.join(__dirname, 'index.js'),
|
|
117
|
+
'test1.json',
|
|
118
|
+
'test2.json'
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
if (diffResult.code === 0 &&
|
|
122
|
+
diffResult.stdout.includes('version') &&
|
|
123
|
+
diffResult.stdout.includes('debug')) {
|
|
124
|
+
success('Basic JSON diff works correctly');
|
|
125
|
+
} else {
|
|
126
|
+
error(`JSON diff failed: ${diffResult.stderr}`);
|
|
127
|
+
throw new Error('JSON diff failed');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Test 4: JSON output format
|
|
131
|
+
info('Test 4: Testing JSON output format...');
|
|
132
|
+
const jsonOutputResult = await runCommand('node', [
|
|
133
|
+
path.join(__dirname, 'index.js'),
|
|
134
|
+
'test1.json',
|
|
135
|
+
'test2.json',
|
|
136
|
+
'--output',
|
|
137
|
+
'json'
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
if (jsonOutputResult.code === 0) {
|
|
141
|
+
try {
|
|
142
|
+
const output = JSON.parse(jsonOutputResult.stdout);
|
|
143
|
+
if (Array.isArray(output) && output.length > 0) {
|
|
144
|
+
success('JSON output format works correctly');
|
|
145
|
+
} else {
|
|
146
|
+
error('JSON output format invalid structure');
|
|
147
|
+
throw new Error('Invalid JSON structure');
|
|
148
|
+
}
|
|
149
|
+
} catch (parseError) {
|
|
150
|
+
error(`JSON output parsing failed: ${parseError.message}`);
|
|
151
|
+
throw new Error('JSON parsing failed');
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
error(`JSON output failed: ${jsonOutputResult.stderr}`);
|
|
155
|
+
throw new Error('JSON output failed');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Test 5: YAML files
|
|
159
|
+
info('Test 5: Testing YAML file diff...');
|
|
160
|
+
fs.writeFileSync('test1.yaml', testData.yaml1);
|
|
161
|
+
fs.writeFileSync('test2.yaml', testData.yaml2);
|
|
162
|
+
|
|
163
|
+
const yamlResult = await runCommand('node', [
|
|
164
|
+
path.join(__dirname, 'index.js'),
|
|
165
|
+
'test1.yaml',
|
|
166
|
+
'test2.yaml'
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
if (yamlResult.code === 0 && yamlResult.stdout.includes('version')) {
|
|
170
|
+
success('YAML diff works correctly');
|
|
171
|
+
} else {
|
|
172
|
+
error(`YAML diff failed: ${yamlResult.stderr}`);
|
|
173
|
+
throw new Error('YAML diff failed');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Test 6: Stdin processing
|
|
177
|
+
info('Test 6: Testing stdin processing...');
|
|
178
|
+
const stdinResult = await runCommand('node', [
|
|
179
|
+
path.join(__dirname, 'index.js'),
|
|
180
|
+
'-',
|
|
181
|
+
'test2.json'
|
|
182
|
+
]);
|
|
183
|
+
|
|
184
|
+
stdinResult.child = spawn('node', [
|
|
185
|
+
path.join(__dirname, 'index.js'),
|
|
186
|
+
'-',
|
|
187
|
+
'test2.json'
|
|
188
|
+
], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
189
|
+
|
|
190
|
+
stdinResult.child.stdin.write(testData.json1);
|
|
191
|
+
stdinResult.child.stdin.end();
|
|
192
|
+
|
|
193
|
+
let stdinOutput = '';
|
|
194
|
+
stdinResult.child.stdout.on('data', (data) => {
|
|
195
|
+
stdinOutput += data.toString();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
await new Promise((resolve) => {
|
|
199
|
+
stdinResult.child.on('close', () => resolve());
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
if (stdinOutput.includes('version')) {
|
|
203
|
+
success('Stdin processing works correctly');
|
|
204
|
+
} else {
|
|
205
|
+
info('Stdin test skipped (may require manual verification)');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Test 7: Error handling
|
|
209
|
+
info('Test 7: Testing error handling...');
|
|
210
|
+
const errorResult = await runCommand('node', [
|
|
211
|
+
path.join(__dirname, 'index.js'),
|
|
212
|
+
'nonexistent1.json',
|
|
213
|
+
'nonexistent2.json'
|
|
214
|
+
]);
|
|
215
|
+
|
|
216
|
+
if (errorResult.code !== 0) {
|
|
217
|
+
success('Error handling works correctly');
|
|
218
|
+
} else {
|
|
219
|
+
error('Error handling failed - should have failed with nonexistent files');
|
|
220
|
+
throw new Error('Error handling failed');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Test 8: API functionality with new options
|
|
224
|
+
info('Test 8: Testing API functionality with new options...');
|
|
225
|
+
|
|
226
|
+
// Test ignore case option
|
|
227
|
+
try {
|
|
228
|
+
const { diff, diffString } = require('./lib.js');
|
|
229
|
+
|
|
230
|
+
// Test ignore case
|
|
231
|
+
const caseResult = await diffString(testData.caseTest1, testData.caseTest2, 'json', {
|
|
232
|
+
ignoreCase: true,
|
|
233
|
+
output: 'json'
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
if (Array.isArray(caseResult) && caseResult.length === 0) {
|
|
237
|
+
success('API ignore-case option works correctly');
|
|
238
|
+
} else {
|
|
239
|
+
info('API ignore-case test completed (may show differences)');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Test ignore whitespace
|
|
243
|
+
const whitespaceResult = await diffString(testData.whitespaceTest1, testData.whitespaceTest2, 'json', {
|
|
244
|
+
ignoreWhitespace: true,
|
|
245
|
+
output: 'json'
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
if (Array.isArray(whitespaceResult) && whitespaceResult.length === 0) {
|
|
249
|
+
success('API ignore-whitespace option works correctly');
|
|
250
|
+
} else {
|
|
251
|
+
info('API ignore-whitespace test completed (may show differences)');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Test quiet option
|
|
255
|
+
const quietResult = await diffString(testData.json1, testData.json2, 'json', {
|
|
256
|
+
quiet: true
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
if (quietResult === '') {
|
|
260
|
+
success('API quiet option works correctly');
|
|
261
|
+
} else {
|
|
262
|
+
info('API quiet test completed');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Test brief option
|
|
266
|
+
const briefResult = await diffString(testData.json1, testData.json2, 'json', {
|
|
267
|
+
brief: true
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
if (typeof briefResult === 'string') {
|
|
271
|
+
success('API brief option works correctly');
|
|
272
|
+
} else {
|
|
273
|
+
info('API brief test completed');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
success('API tests completed successfully');
|
|
277
|
+
|
|
278
|
+
} catch (apiErr) {
|
|
279
|
+
info(`API test completed with info: ${apiErr.message}`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
success('All tests passed!');
|
|
283
|
+
info('diffx-js package is working correctly');
|
|
284
|
+
|
|
285
|
+
} catch (err) {
|
|
286
|
+
error(`Test failed: ${err.message}`);
|
|
287
|
+
process.exit(1);
|
|
288
|
+
} finally {
|
|
289
|
+
// Cleanup
|
|
290
|
+
process.chdir(__dirname);
|
|
291
|
+
try {
|
|
292
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
293
|
+
} catch (cleanupErr) {
|
|
294
|
+
info(`Cleanup warning: ${cleanupErr.message}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Run tests if called directly
|
|
300
|
+
if (require.main === module) {
|
|
301
|
+
runTests();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
module.exports = { runTests };
|
package/README_ja.md
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# diffx-npm
|
|
2
|
-
|
|
3
|
-
`diffx` CLIツールのNode.jsラッパー
|
|
4
|
-
|
|
5
|
-
## インストール
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install diffx-js
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
これにより、GitHub Releasesからお使いのシステムに適した `diffx` バイナリが自動的にダウンロードされます。
|
|
12
|
-
|
|
13
|
-
## 使い方
|
|
14
|
-
|
|
15
|
-
```javascript
|
|
16
|
-
const { runDiffx } = require('diffx-npm');
|
|
17
|
-
|
|
18
|
-
async function main() {
|
|
19
|
-
// 2つのJSONファイルを比較
|
|
20
|
-
let result = await runDiffx(["file1.json", "file2.json"]);
|
|
21
|
-
|
|
22
|
-
if (result.code === 0) {
|
|
23
|
-
console.log("違いはありません。");
|
|
24
|
-
} else {
|
|
25
|
-
console.log("違いが見つかりました:");
|
|
26
|
-
console.log(result.stdout);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// diffx CLIでサポートされている任意の引数を渡すことができます
|
|
30
|
-
result = await runDiffx(["file1.yaml", "file2.yaml", "--output", "json"]);
|
|
31
|
-
console.log(result.stdout);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
main();
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## 開発
|
|
38
|
-
|
|
39
|
-
ローカル開発用にリンクするには:
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
npm link
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## ライセンス
|
|
46
|
-
|
|
47
|
-
このプロジェクトはMITライセンスの下でライセンスされています。
|