@dependabit/manifest 0.1.1
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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +32 -0
- package/dist/config.d.ts +27 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +79 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +43 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +191 -0
- package/dist/manifest.js.map +1 -0
- package/dist/schema.d.ts +488 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +213 -0
- package/dist/schema.js.map +1 -0
- package/dist/size-check.d.ts +39 -0
- package/dist/size-check.d.ts.map +1 -0
- package/dist/size-check.js +84 -0
- package/dist/size-check.js.map +1 -0
- package/dist/validator.d.ts +53 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +110 -0
- package/dist/validator.js.map +1 -0
- package/package.json +38 -0
- package/src/config.test.ts +266 -0
- package/src/config.ts +96 -0
- package/src/index.ts +16 -0
- package/src/manifest.test.ts +400 -0
- package/src/manifest.ts +261 -0
- package/src/schema.test.ts +293 -0
- package/src/schema.ts +265 -0
- package/src/size-check.test.ts +246 -0
- package/src/size-check.ts +124 -0
- package/src/validator.test.ts +161 -0
- package/src/validator.ts +131 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
checkManifestSize,
|
|
4
|
+
formatSize,
|
|
5
|
+
validateManifestObject,
|
|
6
|
+
estimateEntrySize,
|
|
7
|
+
canAddEntry
|
|
8
|
+
} from './size-check.js';
|
|
9
|
+
|
|
10
|
+
describe('checkManifestSize', () => {
|
|
11
|
+
it('should return ok status for small content', () => {
|
|
12
|
+
const content = 'small content';
|
|
13
|
+
const result = checkManifestSize(content);
|
|
14
|
+
|
|
15
|
+
expect(result.status).toBe('ok');
|
|
16
|
+
expect(result.message).toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should return warning status when exceeding warn threshold', () => {
|
|
20
|
+
const content = Buffer.alloc(1.5 * 1024 * 1024); // 1.5 MB
|
|
21
|
+
const result = checkManifestSize(content);
|
|
22
|
+
|
|
23
|
+
expect(result.status).toBe('warning');
|
|
24
|
+
expect(result.message).toContain('approaching limit');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should return error status when exceeding error threshold', () => {
|
|
28
|
+
const content = Buffer.alloc(11 * 1024 * 1024); // 11 MB
|
|
29
|
+
const result = checkManifestSize(content);
|
|
30
|
+
|
|
31
|
+
expect(result.status).toBe('error');
|
|
32
|
+
expect(result.message).toContain('exceeds maximum limit');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should handle exact threshold values', () => {
|
|
36
|
+
const exactWarnSize = 1 * 1024 * 1024; // Exactly 1 MB
|
|
37
|
+
const result = checkManifestSize(Buffer.alloc(exactWarnSize));
|
|
38
|
+
|
|
39
|
+
expect(result.status).toBe('warning');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should support custom thresholds', () => {
|
|
43
|
+
const content = Buffer.alloc(2.5 * 1024 * 1024); // 2.5 MB
|
|
44
|
+
const result = checkManifestSize(content, {
|
|
45
|
+
warnThreshold: 2,
|
|
46
|
+
errorThreshold: 5
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(result.status).toBe('warning');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should handle empty content', () => {
|
|
53
|
+
const result = checkManifestSize('');
|
|
54
|
+
|
|
55
|
+
expect(result.status).toBe('ok');
|
|
56
|
+
expect(result.sizeBytes).toBe(0);
|
|
57
|
+
expect(result.sizeMB).toBe(0);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should handle Buffer input', () => {
|
|
61
|
+
const buffer = Buffer.from('test content');
|
|
62
|
+
const result = checkManifestSize(buffer);
|
|
63
|
+
|
|
64
|
+
expect(result.sizeBytes).toBe(buffer.length);
|
|
65
|
+
expect(result.status).toBe('ok');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should handle string input with multi-byte characters', () => {
|
|
69
|
+
const content = '你好世界🌍'; // Multi-byte UTF-8 characters
|
|
70
|
+
const result = checkManifestSize(content);
|
|
71
|
+
|
|
72
|
+
expect(result.sizeBytes).toBe(Buffer.byteLength(content, 'utf8'));
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('formatSize', () => {
|
|
77
|
+
it('should format bytes correctly', () => {
|
|
78
|
+
expect(formatSize(512)).toBe('512 B');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should format kilobytes correctly', () => {
|
|
82
|
+
expect(formatSize(1024)).toBe('1.00 KB');
|
|
83
|
+
expect(formatSize(5 * 1024)).toBe('5.00 KB');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should format megabytes correctly', () => {
|
|
87
|
+
expect(formatSize(1024 * 1024)).toBe('1.00 MB');
|
|
88
|
+
expect(formatSize(2.5 * 1024 * 1024)).toBe('2.50 MB');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should handle zero', () => {
|
|
92
|
+
expect(formatSize(0)).toBe('0 B');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should handle large values', () => {
|
|
96
|
+
expect(formatSize(100 * 1024 * 1024)).toBe('100.00 MB');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('validateManifestObject', () => {
|
|
101
|
+
it('should validate small manifest objects', () => {
|
|
102
|
+
const manifest = { dependencies: [{ id: '1', name: 'test' }] };
|
|
103
|
+
const result = validateManifestObject(manifest);
|
|
104
|
+
|
|
105
|
+
expect(result.status).toBe('ok');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should detect large manifest objects', () => {
|
|
109
|
+
const largeDeps = Array.from({ length: 10000 }, (_, i) => ({
|
|
110
|
+
id: `dep-${i}`,
|
|
111
|
+
name: `dependency-${i}`,
|
|
112
|
+
url: `https://github.com/org/repo-${i}`,
|
|
113
|
+
description: 'A'.repeat(100)
|
|
114
|
+
}));
|
|
115
|
+
|
|
116
|
+
const manifest = { dependencies: largeDeps };
|
|
117
|
+
const result = validateManifestObject(manifest);
|
|
118
|
+
|
|
119
|
+
expect(result.status).not.toBe('ok');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should handle empty objects', () => {
|
|
123
|
+
const result = validateManifestObject({});
|
|
124
|
+
|
|
125
|
+
expect(result.status).toBe('ok');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should support custom thresholds', () => {
|
|
129
|
+
const manifest = { data: 'x'.repeat(500 * 1024) }; // ~500 KB
|
|
130
|
+
const result = validateManifestObject(manifest, {
|
|
131
|
+
warnThreshold: 0.4,
|
|
132
|
+
errorThreshold: 1
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(result.status).toBe('warning');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('estimateEntrySize', () => {
|
|
140
|
+
it('should estimate size of simple entries', () => {
|
|
141
|
+
const entry = { id: '1', name: 'test' };
|
|
142
|
+
const size = estimateEntrySize(entry);
|
|
143
|
+
|
|
144
|
+
expect(size).toBeGreaterThan(0);
|
|
145
|
+
expect(size).toBe(JSON.stringify(entry).length);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should estimate size of complex entries', () => {
|
|
149
|
+
const entry = {
|
|
150
|
+
id: '1',
|
|
151
|
+
name: 'complex-dependency',
|
|
152
|
+
metadata: {
|
|
153
|
+
version: '1.0.0',
|
|
154
|
+
tags: ['tag1', 'tag2', 'tag3'],
|
|
155
|
+
description: 'A long description with multiple words'
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const size = estimateEntrySize(entry);
|
|
160
|
+
expect(size).toBeGreaterThan(50);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should handle null', () => {
|
|
164
|
+
expect(estimateEntrySize(null)).toBe(4); // "null"
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should handle arrays', () => {
|
|
168
|
+
const entry = [1, 2, 3];
|
|
169
|
+
const size = estimateEntrySize(entry);
|
|
170
|
+
expect(size).toBe(JSON.stringify(entry).length);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('canAddEntry', () => {
|
|
175
|
+
it('should allow adding small entries to small manifests', () => {
|
|
176
|
+
const manifest = { dependencies: [] };
|
|
177
|
+
const entry = { id: '1', name: 'test' };
|
|
178
|
+
|
|
179
|
+
const result = canAddEntry(manifest, entry);
|
|
180
|
+
|
|
181
|
+
expect(result.canAdd).toBe(true);
|
|
182
|
+
expect(result.currentSize.status).toBe('ok');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should prevent adding entries when it would exceed limit', () => {
|
|
186
|
+
// Create a manifest that's already close to the limit
|
|
187
|
+
const largeDeps = Array.from({ length: 10000 }, (_, i) => ({
|
|
188
|
+
id: `dep-${i}`,
|
|
189
|
+
url: `https://example.com/${i}`,
|
|
190
|
+
description: 'A'.repeat(800) // ~800 bytes each, total ~8MB
|
|
191
|
+
}));
|
|
192
|
+
|
|
193
|
+
const manifest = { dependencies: largeDeps };
|
|
194
|
+
const newEntry = {
|
|
195
|
+
id: 'new-dep',
|
|
196
|
+
url: 'https://example.com/new',
|
|
197
|
+
description: 'B'.repeat(3000000) // Very large entry ~3MB
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const result = canAddEntry(manifest, newEntry, {
|
|
201
|
+
errorThreshold: 10
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
expect(result.canAdd).toBe(false);
|
|
205
|
+
expect(result.estimatedSize.message).toContain('exceed size limit');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should show warning status when approaching limit', () => {
|
|
209
|
+
const deps = Array.from({ length: 1000 }, (_, i) => ({
|
|
210
|
+
id: `dep-${i}`,
|
|
211
|
+
data: 'x'.repeat(900) // Each ~900 bytes
|
|
212
|
+
}));
|
|
213
|
+
|
|
214
|
+
const manifest = { dependencies: deps };
|
|
215
|
+
const entry = { id: 'new', data: 'y'.repeat(100) };
|
|
216
|
+
|
|
217
|
+
const result = canAddEntry(manifest, entry, {
|
|
218
|
+
warnThreshold: 0.8,
|
|
219
|
+
errorThreshold: 10
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(result.canAdd).toBe(true);
|
|
223
|
+
expect(result.estimatedSize.status).toBe('warning');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should handle adding entry to empty manifest', () => {
|
|
227
|
+
const manifest = {};
|
|
228
|
+
const entry = { id: '1', name: 'first' };
|
|
229
|
+
|
|
230
|
+
const result = canAddEntry(manifest, entry);
|
|
231
|
+
|
|
232
|
+
expect(result.canAdd).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should account for JSON formatting overhead', () => {
|
|
236
|
+
const manifest = { dependencies: [{ id: '1' }] };
|
|
237
|
+
const entry = { id: '2' };
|
|
238
|
+
|
|
239
|
+
const result = canAddEntry(manifest, entry);
|
|
240
|
+
|
|
241
|
+
// Estimated size should be larger than current + entry due to formatting buffer
|
|
242
|
+
expect(result.estimatedSize.sizeBytes).toBeGreaterThan(
|
|
243
|
+
result.currentSize.sizeBytes + estimateEntrySize(entry)
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifest size validation and warnings
|
|
3
|
+
* Checks manifest size and warns when approaching limits
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface SizeCheckResult {
|
|
7
|
+
sizeBytes: number;
|
|
8
|
+
sizeMB: number;
|
|
9
|
+
status: 'ok' | 'warning' | 'error';
|
|
10
|
+
message?: string | undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SizeCheckOptions {
|
|
14
|
+
warnThreshold?: number; // MB (default: 1)
|
|
15
|
+
errorThreshold?: number; // MB (default: 10)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Check manifest size and return status
|
|
20
|
+
*/
|
|
21
|
+
export function checkManifestSize(
|
|
22
|
+
content: string | Buffer,
|
|
23
|
+
options?: SizeCheckOptions
|
|
24
|
+
): SizeCheckResult {
|
|
25
|
+
const warnThreshold = (options?.warnThreshold ?? 1) * 1024 * 1024; // Convert MB to bytes
|
|
26
|
+
const errorThreshold = (options?.errorThreshold ?? 10) * 1024 * 1024;
|
|
27
|
+
|
|
28
|
+
const sizeBytes =
|
|
29
|
+
typeof content === 'string' ? Buffer.byteLength(content, 'utf8') : content.length;
|
|
30
|
+
|
|
31
|
+
const sizeMB = sizeBytes / (1024 * 1024);
|
|
32
|
+
|
|
33
|
+
if (sizeBytes >= errorThreshold) {
|
|
34
|
+
return {
|
|
35
|
+
sizeBytes,
|
|
36
|
+
sizeMB,
|
|
37
|
+
status: 'error',
|
|
38
|
+
message: `Manifest size (${sizeMB.toFixed(2)}MB) exceeds maximum limit of ${(errorThreshold / 1024 / 1024).toFixed(0)}MB. Consider splitting or pruning data.`
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (sizeBytes >= warnThreshold) {
|
|
43
|
+
return {
|
|
44
|
+
sizeBytes,
|
|
45
|
+
sizeMB,
|
|
46
|
+
status: 'warning',
|
|
47
|
+
message: `Manifest size (${sizeMB.toFixed(2)}MB) is approaching limit. Warning threshold: ${(warnThreshold / 1024 / 1024).toFixed(0)}MB, Max: ${(errorThreshold / 1024 / 1024).toFixed(0)}MB.`
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
sizeBytes,
|
|
53
|
+
sizeMB,
|
|
54
|
+
status: 'ok'
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get formatted size string
|
|
60
|
+
*/
|
|
61
|
+
export function formatSize(bytes: number): string {
|
|
62
|
+
if (bytes < 1024) {
|
|
63
|
+
return `${bytes} B`;
|
|
64
|
+
}
|
|
65
|
+
if (bytes < 1024 * 1024) {
|
|
66
|
+
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
67
|
+
}
|
|
68
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Validate manifest object size before serialization
|
|
73
|
+
*/
|
|
74
|
+
export function validateManifestObject(
|
|
75
|
+
manifest: unknown,
|
|
76
|
+
options?: SizeCheckOptions
|
|
77
|
+
): SizeCheckResult {
|
|
78
|
+
const serialized = JSON.stringify(manifest, null, 2);
|
|
79
|
+
return checkManifestSize(serialized, options);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Estimate manifest size impact of adding an entry
|
|
84
|
+
*/
|
|
85
|
+
export function estimateEntrySize(entry: unknown): number {
|
|
86
|
+
const serialized = JSON.stringify(entry);
|
|
87
|
+
return Buffer.byteLength(serialized, 'utf8');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check if adding an entry would exceed limits
|
|
92
|
+
*/
|
|
93
|
+
export function canAddEntry(
|
|
94
|
+
currentManifest: unknown,
|
|
95
|
+
newEntry: unknown,
|
|
96
|
+
options?: SizeCheckOptions
|
|
97
|
+
): {
|
|
98
|
+
canAdd: boolean;
|
|
99
|
+
currentSize: SizeCheckResult;
|
|
100
|
+
estimatedSize: SizeCheckResult;
|
|
101
|
+
} {
|
|
102
|
+
const currentSize = validateManifestObject(currentManifest, options);
|
|
103
|
+
const entrySize = estimateEntrySize(newEntry);
|
|
104
|
+
|
|
105
|
+
// Estimate new size (accounting for JSON formatting)
|
|
106
|
+
const estimatedBytes = currentSize.sizeBytes + entrySize + 100; // Add buffer for formatting
|
|
107
|
+
const estimatedSizeMB = estimatedBytes / (1024 * 1024);
|
|
108
|
+
|
|
109
|
+
const errorThreshold = (options?.errorThreshold ?? 10) * 1024 * 1024;
|
|
110
|
+
const canAdd = estimatedBytes < errorThreshold;
|
|
111
|
+
|
|
112
|
+
const estimatedResult = checkManifestSize(Buffer.alloc(estimatedBytes), options);
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
canAdd,
|
|
116
|
+
currentSize,
|
|
117
|
+
estimatedSize: {
|
|
118
|
+
...estimatedResult,
|
|
119
|
+
message: canAdd
|
|
120
|
+
? estimatedResult.message
|
|
121
|
+
: `Adding entry would exceed size limit (estimated: ${estimatedSizeMB.toFixed(2)}MB)`
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
validateManifest,
|
|
4
|
+
validateDependencyEntry,
|
|
5
|
+
validateConfig,
|
|
6
|
+
safeValidateManifest,
|
|
7
|
+
ValidationError
|
|
8
|
+
} from '../src/validator.js';
|
|
9
|
+
|
|
10
|
+
describe('Validator Tests', () => {
|
|
11
|
+
describe('validateManifest', () => {
|
|
12
|
+
it('should validate a valid manifest', () => {
|
|
13
|
+
const manifest = {
|
|
14
|
+
version: '1.0.0',
|
|
15
|
+
generatedAt: '2026-01-29T10:30:00Z',
|
|
16
|
+
generatedBy: {
|
|
17
|
+
action: 'dependabit',
|
|
18
|
+
version: '1.0.0',
|
|
19
|
+
llmProvider: 'github-copilot'
|
|
20
|
+
},
|
|
21
|
+
repository: {
|
|
22
|
+
owner: 'pradeepmouli',
|
|
23
|
+
name: 'dependabit',
|
|
24
|
+
branch: 'main',
|
|
25
|
+
commit: 'abc123'
|
|
26
|
+
},
|
|
27
|
+
dependencies: [],
|
|
28
|
+
statistics: {
|
|
29
|
+
totalDependencies: 0,
|
|
30
|
+
byType: {},
|
|
31
|
+
byAccessMethod: {},
|
|
32
|
+
byDetectionMethod: {},
|
|
33
|
+
averageConfidence: 0
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const result = validateManifest(manifest);
|
|
38
|
+
expect(result.version).toBe('1.0.0');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should throw ValidationError for invalid manifest', () => {
|
|
42
|
+
const invalid = {
|
|
43
|
+
version: '2.0.0'
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
expect(() => validateManifest(invalid)).toThrow(ValidationError);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should provide formatted error messages', () => {
|
|
50
|
+
const invalid = {
|
|
51
|
+
version: '1.0.0'
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
validateManifest(invalid);
|
|
56
|
+
expect.fail('Should have thrown');
|
|
57
|
+
} catch (error) {
|
|
58
|
+
expect(error).toBeInstanceOf(ValidationError);
|
|
59
|
+
const validationError = error as ValidationError;
|
|
60
|
+
const formatted = validationError.getFormattedErrors();
|
|
61
|
+
expect(formatted.length).toBeGreaterThan(0);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('validateDependencyEntry', () => {
|
|
67
|
+
it('should validate a valid dependency entry', () => {
|
|
68
|
+
const entry = {
|
|
69
|
+
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
70
|
+
url: 'https://github.com/microsoft/TypeScript',
|
|
71
|
+
type: 'reference-implementation',
|
|
72
|
+
accessMethod: 'github-api',
|
|
73
|
+
name: 'TypeScript',
|
|
74
|
+
currentStateHash: 'sha256:abc123',
|
|
75
|
+
detectionMethod: 'manual',
|
|
76
|
+
detectionConfidence: 1.0,
|
|
77
|
+
detectedAt: '2026-01-29T10:30:00Z',
|
|
78
|
+
lastChecked: '2026-01-29T10:30:00Z',
|
|
79
|
+
referencedIn: []
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const result = validateDependencyEntry(entry);
|
|
83
|
+
expect(result.name).toBe('TypeScript');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should throw ValidationError for invalid entry', () => {
|
|
87
|
+
const invalid = {
|
|
88
|
+
url: 'https://example.com'
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
expect(() => validateDependencyEntry(invalid)).toThrow(ValidationError);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('validateConfig', () => {
|
|
96
|
+
it('should validate a valid config', () => {
|
|
97
|
+
const config = {
|
|
98
|
+
version: '1',
|
|
99
|
+
schedule: {
|
|
100
|
+
interval: 'daily',
|
|
101
|
+
timezone: 'UTC'
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const result = validateConfig(config);
|
|
106
|
+
expect(result.version).toBe('1');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should throw ValidationError for invalid config', () => {
|
|
110
|
+
const invalid = {
|
|
111
|
+
version: '2'
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
expect(() => validateConfig(invalid)).toThrow(ValidationError);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('safeValidateManifest', () => {
|
|
119
|
+
it('should return success for valid manifest', () => {
|
|
120
|
+
const manifest = {
|
|
121
|
+
version: '1.0.0',
|
|
122
|
+
generatedAt: '2026-01-29T10:30:00Z',
|
|
123
|
+
generatedBy: {
|
|
124
|
+
action: 'dependabit',
|
|
125
|
+
version: '1.0.0',
|
|
126
|
+
llmProvider: 'github-copilot'
|
|
127
|
+
},
|
|
128
|
+
repository: {
|
|
129
|
+
owner: 'pradeepmouli',
|
|
130
|
+
name: 'dependabit',
|
|
131
|
+
branch: 'main',
|
|
132
|
+
commit: 'abc123'
|
|
133
|
+
},
|
|
134
|
+
dependencies: [],
|
|
135
|
+
statistics: {
|
|
136
|
+
totalDependencies: 0,
|
|
137
|
+
byType: {},
|
|
138
|
+
byAccessMethod: {},
|
|
139
|
+
byDetectionMethod: {},
|
|
140
|
+
averageConfidence: 0
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const result = safeValidateManifest(manifest);
|
|
145
|
+
expect(result.success).toBe(true);
|
|
146
|
+
expect(result.data).toBeDefined();
|
|
147
|
+
expect(result.error).toBeUndefined();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should return error for invalid manifest', () => {
|
|
151
|
+
const invalid = {
|
|
152
|
+
version: '2.0.0'
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const result = safeValidateManifest(invalid);
|
|
156
|
+
expect(result.success).toBe(false);
|
|
157
|
+
expect(result.data).toBeUndefined();
|
|
158
|
+
expect(result.error).toBeInstanceOf(ValidationError);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
package/src/validator.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DependencyManifestSchema,
|
|
3
|
+
DependencyEntrySchema,
|
|
4
|
+
DependabitConfigSchema,
|
|
5
|
+
type DependencyManifest,
|
|
6
|
+
type DependencyEntry,
|
|
7
|
+
type DependabitConfig
|
|
8
|
+
} from './schema.js';
|
|
9
|
+
import { ZodError } from 'zod';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validation error class with detailed error information
|
|
13
|
+
*/
|
|
14
|
+
export class ValidationError extends Error {
|
|
15
|
+
constructor(
|
|
16
|
+
message: string,
|
|
17
|
+
public readonly errors: ZodError
|
|
18
|
+
) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'ValidationError';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get formatted error messages
|
|
25
|
+
*/
|
|
26
|
+
getFormattedErrors(): string[] {
|
|
27
|
+
return this.errors.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Validate a dependency manifest
|
|
33
|
+
* @throws {ValidationError} if validation fails
|
|
34
|
+
*/
|
|
35
|
+
export function validateManifest(data: unknown): DependencyManifest {
|
|
36
|
+
try {
|
|
37
|
+
return DependencyManifestSchema.parse(data);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error instanceof ZodError) {
|
|
40
|
+
throw new ValidationError('Manifest validation failed', error);
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Validate a dependency entry
|
|
48
|
+
* @throws {ValidationError} if validation fails
|
|
49
|
+
*/
|
|
50
|
+
export function validateDependencyEntry(data: unknown): DependencyEntry {
|
|
51
|
+
try {
|
|
52
|
+
return DependencyEntrySchema.parse(data);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error instanceof ZodError) {
|
|
55
|
+
throw new ValidationError('Dependency entry validation failed', error);
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Validate a dependabit configuration
|
|
63
|
+
* @throws {ValidationError} if validation fails
|
|
64
|
+
*/
|
|
65
|
+
export function validateConfig(data: unknown): DependabitConfig {
|
|
66
|
+
try {
|
|
67
|
+
return DependabitConfigSchema.parse(data);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (error instanceof ZodError) {
|
|
70
|
+
throw new ValidationError('Config validation failed', error);
|
|
71
|
+
}
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Safe validation that returns success/error result
|
|
78
|
+
*/
|
|
79
|
+
export function safeValidateManifest(data: unknown): {
|
|
80
|
+
success: boolean;
|
|
81
|
+
data?: DependencyManifest;
|
|
82
|
+
error?: ValidationError;
|
|
83
|
+
} {
|
|
84
|
+
try {
|
|
85
|
+
const validated = validateManifest(data);
|
|
86
|
+
return { success: true, data: validated };
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (error instanceof ValidationError) {
|
|
89
|
+
return { success: false, error };
|
|
90
|
+
}
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Safe validation for dependency entry
|
|
97
|
+
*/
|
|
98
|
+
export function safeValidateDependencyEntry(data: unknown): {
|
|
99
|
+
success: boolean;
|
|
100
|
+
data?: DependencyEntry;
|
|
101
|
+
error?: ValidationError;
|
|
102
|
+
} {
|
|
103
|
+
try {
|
|
104
|
+
const validated = validateDependencyEntry(data);
|
|
105
|
+
return { success: true, data: validated };
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (error instanceof ValidationError) {
|
|
108
|
+
return { success: false, error };
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Safe validation for config
|
|
116
|
+
*/
|
|
117
|
+
export function safeValidateConfig(data: unknown): {
|
|
118
|
+
success: boolean;
|
|
119
|
+
data?: DependabitConfig;
|
|
120
|
+
error?: ValidationError;
|
|
121
|
+
} {
|
|
122
|
+
try {
|
|
123
|
+
const validated = validateConfig(data);
|
|
124
|
+
return { success: true, data: validated };
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (error instanceof ValidationError) {
|
|
127
|
+
return { success: false, error };
|
|
128
|
+
}
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|