@dependabit/manifest 0.1.13 → 0.1.15

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/src/manifest.ts DELETED
@@ -1,278 +0,0 @@
1
- import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
- import { dirname } from 'node:path';
3
- import { type DependencyManifest, type DependencyEntry } from './schema.js';
4
- import { validateManifest, validateDependencyEntry, safeValidateManifest } from './validator.js';
5
-
6
- /**
7
- * Read and parse a manifest file
8
- */
9
- export async function readManifest(path: string): Promise<DependencyManifest> {
10
- const content = await readFile(path, 'utf-8');
11
- const data = JSON.parse(content);
12
- return validateManifest(data);
13
- }
14
-
15
- /**
16
- * Write a manifest to file
17
- */
18
- export async function writeManifest(
19
- path: string,
20
- manifest: DependencyManifest,
21
- options?: { strict?: boolean }
22
- ): Promise<{ validationErrors?: string[] }> {
23
- const strict = options?.strict ?? false;
24
- const result: { validationErrors?: string[] } = {};
25
-
26
- // Validate before writing
27
- const validation = safeValidateManifest(manifest);
28
- if (!validation.success) {
29
- const errors = validation.error!.getFormattedErrors();
30
- if (strict) {
31
- throw validation.error!;
32
- }
33
- // Non-strict: record warnings but still write the manifest
34
- result.validationErrors = errors;
35
- }
36
-
37
- // Ensure directory exists
38
- await mkdir(dirname(path), { recursive: true });
39
-
40
- // Write formatted JSON
41
- const content = JSON.stringify(manifest, null, 2);
42
- await writeFile(path, content, 'utf-8');
43
-
44
- return result;
45
- }
46
-
47
- /**
48
- * Update a dependency entry in the manifest
49
- */
50
- export async function updateDependency(
51
- path: string,
52
- dependencyId: string,
53
- updates: Partial<DependencyEntry>
54
- ): Promise<DependencyManifest> {
55
- const manifest = await readManifest(path);
56
-
57
- const dep = manifest.dependencies.find((d) => d.id === dependencyId);
58
- if (!dep) {
59
- throw new Error(`Dependency with id ${dependencyId} not found`);
60
- }
61
-
62
- // Update the dependency in place
63
- Object.assign(dep, updates);
64
-
65
- // Validate the merged dependency
66
- validateDependencyEntry(dep);
67
-
68
- // Update statistics
69
- manifest.statistics = calculateStatistics(manifest.dependencies);
70
-
71
- // Write back
72
- await writeManifest(path, manifest);
73
-
74
- return manifest;
75
- }
76
-
77
- /**
78
- * Add a new dependency to the manifest
79
- */
80
- export async function addDependency(
81
- path: string,
82
- dependency: DependencyEntry
83
- ): Promise<DependencyManifest> {
84
- const manifest = await readManifest(path);
85
-
86
- // Check for duplicates by ID or URL
87
- const existingById = manifest.dependencies.find((dep) => dep.id === dependency.id);
88
- const existingByUrl = manifest.dependencies.find((dep) => dep.url === dependency.url);
89
-
90
- if (existingById) {
91
- throw new Error(`Dependency with id ${dependency.id} already exists`);
92
- }
93
-
94
- if (existingByUrl) {
95
- throw new Error(`Dependency with url ${dependency.url} already exists`);
96
- }
97
-
98
- // Add dependency
99
- manifest.dependencies.push(dependency);
100
-
101
- // Update statistics
102
- manifest.statistics = calculateStatistics(manifest.dependencies);
103
-
104
- // Write back
105
- await writeManifest(path, manifest);
106
-
107
- return manifest;
108
- }
109
-
110
- /**
111
- * Remove a dependency from the manifest
112
- */
113
- export async function removeDependency(
114
- path: string,
115
- dependencyId: string
116
- ): Promise<DependencyManifest> {
117
- const manifest = await readManifest(path);
118
-
119
- const index = manifest.dependencies.findIndex((dep) => dep.id === dependencyId);
120
- if (index === -1) {
121
- throw new Error(`Dependency with id ${dependencyId} not found`);
122
- }
123
-
124
- // Remove dependency
125
- manifest.dependencies.splice(index, 1);
126
-
127
- // Update statistics
128
- manifest.statistics = calculateStatistics(manifest.dependencies);
129
-
130
- // Write back
131
- await writeManifest(path, manifest);
132
-
133
- return manifest;
134
- }
135
-
136
- /**
137
- * Merge two manifests, preserving manual entries
138
- * Manual entries are those with detectionMethod === 'manual'
139
- */
140
- export function mergeManifests(
141
- existing: DependencyManifest,
142
- updated: DependencyManifest,
143
- options: {
144
- preserveManual?: boolean;
145
- preserveHistory?: boolean;
146
- } = {}
147
- ): DependencyManifest {
148
- const { preserveManual = true, preserveHistory = true } = options;
149
-
150
- // Create a deep copy of the updated manifest to avoid mutations
151
- const merged: DependencyManifest = {
152
- ...updated,
153
- dependencies: updated.dependencies.map((dep) => ({
154
- ...dep,
155
- changeHistory: dep.changeHistory ? [...dep.changeHistory] : [],
156
- referencedIn: dep.referencedIn ? [...dep.referencedIn] : []
157
- }))
158
- };
159
-
160
- if (preserveManual) {
161
- // Find manual entries in existing manifest
162
- const manualEntries = existing.dependencies.filter((dep) => dep.detectionMethod === 'manual');
163
-
164
- // Add manual entries that aren't in the updated manifest
165
- for (const manualEntry of manualEntries) {
166
- const existsInUpdated = merged.dependencies.some(
167
- (dep) => dep.id === manualEntry.id || dep.url === manualEntry.url
168
- );
169
-
170
- if (!existsInUpdated) {
171
- merged.dependencies.push({
172
- ...manualEntry,
173
- changeHistory: manualEntry.changeHistory ? [...manualEntry.changeHistory] : [],
174
- referencedIn: manualEntry.referencedIn ? [...manualEntry.referencedIn] : []
175
- });
176
- }
177
- }
178
- }
179
-
180
- if (preserveHistory) {
181
- // Preserve change history for matching dependencies
182
- merged.dependencies = merged.dependencies.map((dep) => {
183
- const existingDep = existing.dependencies.find((d) => d.id === dep.id || d.url === dep.url);
184
-
185
- if (existingDep && existingDep.changeHistory && existingDep.changeHistory.length > 0) {
186
- return {
187
- ...dep,
188
- changeHistory: [...existingDep.changeHistory, ...(dep.changeHistory || [])]
189
- };
190
- }
191
-
192
- return dep;
193
- });
194
- }
195
-
196
- // Recalculate statistics
197
- merged.statistics = calculateStatistics(merged.dependencies);
198
-
199
- return merged;
200
- }
201
-
202
- /**
203
- * Calculate statistics for a list of dependencies
204
- */
205
- function calculateStatistics(dependencies: DependencyEntry[]): DependencyManifest['statistics'] {
206
- const byType: Record<string, number> = {};
207
- const byAccessMethod: Record<string, number> = {};
208
- const byDetectionMethod: Record<string, number> = {};
209
- let totalConfidence = 0;
210
- let falsePositiveCount = 0;
211
- let totalChangeCount = 0;
212
-
213
- for (const dep of dependencies) {
214
- byType[dep.type] = (byType[dep.type] || 0) + 1;
215
- byAccessMethod[dep.accessMethod] = (byAccessMethod[dep.accessMethod] || 0) + 1;
216
- byDetectionMethod[dep.detectionMethod] = (byDetectionMethod[dep.detectionMethod] || 0) + 1;
217
- totalConfidence += dep.detectionConfidence;
218
-
219
- // Count false positives in change history
220
- const changeHistory = dep.changeHistory || [];
221
- const fpCount = changeHistory.filter((change) => change.falsePositive).length;
222
- falsePositiveCount += fpCount;
223
- totalChangeCount += changeHistory.length;
224
- }
225
-
226
- const averageConfidence = dependencies.length > 0 ? totalConfidence / dependencies.length : 0;
227
-
228
- const falsePositiveRate =
229
- totalChangeCount > 0 ? falsePositiveCount / totalChangeCount : undefined;
230
-
231
- return {
232
- totalDependencies: dependencies.length,
233
- byType,
234
- byAccessMethod,
235
- byDetectionMethod,
236
- averageConfidence,
237
- falsePositiveRate
238
- };
239
- }
240
-
241
- /**
242
- * Create an empty manifest template
243
- */
244
- export function createEmptyManifest(options: {
245
- owner: string;
246
- name: string;
247
- branch: string;
248
- commit: string;
249
- action?: string;
250
- version?: string;
251
- llmProvider?: string;
252
- llmModel?: string;
253
- }): DependencyManifest {
254
- return {
255
- version: '1.0.0',
256
- generatedAt: new Date().toISOString(),
257
- generatedBy: {
258
- action: options.action || 'dependabit',
259
- version: options.version || '0.1.0',
260
- llmProvider: options.llmProvider || 'github-copilot',
261
- llmModel: options.llmModel
262
- },
263
- repository: {
264
- owner: options.owner,
265
- name: options.name,
266
- branch: options.branch,
267
- commit: options.commit
268
- },
269
- dependencies: [],
270
- statistics: {
271
- totalDependencies: 0,
272
- byType: {},
273
- byAccessMethod: {},
274
- byDetectionMethod: {},
275
- averageConfidence: 0
276
- }
277
- };
278
- }
@@ -1,293 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import {
3
- DependencyManifestSchema,
4
- DependencyEntrySchema,
5
- DependabitConfigSchema,
6
- AccessMethodSchema,
7
- DependencyTypeSchema,
8
- DetectionMethodSchema,
9
- SeveritySchema
10
- } from '../src/schema.js';
11
-
12
- describe('Schema Tests', () => {
13
- describe('AccessMethodSchema', () => {
14
- it('should accept valid access methods', () => {
15
- expect(AccessMethodSchema.parse('context7')).toBe('context7');
16
- expect(AccessMethodSchema.parse('arxiv')).toBe('arxiv');
17
- expect(AccessMethodSchema.parse('openapi')).toBe('openapi');
18
- expect(AccessMethodSchema.parse('github-api')).toBe('github-api');
19
- expect(AccessMethodSchema.parse('http')).toBe('http');
20
- });
21
-
22
- it('should reject invalid access methods', () => {
23
- expect(() => AccessMethodSchema.parse('invalid')).toThrow();
24
- });
25
- });
26
-
27
- describe('DependencyTypeSchema', () => {
28
- it('should accept valid dependency types', () => {
29
- expect(DependencyTypeSchema.parse('reference-implementation')).toBe(
30
- 'reference-implementation'
31
- );
32
- expect(DependencyTypeSchema.parse('schema')).toBe('schema');
33
- expect(DependencyTypeSchema.parse('documentation')).toBe('documentation');
34
- });
35
-
36
- it('should reject invalid dependency types', () => {
37
- expect(() => DependencyTypeSchema.parse('invalid')).toThrow();
38
- });
39
- });
40
-
41
- describe('DetectionMethodSchema', () => {
42
- it('should accept valid detection methods', () => {
43
- expect(DetectionMethodSchema.parse('llm-analysis')).toBe('llm-analysis');
44
- expect(DetectionMethodSchema.parse('manual')).toBe('manual');
45
- expect(DetectionMethodSchema.parse('package-json')).toBe('package-json');
46
- });
47
- });
48
-
49
- describe('SeveritySchema', () => {
50
- it('should accept valid severity levels', () => {
51
- expect(SeveritySchema.parse('breaking')).toBe('breaking');
52
- expect(SeveritySchema.parse('major')).toBe('major');
53
- expect(SeveritySchema.parse('minor')).toBe('minor');
54
- });
55
- });
56
-
57
- describe('DependencyEntrySchema', () => {
58
- it('should validate a complete dependency entry', () => {
59
- const entry = {
60
- id: '550e8400-e29b-41d4-a716-446655440000',
61
- url: 'https://github.com/microsoft/TypeScript',
62
- type: 'reference-implementation',
63
- accessMethod: 'github-api',
64
- name: 'TypeScript',
65
- description: 'TypeScript compiler',
66
- currentVersion: '5.9.3',
67
- currentStateHash: 'sha256:abc123',
68
- detectionMethod: 'package-json',
69
- detectionConfidence: 1.0,
70
- detectedAt: '2026-01-29T10:30:00Z',
71
- lastChecked: '2026-01-29T10:30:00Z',
72
- auth: undefined,
73
- referencedIn: [
74
- {
75
- file: 'package.json',
76
- line: 15,
77
- context: '"typescript": "^5.9.3"'
78
- }
79
- ],
80
- changeHistory: []
81
- };
82
-
83
- const result = DependencyEntrySchema.parse(entry);
84
- expect(result.id).toBe(entry.id);
85
- expect(result.name).toBe('TypeScript');
86
- });
87
-
88
- it('should require all mandatory fields', () => {
89
- expect(() =>
90
- DependencyEntrySchema.parse({
91
- url: 'https://example.com'
92
- })
93
- ).toThrow();
94
- });
95
-
96
- it('should validate UUID format', () => {
97
- const entry = {
98
- id: 'not-a-uuid',
99
- url: 'https://github.com/microsoft/TypeScript',
100
- type: 'reference-implementation',
101
- accessMethod: 'github-api',
102
- name: 'TypeScript',
103
- currentStateHash: 'sha256:abc123',
104
- detectionMethod: 'manual',
105
- detectionConfidence: 1.0,
106
- detectedAt: '2026-01-29T10:30:00Z',
107
- lastChecked: '2026-01-29T10:30:00Z',
108
- referencedIn: []
109
- };
110
-
111
- expect(() => DependencyEntrySchema.parse(entry)).toThrow();
112
- });
113
-
114
- it('should validate URL format', () => {
115
- const entry = {
116
- id: '550e8400-e29b-41d4-a716-446655440000',
117
- url: 'not-a-url',
118
- type: 'reference-implementation',
119
- accessMethod: 'github-api',
120
- name: 'TypeScript',
121
- currentStateHash: 'sha256:abc123',
122
- detectionMethod: 'manual',
123
- detectionConfidence: 1.0,
124
- detectedAt: '2026-01-29T10:30:00Z',
125
- lastChecked: '2026-01-29T10:30:00Z',
126
- referencedIn: []
127
- };
128
-
129
- expect(() => DependencyEntrySchema.parse(entry)).toThrow();
130
- });
131
-
132
- it('should validate confidence range', () => {
133
- const entry = {
134
- id: '550e8400-e29b-41d4-a716-446655440000',
135
- url: 'https://github.com/microsoft/TypeScript',
136
- type: 'reference-implementation',
137
- accessMethod: 'github-api',
138
- name: 'TypeScript',
139
- currentStateHash: 'sha256:abc123',
140
- detectionMethod: 'manual',
141
- detectionConfidence: 1.5,
142
- detectedAt: '2026-01-29T10:30:00Z',
143
- lastChecked: '2026-01-29T10:30:00Z',
144
- referencedIn: []
145
- };
146
-
147
- expect(() => DependencyEntrySchema.parse(entry)).toThrow();
148
- });
149
- });
150
-
151
- describe('DependencyManifestSchema', () => {
152
- it('should validate a complete manifest', () => {
153
- const manifest = {
154
- version: '1.0.0',
155
- generatedAt: '2026-01-29T10:30:00Z',
156
- generatedBy: {
157
- action: 'dependabit',
158
- version: '1.0.0',
159
- llmProvider: 'github-copilot',
160
- llmModel: 'gpt-4'
161
- },
162
- repository: {
163
- owner: 'pradeepmouli',
164
- name: 'dependabit',
165
- branch: 'main',
166
- commit: 'abc123def456'
167
- },
168
- dependencies: [
169
- {
170
- id: '550e8400-e29b-41d4-a716-446655440000',
171
- url: 'https://github.com/microsoft/TypeScript',
172
- type: 'reference-implementation',
173
- accessMethod: 'github-api',
174
- name: 'TypeScript',
175
- currentStateHash: 'sha256:abc123',
176
- detectionMethod: 'package-json',
177
- detectionConfidence: 1.0,
178
- detectedAt: '2026-01-29T10:30:00Z',
179
- lastChecked: '2026-01-29T10:30:00Z',
180
- referencedIn: []
181
- }
182
- ],
183
- statistics: {
184
- totalDependencies: 1,
185
- byType: { 'reference-implementation': 1 },
186
- byAccessMethod: { 'github-api': 1 },
187
- byDetectionMethod: { 'package-json': 1 },
188
- averageConfidence: 1.0
189
- }
190
- };
191
-
192
- const result = DependencyManifestSchema.parse(manifest);
193
- expect(result.version).toBe('1.0.0');
194
- expect(result.dependencies).toHaveLength(1);
195
- });
196
-
197
- it('should require correct version', () => {
198
- const manifest = {
199
- version: '2.0.0',
200
- generatedAt: '2026-01-29T10:30:00Z',
201
- generatedBy: {
202
- action: 'dependabit',
203
- version: '1.0.0',
204
- llmProvider: 'github-copilot'
205
- },
206
- repository: {
207
- owner: 'pradeepmouli',
208
- name: 'dependabit',
209
- branch: 'main',
210
- commit: 'abc123'
211
- },
212
- dependencies: [],
213
- statistics: {
214
- totalDependencies: 0,
215
- byType: {},
216
- byAccessMethod: {},
217
- byDetectionMethod: {},
218
- averageConfidence: 0
219
- }
220
- };
221
-
222
- expect(() => DependencyManifestSchema.parse(manifest)).toThrow();
223
- });
224
- });
225
-
226
- describe('DependabitConfigSchema', () => {
227
- it('should validate a complete config', () => {
228
- const config = {
229
- version: '1',
230
- llm: {
231
- provider: 'github-copilot',
232
- model: 'gpt-4',
233
- maxTokens: 4000,
234
- temperature: 0.3
235
- },
236
- schedule: {
237
- interval: 'daily',
238
- time: '02:00',
239
- timezone: 'UTC'
240
- },
241
- issues: {
242
- labels: ['dependabit', 'dependency-update'],
243
- assignees: ['pradeepmouli'],
244
- titleTemplate: '[dependabit] {name}: {change}'
245
- },
246
- monitoring: {
247
- enabled: true,
248
- autoUpdate: true,
249
- falsePositiveThreshold: 0.1
250
- }
251
- };
252
-
253
- const result = DependabitConfigSchema.parse(config);
254
- expect(result.version).toBe('1');
255
- expect(result.llm?.provider).toBe('github-copilot');
256
- });
257
-
258
- it('should apply defaults', () => {
259
- const config = {
260
- version: '1'
261
- };
262
-
263
- const result = DependabitConfigSchema.parse(config);
264
- expect(result.schedule.interval).toBe('daily');
265
- expect(result.schedule.timezone).toBe('UTC');
266
- });
267
-
268
- it('should validate schedule time format', () => {
269
- const config = {
270
- version: '1',
271
- schedule: {
272
- interval: 'daily',
273
- time: '25:00',
274
- timezone: 'UTC'
275
- }
276
- };
277
-
278
- expect(() => DependabitConfigSchema.parse(config)).toThrow();
279
- });
280
-
281
- it('should validate LLM temperature range', () => {
282
- const config = {
283
- version: '1',
284
- llm: {
285
- provider: 'github-copilot',
286
- temperature: 3.0
287
- }
288
- };
289
-
290
- expect(() => DependabitConfigSchema.parse(config)).toThrow();
291
- });
292
- });
293
- });