@dependabit/monitor 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/monitor.ts DELETED
@@ -1,120 +0,0 @@
1
- /**
2
- * Monitor Orchestrator
3
- * Coordinates dependency checking across multiple access methods
4
- */
5
-
6
- import { GitHubRepoChecker } from './checkers/github-repo.js';
7
- import { URLContentChecker } from './checkers/url-content.js';
8
- import { OpenAPIChecker } from './checkers/openapi.js';
9
- import { SeverityClassifier } from './severity.js';
10
- import type { Checker, AccessConfig, DependencySnapshot, ChangeDetection } from './types.js';
11
-
12
- export interface DependencyConfig extends AccessConfig {
13
- id: string;
14
- name?: string;
15
- type?: string;
16
- currentStateHash: string;
17
- currentVersion?: string;
18
- lastChecked?: string;
19
- monitoring?: {
20
- enabled?: boolean;
21
- ignoreChanges?: boolean;
22
- };
23
- }
24
-
25
- export interface CheckResult {
26
- dependency: DependencyConfig;
27
- hasChanged: boolean;
28
- changes?: ChangeDetection;
29
- severity?: 'breaking' | 'major' | 'minor' | undefined;
30
- newSnapshot?: DependencySnapshot;
31
- error?: string;
32
- }
33
-
34
- export class Monitor {
35
- private checkers: Map<string, Checker>;
36
- private classifier: SeverityClassifier;
37
-
38
- constructor() {
39
- this.checkers = new Map();
40
- this.checkers.set('github-api', new GitHubRepoChecker());
41
- this.checkers.set('http', new URLContentChecker());
42
- this.checkers.set('openapi', new OpenAPIChecker());
43
-
44
- this.classifier = new SeverityClassifier();
45
- }
46
-
47
- /**
48
- * Checks a single dependency for changes
49
- */
50
- async checkDependency(dependency: DependencyConfig): Promise<CheckResult> {
51
- try {
52
- // Get appropriate checker for access method
53
- const checker = this.checkers.get(dependency.accessMethod);
54
- if (!checker) {
55
- throw new Error(`Unsupported access method: ${dependency.accessMethod}`);
56
- }
57
-
58
- // Fetch current state
59
- const newSnapshot = await checker.fetch(dependency);
60
-
61
- // Create old snapshot from stored data
62
- const oldSnapshot: DependencySnapshot = {
63
- stateHash: dependency.currentStateHash,
64
- version: dependency.currentVersion,
65
- fetchedAt: dependency.lastChecked ? new Date(dependency.lastChecked) : new Date()
66
- };
67
-
68
- // Compare states
69
- const changes = await checker.compare(oldSnapshot, newSnapshot);
70
-
71
- // Classify severity if changes detected
72
- let severity: 'breaking' | 'major' | 'minor' | undefined;
73
- if (changes.hasChanged) {
74
- severity = this.classifier.classify(changes);
75
- }
76
-
77
- return {
78
- dependency,
79
- hasChanged: changes.hasChanged,
80
- changes,
81
- severity,
82
- newSnapshot
83
- };
84
- } catch (error) {
85
- return {
86
- dependency,
87
- hasChanged: false,
88
- error: (error as Error).message
89
- };
90
- }
91
- }
92
-
93
- /**
94
- * Checks multiple dependencies, respecting monitoring rules
95
- */
96
- async checkAll(dependencies: DependencyConfig[]): Promise<CheckResult[]> {
97
- // Filter out disabled dependencies
98
- const enabledDeps = dependencies.filter((dep) => {
99
- if (dep.monitoring?.enabled === false) {
100
- return false;
101
- }
102
- if (dep.monitoring?.ignoreChanges === true) {
103
- return false;
104
- }
105
- return true;
106
- });
107
-
108
- // Check all dependencies in parallel
109
- const results = await Promise.all(enabledDeps.map((dep) => this.checkDependency(dep)));
110
-
111
- return results;
112
- }
113
-
114
- /**
115
- * Registers a custom checker for an access method
116
- */
117
- registerChecker(accessMethod: string, checker: Checker): void {
118
- this.checkers.set(accessMethod, checker);
119
- }
120
- }
package/src/normalizer.ts DELETED
@@ -1,122 +0,0 @@
1
- /**
2
- * HTML Normalizer for content-based change detection
3
- *
4
- * Implements 6-step normalization to reduce false positives:
5
- * 1. Remove <script> and <style> tags
6
- * 2. Strip HTML comments
7
- * 3. Normalize whitespace
8
- * 4. Remove timestamp patterns
9
- * 5. Remove analytics/tracking parameters
10
- * 6. Preserve meaningful content
11
- */
12
-
13
- /**
14
- * Normalizes HTML content for consistent comparison
15
- * @param html Raw HTML content
16
- * @returns Normalized HTML string
17
- */
18
- export function normalizeHTML(html: string): string {
19
- if (!html || html.trim() === '') {
20
- return '';
21
- }
22
-
23
- let normalized = html;
24
-
25
- // Step 1: Remove script and style tags (with content)
26
- // Use a simpler, more secure approach - remove everything between opening and closing tags
27
- // This handles variations like </script >, </script\n>, etc.
28
- let prevLength = 0;
29
- while (normalized.length !== prevLength) {
30
- prevLength = normalized.length;
31
- // Match <script...> then any content, then </script with any whitespace/attributes before >
32
- normalized = normalized.replace(/<script\b[^>]*>[\s\S]*?<\/script\s*[^>]*>/gi, '');
33
- }
34
- prevLength = 0;
35
- while (normalized.length !== prevLength) {
36
- prevLength = normalized.length;
37
- normalized = normalized.replace(/<style\b[^>]*>[\s\S]*?<\/style\s*[^>]*>/gi, '');
38
- }
39
-
40
- // Step 2: Strip HTML comments - iterative to handle nested comments
41
- prevLength = 0;
42
- while (normalized.length !== prevLength) {
43
- prevLength = normalized.length;
44
- normalized = normalized.replace(/<!--[\s\S]*?-->/g, '');
45
- }
46
-
47
- // Step 3: Normalize whitespace (collapse multiple spaces/newlines)
48
- normalized = normalized.replace(/\s+/g, ' ');
49
- normalized = normalized.trim();
50
-
51
- // Step 4: Remove common timestamp patterns
52
- // Patterns like "Updated: 2024-01-01" or "Last modified: Jan 1, 2024"
53
- normalized = normalized.replace(
54
- /\b(Updated|Last\s+modified|Modified|Created|Published):\s*[^<\n]+/gi,
55
- ''
56
- );
57
-
58
- // Remove ISO timestamps
59
- normalized = normalized.replace(
60
- /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?/g,
61
- ''
62
- );
63
-
64
- // Remove common date formats
65
- normalized = normalized.replace(
66
- /\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2},?\s+\d{4}\b/gi,
67
- ''
68
- );
69
-
70
- // Step 5: Remove analytics/tracking parameters from URLs
71
- // UTM parameters, tracking IDs, session IDs, etc.
72
- normalized = normalized.replace(
73
- /[?&](utm_[^&\s"']+|fbclid|gclid|msclkid|mc_[^&\s"']+|_ga|sessionid)[^&\s"']*/gi,
74
- ''
75
- );
76
-
77
- // Step 6: Final cleanup - preserve meaningful content structure
78
- // Remove extra spaces that may have been introduced
79
- normalized = normalized.replace(/\s+/g, ' ');
80
- normalized = normalized.trim();
81
-
82
- return normalized;
83
- }
84
-
85
- /**
86
- * Normalizes a URL by removing tracking parameters
87
- * @param url URL string
88
- * @returns Normalized URL
89
- */
90
- export function normalizeURL(url: string): string {
91
- try {
92
- const urlObj = new URL(url);
93
-
94
- // Remove common tracking parameters
95
- const trackingParams = [
96
- 'utm_source',
97
- 'utm_medium',
98
- 'utm_campaign',
99
- 'utm_term',
100
- 'utm_content',
101
- 'fbclid',
102
- 'gclid',
103
- 'msclkid',
104
- 'mc_cid',
105
- 'mc_eid',
106
- '_ga',
107
- '_gac',
108
- 'sessionid',
109
- 'sid',
110
- 'SSID'
111
- ];
112
-
113
- trackingParams.forEach((param) => {
114
- urlObj.searchParams.delete(param);
115
- });
116
-
117
- return urlObj.toString();
118
- } catch {
119
- // If URL parsing fails, return original
120
- return url;
121
- }
122
- }
package/src/scheduler.ts DELETED
@@ -1,175 +0,0 @@
1
- import {
2
- type DependencyEntry,
3
- type DependabitConfig,
4
- getEffectiveMonitoringRules
5
- } from '@dependabit/manifest';
6
-
7
- export interface SchedulerOptions {
8
- currentTime?: Date;
9
- }
10
-
11
- /**
12
- * Scheduler for per-dependency monitoring
13
- *
14
- * Determines which dependencies should be checked based on:
15
- * - Check frequency (hourly, daily, weekly, monthly)
16
- * - Last checked timestamp
17
- * - Enabled/disabled status
18
- * - IgnoreChanges flag
19
- * - Config overrides
20
- */
21
- export class Scheduler {
22
- /**
23
- * Check if a dependency should be checked now
24
- *
25
- * @param dependency Dependency entry
26
- * @param config Configuration
27
- * @param currentTime Current time (defaults to now)
28
- * @returns true if dependency should be checked
29
- */
30
- shouldCheckDependency(
31
- dependency: DependencyEntry,
32
- config: DependabitConfig,
33
- currentTime: Date = new Date()
34
- ): boolean {
35
- // Check dependency's own monitoring rules first
36
- if (dependency.monitoring) {
37
- if (!dependency.monitoring.enabled) {
38
- return false;
39
- }
40
-
41
- if (dependency.monitoring.ignoreChanges) {
42
- return false;
43
- }
44
- }
45
-
46
- // Get effective monitoring rules (applies config overrides)
47
- const rules = getEffectiveMonitoringRules(config, dependency.url);
48
-
49
- // Check if monitoring is enabled at config level
50
- if (!rules.enabled) {
51
- return false;
52
- }
53
-
54
- // Check if changes should be ignored at config level
55
- if (rules.ignoreChanges) {
56
- return false;
57
- }
58
-
59
- // Get last checked time
60
- const lastChecked = new Date(dependency.lastChecked);
61
- const timeSinceCheck = currentTime.getTime() - lastChecked.getTime();
62
-
63
- // Determine frequency to use (dependency's own monitoring rules take precedence)
64
- const checkFrequency = dependency.monitoring?.checkFrequency || rules.checkFrequency;
65
-
66
- // Determine if enough time has passed based on frequency
67
- const intervalMs = this.getIntervalMs(checkFrequency);
68
-
69
- return timeSinceCheck >= intervalMs;
70
- }
71
-
72
- /**
73
- * Filter dependencies that should be checked
74
- *
75
- * @param dependencies Array of dependencies
76
- * @param config Configuration
77
- * @param currentTime Current time (defaults to now)
78
- * @returns Filtered array of dependencies to check
79
- */
80
- filterDependenciesToCheck(
81
- dependencies: DependencyEntry[],
82
- config: DependabitConfig,
83
- currentTime: Date = new Date()
84
- ): DependencyEntry[] {
85
- return dependencies.filter((dep) => this.shouldCheckDependency(dep, config, currentTime));
86
- }
87
-
88
- /**
89
- * Get interval in milliseconds for a check frequency
90
- *
91
- * @param frequency Check frequency
92
- * @returns Interval in milliseconds
93
- */
94
- private getIntervalMs(frequency: 'hourly' | 'daily' | 'weekly' | 'monthly'): number {
95
- switch (frequency) {
96
- case 'hourly':
97
- return 60 * 60 * 1000; // 1 hour
98
- case 'daily':
99
- return 24 * 60 * 60 * 1000; // 24 hours
100
- case 'weekly':
101
- return 7 * 24 * 60 * 60 * 1000; // 7 days
102
- case 'monthly':
103
- return 30 * 24 * 60 * 60 * 1000; // 30 days (approximate)
104
- default:
105
- return 24 * 60 * 60 * 1000; // Default to daily
106
- }
107
- }
108
-
109
- /**
110
- * Get next check time for a dependency
111
- *
112
- * @param dependency Dependency entry
113
- * @param config Configuration
114
- * @returns Next check time
115
- */
116
- getNextCheckTime(dependency: DependencyEntry, config: DependabitConfig): Date {
117
- const rules = getEffectiveMonitoringRules(config, dependency.url);
118
- const lastChecked = new Date(dependency.lastChecked);
119
-
120
- // Determine frequency to use (dependency's own monitoring rules take precedence)
121
- const checkFrequency = dependency.monitoring?.checkFrequency || rules.checkFrequency;
122
- const intervalMs = this.getIntervalMs(checkFrequency);
123
-
124
- return new Date(lastChecked.getTime() + intervalMs);
125
- }
126
-
127
- /**
128
- * Get schedule summary for all dependencies
129
- *
130
- * @param dependencies Array of dependencies
131
- * @param config Configuration
132
- * @returns Schedule summary grouped by frequency
133
- */
134
- getScheduleSummary(
135
- dependencies: DependencyEntry[],
136
- config: DependabitConfig
137
- ): {
138
- hourly: number;
139
- daily: number;
140
- weekly: number;
141
- monthly: number;
142
- disabled: number;
143
- } {
144
- const summary = {
145
- hourly: 0,
146
- daily: 0,
147
- weekly: 0,
148
- monthly: 0,
149
- disabled: 0
150
- };
151
-
152
- for (const dep of dependencies) {
153
- // Check dependency's own monitoring rules first
154
- if (dep.monitoring) {
155
- if (!dep.monitoring.enabled || dep.monitoring.ignoreChanges) {
156
- summary.disabled++;
157
- continue;
158
- }
159
- }
160
-
161
- const rules = getEffectiveMonitoringRules(config, dep.url);
162
-
163
- if (!rules.enabled || rules.ignoreChanges) {
164
- summary.disabled++;
165
- } else {
166
- // Use dependency-level frequency if available, otherwise use rules
167
- const checkFrequency: 'hourly' | 'daily' | 'weekly' | 'monthly' =
168
- dep.monitoring?.checkFrequency ?? rules.checkFrequency;
169
- summary[checkFrequency]++;
170
- }
171
- }
172
-
173
- return summary;
174
- }
175
- }
package/src/severity.ts DELETED
@@ -1,112 +0,0 @@
1
- /**
2
- * Severity Classifier
3
- * Classifies dependency changes into breaking, major, or minor severity levels
4
- */
5
-
6
- import type { ChangeDetection } from './types.js';
7
-
8
- export type Severity = 'breaking' | 'major' | 'minor';
9
-
10
- export class SeverityClassifier {
11
- /**
12
- * Classifies the severity of a change based on version changes and change types
13
- */
14
- classify(changes: ChangeDetection): Severity {
15
- if (!changes.hasChanged) {
16
- return 'minor';
17
- }
18
-
19
- // Check for version-based classification
20
- if (changes.oldVersion && changes.newVersion) {
21
- const severity = this.classifyVersionChange(changes.oldVersion, changes.newVersion);
22
- if (severity) {
23
- return severity;
24
- }
25
- }
26
-
27
- // Check for OpenAPI/schema changes
28
- if (changes.diff && typeof changes.diff === 'object') {
29
- const diff = changes.diff as {
30
- removedEndpoints?: string[];
31
- schemaChanges?: string[];
32
- addedEndpoints?: string[];
33
- };
34
-
35
- // Breaking: Removed endpoints or incompatible schema changes
36
- if (diff.removedEndpoints && diff.removedEndpoints.length > 0) {
37
- return 'breaking';
38
- }
39
-
40
- if (diff.schemaChanges && diff.schemaChanges.length > 0) {
41
- return 'breaking';
42
- }
43
-
44
- // Major: New endpoints or features
45
- if (diff.addedEndpoints && diff.addedEndpoints.length > 0) {
46
- return 'major';
47
- }
48
- }
49
-
50
- // Default to minor for content changes
51
- return 'minor';
52
- }
53
-
54
- /**
55
- * Classifies severity based on semantic versioning
56
- * @returns Severity level or undefined if version format not recognized
57
- */
58
- private classifyVersionChange(oldVersion: string, newVersion: string): Severity | undefined {
59
- // Parse semver-like versions
60
- const oldParts = this.parseVersion(oldVersion);
61
- const newParts = this.parseVersion(newVersion);
62
-
63
- if (!oldParts || !newParts) {
64
- return undefined;
65
- }
66
-
67
- const [oldMajor, oldMinor, oldPatch] = oldParts;
68
- const [newMajor, newMinor, newPatch] = newParts;
69
-
70
- // Breaking: Major version increase
71
- if (newMajor > oldMajor) {
72
- return 'breaking';
73
- }
74
-
75
- // Major: Minor version increase
76
- if (newMajor === oldMajor && newMinor > oldMinor) {
77
- return 'major';
78
- }
79
-
80
- // Minor: Patch version increase or same version
81
- if (newMajor === oldMajor && newMinor === oldMinor && newPatch >= oldPatch) {
82
- return 'minor';
83
- }
84
-
85
- // Default for other cases
86
- return 'major';
87
- }
88
-
89
- /**
90
- * Parses a version string into [major, minor, patch]
91
- */
92
- private parseVersion(version: string): [number, number, number] | null {
93
- if (!version) {
94
- return null;
95
- }
96
-
97
- // Remove 'v' prefix if present
98
- const cleaned = version.replace(/^v/, '');
99
-
100
- // Match semver pattern
101
- const match = cleaned.match(/^(\d+)\.(\d+)\.(\d+)/);
102
- if (!match) {
103
- return null;
104
- }
105
-
106
- return [
107
- parseInt(match[1] || '0', 10),
108
- parseInt(match[2] || '0', 10),
109
- parseInt(match[3] || '0', 10)
110
- ];
111
- }
112
- }
package/src/types.ts DELETED
@@ -1,40 +0,0 @@
1
- /**
2
- * Common types for dependency checkers
3
- */
4
-
5
- export interface DependencySnapshot {
6
- version?: string | undefined;
7
- stateHash: string;
8
- fetchedAt: Date;
9
- metadata?: Record<string, unknown>;
10
- }
11
-
12
- export interface ChangeDetection {
13
- hasChanged: boolean;
14
- changes: string[];
15
- oldVersion?: string | undefined;
16
- newVersion?: string | undefined;
17
- diff?: unknown;
18
- severity?: 'breaking' | 'major' | 'minor';
19
- }
20
-
21
- export interface AccessConfig {
22
- url: string;
23
- accessMethod: 'github-api' | 'http' | 'openapi' | 'context7' | 'arxiv';
24
- auth?: {
25
- type: 'token' | 'basic' | 'oauth' | 'none';
26
- secret?: string;
27
- };
28
- }
29
-
30
- export interface Checker {
31
- /**
32
- * Fetches the current state of a dependency
33
- */
34
- fetch(config: AccessConfig): Promise<DependencySnapshot>;
35
-
36
- /**
37
- * Compares two snapshots to detect changes
38
- */
39
- compare(prev: DependencySnapshot, curr: DependencySnapshot): Promise<ChangeDetection>;
40
- }
@@ -1,124 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { GitHubRepoChecker } from '../../src/checkers/github-repo.js';
3
-
4
- // Mock global fetch
5
- const mockFetch = vi.fn();
6
- global.fetch = mockFetch as any;
7
-
8
- describe('GitHubRepoChecker', () => {
9
- let checker: GitHubRepoChecker;
10
-
11
- beforeEach(() => {
12
- checker = new GitHubRepoChecker();
13
- vi.clearAllMocks();
14
-
15
- // Default mock response for releases
16
- mockFetch.mockResolvedValue({
17
- ok: true,
18
- status: 200,
19
- json: async () => ({
20
- tag_name: 'v1.0.0',
21
- name: 'Release v1.0.0',
22
- published_at: '2024-01-01T00:00:00Z',
23
- body: 'Release notes',
24
- html_url: 'https://github.com/owner/repo/releases/v1.0.0'
25
- })
26
- });
27
- });
28
-
29
- describe('fetch', () => {
30
- it('should fetch latest release information', async () => {
31
- const config = {
32
- url: 'https://github.com/owner/repo',
33
- accessMethod: 'github-api' as const
34
- };
35
-
36
- const result = await checker.fetch(config);
37
-
38
- expect(result).toBeDefined();
39
- expect(result.version).toBeDefined();
40
- expect(result.stateHash).toBeDefined();
41
- expect(result.fetchedAt).toBeInstanceOf(Date);
42
- });
43
-
44
- it('should handle repositories without releases', async () => {
45
- // Mock 404 response for no releases, then mock commits response
46
- mockFetch
47
- .mockResolvedValueOnce({
48
- ok: false,
49
- status: 404
50
- })
51
- .mockResolvedValueOnce({
52
- ok: true,
53
- status: 200,
54
- json: async () => [
55
- {
56
- sha: 'abc123def456',
57
- commit: {
58
- message: 'Latest commit',
59
- author: { date: '2024-01-01T00:00:00Z' }
60
- }
61
- }
62
- ]
63
- });
64
-
65
- const config = {
66
- url: 'https://github.com/owner/no-releases',
67
- accessMethod: 'github-api' as const
68
- };
69
-
70
- const result = await checker.fetch(config);
71
-
72
- expect(result).toBeDefined();
73
- expect(result.version).toBeUndefined();
74
- });
75
-
76
- it('should throw error for invalid GitHub URL', async () => {
77
- const config = {
78
- url: 'https://example.com/not-github',
79
- accessMethod: 'github-api' as const
80
- };
81
-
82
- await expect(checker.fetch(config)).rejects.toThrow();
83
- });
84
- });
85
-
86
- describe('compare', () => {
87
- it('should detect version changes', async () => {
88
- const prev = {
89
- version: 'v1.0.0',
90
- stateHash: 'hash1',
91
- fetchedAt: new Date('2024-01-01')
92
- };
93
-
94
- const curr = {
95
- version: 'v1.1.0',
96
- stateHash: 'hash2',
97
- fetchedAt: new Date('2024-01-02')
98
- };
99
-
100
- const result = await checker.compare(prev, curr);
101
-
102
- expect(result.hasChanged).toBe(true);
103
- expect(result.changes).toContain('version');
104
- });
105
-
106
- it('should return no change when versions match', async () => {
107
- const prev = {
108
- version: 'v1.0.0',
109
- stateHash: 'hash1',
110
- fetchedAt: new Date('2024-01-01')
111
- };
112
-
113
- const curr = {
114
- version: 'v1.0.0',
115
- stateHash: 'hash1',
116
- fetchedAt: new Date('2024-01-02')
117
- };
118
-
119
- const result = await checker.compare(prev, curr);
120
-
121
- expect(result.hasChanged).toBe(false);
122
- });
123
- });
124
- });