@dependabit/action 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.
Files changed (92) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +225 -0
  4. package/action.yml +85 -0
  5. package/dist/actions/check.d.ts +33 -0
  6. package/dist/actions/check.d.ts.map +1 -0
  7. package/dist/actions/check.js +162 -0
  8. package/dist/actions/check.js.map +1 -0
  9. package/dist/actions/generate.d.ts +9 -0
  10. package/dist/actions/generate.d.ts.map +1 -0
  11. package/dist/actions/generate.js +152 -0
  12. package/dist/actions/generate.js.map +1 -0
  13. package/dist/actions/update.d.ts +9 -0
  14. package/dist/actions/update.d.ts.map +1 -0
  15. package/dist/actions/update.js +246 -0
  16. package/dist/actions/update.js.map +1 -0
  17. package/dist/actions/validate.d.ts +33 -0
  18. package/dist/actions/validate.d.ts.map +1 -0
  19. package/dist/actions/validate.js +226 -0
  20. package/dist/actions/validate.js.map +1 -0
  21. package/dist/index.d.ts +8 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +35 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/logger.d.ts +114 -0
  26. package/dist/logger.d.ts.map +1 -0
  27. package/dist/logger.js +154 -0
  28. package/dist/logger.js.map +1 -0
  29. package/dist/utils/agent-config.d.ts +31 -0
  30. package/dist/utils/agent-config.d.ts.map +1 -0
  31. package/dist/utils/agent-config.js +42 -0
  32. package/dist/utils/agent-config.js.map +1 -0
  33. package/dist/utils/agent-router.d.ts +33 -0
  34. package/dist/utils/agent-router.d.ts.map +1 -0
  35. package/dist/utils/agent-router.js +57 -0
  36. package/dist/utils/agent-router.js.map +1 -0
  37. package/dist/utils/errors.d.ts +51 -0
  38. package/dist/utils/errors.d.ts.map +1 -0
  39. package/dist/utils/errors.js +219 -0
  40. package/dist/utils/errors.js.map +1 -0
  41. package/dist/utils/inputs.d.ts +35 -0
  42. package/dist/utils/inputs.d.ts.map +1 -0
  43. package/dist/utils/inputs.js +47 -0
  44. package/dist/utils/inputs.js.map +1 -0
  45. package/dist/utils/metrics.d.ts +66 -0
  46. package/dist/utils/metrics.d.ts.map +1 -0
  47. package/dist/utils/metrics.js +116 -0
  48. package/dist/utils/metrics.js.map +1 -0
  49. package/dist/utils/outputs.d.ts +43 -0
  50. package/dist/utils/outputs.d.ts.map +1 -0
  51. package/dist/utils/outputs.js +146 -0
  52. package/dist/utils/outputs.js.map +1 -0
  53. package/dist/utils/performance.d.ts +100 -0
  54. package/dist/utils/performance.d.ts.map +1 -0
  55. package/dist/utils/performance.js +185 -0
  56. package/dist/utils/performance.js.map +1 -0
  57. package/dist/utils/reporter.d.ts +43 -0
  58. package/dist/utils/reporter.d.ts.map +1 -0
  59. package/dist/utils/reporter.js +122 -0
  60. package/dist/utils/reporter.js.map +1 -0
  61. package/dist/utils/secrets.d.ts +45 -0
  62. package/dist/utils/secrets.d.ts.map +1 -0
  63. package/dist/utils/secrets.js +94 -0
  64. package/dist/utils/secrets.js.map +1 -0
  65. package/package.json +45 -0
  66. package/src/actions/check.ts +223 -0
  67. package/src/actions/generate.ts +181 -0
  68. package/src/actions/update.ts +284 -0
  69. package/src/actions/validate.ts +292 -0
  70. package/src/index.ts +43 -0
  71. package/src/logger.test.ts +200 -0
  72. package/src/logger.ts +210 -0
  73. package/src/utils/agent-config.ts +61 -0
  74. package/src/utils/agent-router.ts +67 -0
  75. package/src/utils/errors.ts +251 -0
  76. package/src/utils/inputs.ts +75 -0
  77. package/src/utils/metrics.ts +169 -0
  78. package/src/utils/outputs.ts +202 -0
  79. package/src/utils/performance.ts +248 -0
  80. package/src/utils/reporter.ts +169 -0
  81. package/src/utils/secrets.ts +124 -0
  82. package/test/actions/check.test.ts +216 -0
  83. package/test/actions/generate.test.ts +82 -0
  84. package/test/actions/update.test.ts +70 -0
  85. package/test/actions/validate.test.ts +257 -0
  86. package/test/utils/agent-config.test.ts +112 -0
  87. package/test/utils/agent-router.test.ts +129 -0
  88. package/test/utils/metrics.test.ts +221 -0
  89. package/test/utils/reporter.test.ts +196 -0
  90. package/test/utils/secrets.test.ts +217 -0
  91. package/tsconfig.json +15 -0
  92. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,221 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { MetricsCalculator } from '../../src/utils/metrics.js';
3
+
4
+ describe('MetricsCalculator', () => {
5
+ let calculator: MetricsCalculator;
6
+
7
+ beforeEach(() => {
8
+ calculator = new MetricsCalculator();
9
+ });
10
+
11
+ describe('constructor', () => {
12
+ it('should create calculator instance', () => {
13
+ expect(calculator).toBeInstanceOf(MetricsCalculator);
14
+ });
15
+
16
+ it('should accept custom window size', () => {
17
+ const calc = new MetricsCalculator({ windowDays: 60 });
18
+ expect(calc).toBeInstanceOf(MetricsCalculator);
19
+ });
20
+ });
21
+
22
+ describe('calculateFalsePositiveRate', () => {
23
+ it('should calculate rate from feedback data', () => {
24
+ const feedback = {
25
+ truePositives: 90,
26
+ falsePositives: 10,
27
+ total: 100
28
+ };
29
+
30
+ const rate = calculator.calculateFalsePositiveRate(feedback);
31
+
32
+ expect(rate).toBe(0.1); // 10%
33
+ });
34
+
35
+ it('should return 0 for no feedback', () => {
36
+ const feedback = {
37
+ truePositives: 0,
38
+ falsePositives: 0,
39
+ total: 0
40
+ };
41
+
42
+ const rate = calculator.calculateFalsePositiveRate(feedback);
43
+
44
+ expect(rate).toBe(0);
45
+ });
46
+
47
+ it('should handle 100% false positives', () => {
48
+ const feedback = {
49
+ truePositives: 0,
50
+ falsePositives: 50,
51
+ total: 50
52
+ };
53
+
54
+ const rate = calculator.calculateFalsePositiveRate(feedback);
55
+
56
+ expect(rate).toBe(1.0);
57
+ });
58
+ });
59
+
60
+ describe('calculateRollingAverage', () => {
61
+ it('should calculate 30-day rolling average', () => {
62
+ const referenceDate = new Date('2026-01-25');
63
+ const dataPoints = [
64
+ { date: '2026-01-01', falsePositiveRate: 0.1 },
65
+ { date: '2026-01-10', falsePositiveRate: 0.15 },
66
+ { date: '2026-01-20', falsePositiveRate: 0.05 }
67
+ ];
68
+
69
+ const average = calculator.calculateRollingAverage(dataPoints, 30, referenceDate);
70
+
71
+ expect(average).toBeCloseTo(0.1, 2); // (0.1 + 0.15 + 0.05) / 3
72
+ });
73
+
74
+ it('should return 0 for empty data', () => {
75
+ const average = calculator.calculateRollingAverage([]);
76
+ expect(average).toBe(0);
77
+ });
78
+
79
+ it('should filter data outside window', () => {
80
+ const now = new Date('2026-01-31');
81
+ const dataPoints = [
82
+ { date: '2025-12-15', falsePositiveRate: 0.5 }, // Outside 30-day window
83
+ { date: '2026-01-20', falsePositiveRate: 0.1 } // Inside window
84
+ ];
85
+
86
+ const average = calculator.calculateRollingAverage(dataPoints, 30, now);
87
+
88
+ expect(average).toBe(0.1); // Only includes recent data
89
+ });
90
+ });
91
+
92
+ describe('getTrend', () => {
93
+ it('should detect improving trend', () => {
94
+ const dataPoints = [
95
+ { date: '2026-01-01', falsePositiveRate: 0.2 },
96
+ { date: '2026-01-10', falsePositiveRate: 0.15 },
97
+ { date: '2026-01-20', falsePositiveRate: 0.1 },
98
+ { date: '2026-01-30', falsePositiveRate: 0.05 }
99
+ ];
100
+
101
+ const trend = calculator.getTrend(dataPoints);
102
+
103
+ expect(trend.direction).toBe('improving');
104
+ expect(trend.slope).toBeLessThan(0);
105
+ });
106
+
107
+ it('should detect worsening trend', () => {
108
+ const dataPoints = [
109
+ { date: '2026-01-01', falsePositiveRate: 0.05 },
110
+ { date: '2026-01-10', falsePositiveRate: 0.1 },
111
+ { date: '2026-01-20', falsePositiveRate: 0.15 },
112
+ { date: '2026-01-30', falsePositiveRate: 0.2 }
113
+ ];
114
+
115
+ const trend = calculator.getTrend(dataPoints);
116
+
117
+ expect(trend.direction).toBe('worsening');
118
+ expect(trend.slope).toBeGreaterThan(0);
119
+ });
120
+
121
+ it('should detect stable trend', () => {
122
+ const dataPoints = [
123
+ { date: '2026-01-01', falsePositiveRate: 0.1 },
124
+ { date: '2026-01-10', falsePositiveRate: 0.1 },
125
+ { date: '2026-01-20', falsePositiveRate: 0.1 },
126
+ { date: '2026-01-30', falsePositiveRate: 0.1 }
127
+ ];
128
+
129
+ const trend = calculator.getTrend(dataPoints);
130
+
131
+ expect(trend.direction).toBe('stable');
132
+ expect(Math.abs(trend.slope)).toBeLessThan(0.01);
133
+ });
134
+
135
+ it('should return no-data for insufficient data', () => {
136
+ const dataPoints = [{ date: '2026-01-01', falsePositiveRate: 0.1 }];
137
+
138
+ const trend = calculator.getTrend(dataPoints);
139
+
140
+ expect(trend.direction).toBe('no-data');
141
+ });
142
+ });
143
+
144
+ describe('checkThreshold', () => {
145
+ it('should pass when rate is below threshold', () => {
146
+ const result = calculator.checkThreshold(0.05, 0.1);
147
+
148
+ expect(result.passed).toBe(true);
149
+ expect(result.rate).toBe(0.05);
150
+ expect(result.threshold).toBe(0.1);
151
+ });
152
+
153
+ it('should fail when rate exceeds threshold', () => {
154
+ const result = calculator.checkThreshold(0.15, 0.1);
155
+
156
+ expect(result.passed).toBe(false);
157
+ expect(result.rate).toBe(0.15);
158
+ expect(result.threshold).toBe(0.1);
159
+ });
160
+
161
+ it('should use default threshold of 0.1', () => {
162
+ const result = calculator.checkThreshold(0.05);
163
+
164
+ expect(result.threshold).toBe(0.1);
165
+ });
166
+ });
167
+
168
+ describe('generateReport', () => {
169
+ it('should generate comprehensive metrics report', () => {
170
+ const feedback = {
171
+ truePositives: 90,
172
+ falsePositives: 10,
173
+ total: 100
174
+ };
175
+
176
+ const history = [
177
+ { date: '2026-01-01', falsePositiveRate: 0.15 },
178
+ { date: '2026-01-15', falsePositiveRate: 0.12 },
179
+ { date: '2026-01-30', falsePositiveRate: 0.1 }
180
+ ];
181
+
182
+ const report = calculator.generateReport(feedback, history);
183
+
184
+ expect(report.currentRate).toBe(0.1);
185
+ expect(report.rollingAverage).toBeCloseTo(0.123, 1); // Less precise check
186
+ expect(report.trend.direction).toBe('improving');
187
+ expect(report.thresholdCheck.passed).toBe(false); // Average > 0.1 threshold
188
+ expect(report.totalFeedback).toBe(100);
189
+ });
190
+
191
+ it('should handle empty history', () => {
192
+ const feedback = {
193
+ truePositives: 95,
194
+ falsePositives: 5,
195
+ total: 100
196
+ };
197
+
198
+ const report = calculator.generateReport(feedback, []);
199
+
200
+ expect(report.currentRate).toBe(0.05);
201
+ expect(report.rollingAverage).toBe(0);
202
+ expect(report.trend.direction).toBe('no-data');
203
+ });
204
+ });
205
+
206
+ describe('formatRate', () => {
207
+ it('should format rate as percentage', () => {
208
+ expect(calculator.formatRate(0.1)).toBe('10.0%');
209
+ expect(calculator.formatRate(0.05)).toBe('5.0%');
210
+ expect(calculator.formatRate(0.123)).toBe('12.3%');
211
+ });
212
+
213
+ it('should handle zero', () => {
214
+ expect(calculator.formatRate(0)).toBe('0.0%');
215
+ });
216
+
217
+ it('should handle 100%', () => {
218
+ expect(calculator.formatRate(1.0)).toBe('100.0%');
219
+ });
220
+ });
221
+ });
@@ -0,0 +1,196 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { SummaryReporter } from '../../src/utils/reporter.js';
3
+
4
+ describe('SummaryReporter', () => {
5
+ let reporter: SummaryReporter;
6
+
7
+ beforeEach(() => {
8
+ reporter = new SummaryReporter();
9
+ });
10
+
11
+ describe('generateSummary', () => {
12
+ it('should generate summary for dependency changes', () => {
13
+ const changes = [
14
+ {
15
+ dependency: {
16
+ id: 'dep1',
17
+ name: 'Test Dependency',
18
+ url: 'https://github.com/owner/repo'
19
+ },
20
+ severity: 'major',
21
+ changes: ['version'],
22
+ oldVersion: 'v1.0.0',
23
+ newVersion: 'v1.1.0'
24
+ }
25
+ ];
26
+
27
+ const summary = reporter.generateSummary(changes);
28
+
29
+ expect(summary).toBeDefined();
30
+ expect(summary).toContain('Test Dependency');
31
+ expect(summary).toContain('v1.0.0');
32
+ expect(summary).toContain('v1.1.0');
33
+ expect(summary).toContain('Major');
34
+ });
35
+
36
+ it('should handle multiple changes', () => {
37
+ const changes = [
38
+ {
39
+ dependency: { id: 'dep1', name: 'Dep 1', url: 'https://example.com/1' },
40
+ severity: 'breaking',
41
+ changes: ['version'],
42
+ oldVersion: 'v1.0.0',
43
+ newVersion: 'v2.0.0'
44
+ },
45
+ {
46
+ dependency: { id: 'dep2', name: 'Dep 2', url: 'https://example.com/2' },
47
+ severity: 'minor',
48
+ changes: ['content']
49
+ }
50
+ ];
51
+
52
+ const summary = reporter.generateSummary(changes);
53
+
54
+ expect(summary).toContain('Dep 1');
55
+ expect(summary).toContain('Dep 2');
56
+ expect(summary).toContain('Breaking');
57
+ expect(summary).toContain('Minor');
58
+ });
59
+
60
+ it('should format breaking changes prominently', () => {
61
+ const changes = [
62
+ {
63
+ dependency: { id: 'dep1', name: 'Breaking Dep', url: 'https://example.com' },
64
+ severity: 'breaking',
65
+ changes: ['version'],
66
+ oldVersion: 'v1.0.0',
67
+ newVersion: 'v2.0.0'
68
+ }
69
+ ];
70
+
71
+ const summary = reporter.generateSummary(changes);
72
+
73
+ expect(summary).toMatch(/breaking|⚠️|WARNING/i);
74
+ });
75
+
76
+ it('should include links to dependency URLs', () => {
77
+ const changes = [
78
+ {
79
+ dependency: {
80
+ id: 'dep1',
81
+ name: 'Test Dep',
82
+ url: 'https://github.com/owner/repo'
83
+ },
84
+ severity: 'major',
85
+ changes: ['version']
86
+ }
87
+ ];
88
+
89
+ const summary = reporter.generateSummary(changes);
90
+
91
+ expect(summary).toContain('https://github.com/owner/repo');
92
+ });
93
+
94
+ it('should handle empty changes', () => {
95
+ const summary = reporter.generateSummary([]);
96
+
97
+ expect(summary).toBeDefined();
98
+ expect(summary).toMatch(/no changes|up to date/i);
99
+ });
100
+ });
101
+
102
+ describe('generateIssueBody', () => {
103
+ it('should generate detailed issue body', () => {
104
+ const change = {
105
+ dependency: {
106
+ id: 'dep1',
107
+ name: 'Test Package',
108
+ url: 'https://github.com/owner/repo',
109
+ type: 'reference-implementation'
110
+ },
111
+ severity: 'major',
112
+ changes: ['version'],
113
+ oldVersion: 'v1.0.0',
114
+ newVersion: 'v1.1.0',
115
+ releaseNotes: 'New features added'
116
+ };
117
+
118
+ const body = reporter.generateIssueBody(change);
119
+
120
+ expect(body).toContain('Test Package');
121
+ expect(body).toContain('v1.0.0');
122
+ expect(body).toContain('v1.1.0');
123
+ expect(body).toContain('New features added');
124
+ expect(body).toContain('https://github.com/owner/repo');
125
+ });
126
+
127
+ it('should include change details', () => {
128
+ const change = {
129
+ dependency: {
130
+ id: 'dep1',
131
+ name: 'Test',
132
+ url: 'https://example.com'
133
+ },
134
+ severity: 'minor',
135
+ changes: ['content', 'metadata'],
136
+ diff: 'Content has been updated'
137
+ };
138
+
139
+ const body = reporter.generateIssueBody(change);
140
+
141
+ expect(body).toContain('content');
142
+ expect(body).toContain('metadata');
143
+ });
144
+
145
+ it('should format for breaking changes', () => {
146
+ const change = {
147
+ dependency: {
148
+ id: 'dep1',
149
+ name: 'Breaking Package',
150
+ url: 'https://example.com'
151
+ },
152
+ severity: 'breaking',
153
+ changes: ['version'],
154
+ oldVersion: 'v1.0.0',
155
+ newVersion: 'v2.0.0'
156
+ };
157
+
158
+ const body = reporter.generateIssueBody(change);
159
+
160
+ expect(body).toMatch(/breaking|⚠️|action required/i);
161
+ });
162
+ });
163
+
164
+ describe('formatChangeSummary', () => {
165
+ it('should format version changes', () => {
166
+ const result = reporter.formatChangeSummary({
167
+ changes: ['version'],
168
+ oldVersion: 'v1.0.0',
169
+ newVersion: 'v1.1.0'
170
+ });
171
+
172
+ expect(result).toContain('v1.0.0');
173
+ expect(result).toContain('v1.1.0');
174
+ expect(result).toContain('→');
175
+ });
176
+
177
+ it('should format content changes', () => {
178
+ const result = reporter.formatChangeSummary({
179
+ changes: ['content'],
180
+ contentDiff: '50 lines changed'
181
+ });
182
+
183
+ expect(result).toContain('content');
184
+ });
185
+
186
+ it('should handle multiple change types', () => {
187
+ const result = reporter.formatChangeSummary({
188
+ changes: ['version', 'content', 'metadata']
189
+ });
190
+
191
+ expect(result).toContain('version');
192
+ expect(result).toContain('content');
193
+ expect(result).toContain('metadata');
194
+ });
195
+ });
196
+ });
@@ -0,0 +1,217 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { SecretResolver } from '../../src/utils/secrets';
3
+
4
+ describe('SecretResolver', () => {
5
+ const originalEnv = process.env;
6
+
7
+ beforeEach(() => {
8
+ vi.clearAllMocks();
9
+ process.env = { ...originalEnv };
10
+ });
11
+
12
+ afterEach(() => {
13
+ process.env = originalEnv;
14
+ });
15
+
16
+ describe('constructor', () => {
17
+ it('should create resolver instance', () => {
18
+ const resolver = new SecretResolver();
19
+ expect(resolver).toBeInstanceOf(SecretResolver);
20
+ });
21
+
22
+ it('should accept custom prefix', () => {
23
+ const resolver = new SecretResolver({ prefix: 'CUSTOM_' });
24
+ expect(resolver).toBeInstanceOf(SecretResolver);
25
+ });
26
+ });
27
+
28
+ describe('resolve', () => {
29
+ it('should resolve secret from environment variable', async () => {
30
+ process.env.GITHUB_TOKEN = 'ghp_test123';
31
+ const resolver = new SecretResolver();
32
+
33
+ const value = await resolver.resolve('GITHUB_TOKEN');
34
+ expect(value).toBe('ghp_test123');
35
+ });
36
+
37
+ it('should resolve secret with custom prefix', async () => {
38
+ process.env.CUSTOM_API_KEY = 'api_key_123';
39
+ const resolver = new SecretResolver({ prefix: 'CUSTOM_' });
40
+
41
+ const value = await resolver.resolve('API_KEY');
42
+ expect(value).toBe('api_key_123');
43
+ });
44
+
45
+ it('should throw error for missing secret', async () => {
46
+ const resolver = new SecretResolver();
47
+
48
+ await expect(resolver.resolve('MISSING_SECRET')).rejects.toThrow(
49
+ 'Secret MISSING_SECRET not found'
50
+ );
51
+ });
52
+
53
+ it('should resolve from GitHub Secrets in Actions context', async () => {
54
+ process.env.GITHUB_ACTIONS = 'true';
55
+ process.env.INPUT_API_TOKEN = 'secret_from_action';
56
+ const resolver = new SecretResolver();
57
+
58
+ const value = await resolver.resolve('INPUT_API_TOKEN');
59
+ expect(value).toBe('secret_from_action');
60
+ });
61
+
62
+ it('should handle secret reference format', async () => {
63
+ process.env.DB_PASSWORD = 'secure_password';
64
+ const resolver = new SecretResolver();
65
+
66
+ const value = await resolver.resolve('${{ secrets.DB_PASSWORD }}');
67
+ expect(value).toBe('secure_password');
68
+ });
69
+ });
70
+
71
+ describe('resolveMultiple', () => {
72
+ it('should resolve multiple secrets', async () => {
73
+ process.env.SECRET_1 = 'value1';
74
+ process.env.SECRET_2 = 'value2';
75
+ process.env.SECRET_3 = 'value3';
76
+
77
+ const resolver = new SecretResolver();
78
+ const secrets = await resolver.resolveMultiple(['SECRET_1', 'SECRET_2', 'SECRET_3']);
79
+
80
+ expect(secrets).toEqual({
81
+ SECRET_1: 'value1',
82
+ SECRET_2: 'value2',
83
+ SECRET_3: 'value3'
84
+ });
85
+ });
86
+
87
+ it('should throw error if any secret is missing', async () => {
88
+ process.env.SECRET_1 = 'value1';
89
+
90
+ const resolver = new SecretResolver();
91
+
92
+ await expect(resolver.resolveMultiple(['SECRET_1', 'MISSING'])).rejects.toThrow(
93
+ 'Secret MISSING not found'
94
+ );
95
+ });
96
+
97
+ it('should resolve partial secrets with allowPartial option', async () => {
98
+ process.env.SECRET_1 = 'value1';
99
+
100
+ const resolver = new SecretResolver();
101
+ const secrets = await resolver.resolveMultiple(['SECRET_1', 'MISSING'], {
102
+ allowPartial: true
103
+ });
104
+
105
+ expect(secrets).toEqual({
106
+ SECRET_1: 'value1'
107
+ });
108
+ });
109
+ });
110
+
111
+ describe('resolveDependencyAuth', () => {
112
+ it('should resolve per-dependency authentication', async () => {
113
+ process.env.NPM_TOKEN = 'npm_token_123';
114
+ process.env.GITHUB_TOKEN = 'ghp_token_456';
115
+
116
+ const resolver = new SecretResolver();
117
+ const config = {
118
+ 'registry.npmjs.org': { secretName: 'NPM_TOKEN' },
119
+ 'github.com': { secretName: 'GITHUB_TOKEN' }
120
+ };
121
+
122
+ const auth = await resolver.resolveDependencyAuth(config);
123
+
124
+ expect(auth).toEqual({
125
+ 'registry.npmjs.org': 'npm_token_123',
126
+ 'github.com': 'ghp_token_456'
127
+ });
128
+ });
129
+
130
+ it('should handle missing dependency auth gracefully', async () => {
131
+ const resolver = new SecretResolver();
132
+ const config = {
133
+ 'registry.npmjs.org': { secretName: 'MISSING_TOKEN' }
134
+ };
135
+
136
+ await expect(resolver.resolveDependencyAuth(config)).rejects.toThrow(
137
+ 'Secret MISSING_TOKEN not found'
138
+ );
139
+ });
140
+ });
141
+
142
+ describe('validate', () => {
143
+ it('should validate secret name format', () => {
144
+ const resolver = new SecretResolver();
145
+
146
+ expect(resolver.validate('VALID_NAME')).toBe(true);
147
+ expect(resolver.validate('VALID_NAME_123')).toBe(true);
148
+ });
149
+
150
+ it('should reject invalid secret names', () => {
151
+ const resolver = new SecretResolver();
152
+
153
+ expect(resolver.validate('invalid-name')).toBe(false);
154
+ expect(resolver.validate('invalid name')).toBe(false);
155
+ expect(resolver.validate('123_INVALID')).toBe(false);
156
+ });
157
+
158
+ it('should validate GitHub Actions secret reference', () => {
159
+ const resolver = new SecretResolver();
160
+
161
+ expect(resolver.validate('${{ secrets.MY_SECRET }}')).toBe(true);
162
+ });
163
+ });
164
+
165
+ describe('security', () => {
166
+ it('should not log secret values', async () => {
167
+ process.env.SECRET_TOKEN = 'secret_value';
168
+ const consoleSpy = vi.spyOn(console, 'log');
169
+
170
+ const resolver = new SecretResolver();
171
+ await resolver.resolve('SECRET_TOKEN');
172
+
173
+ const logs = consoleSpy.mock.calls.flat().join(' ');
174
+ expect(logs).not.toContain('secret_value');
175
+
176
+ consoleSpy.mockRestore();
177
+ });
178
+
179
+ it('should mask secrets in error messages', async () => {
180
+ process.env.SECRET_TOKEN = 'secret_value';
181
+ const resolver = new SecretResolver();
182
+
183
+ try {
184
+ await resolver.resolve('INVALID_SECRET');
185
+ } catch (error) {
186
+ expect((error as Error).message).not.toContain('secret_value');
187
+ }
188
+ });
189
+ });
190
+
191
+ describe('caching', () => {
192
+ it('should cache resolved secrets', async () => {
193
+ process.env.CACHED_SECRET = 'cached_value';
194
+ const resolver = new SecretResolver({ enableCache: true });
195
+
196
+ const value1 = await resolver.resolve('CACHED_SECRET');
197
+ process.env.CACHED_SECRET = 'new_value';
198
+ const value2 = await resolver.resolve('CACHED_SECRET');
199
+
200
+ expect(value1).toBe('cached_value');
201
+ expect(value2).toBe('cached_value'); // Should return cached value
202
+ });
203
+
204
+ it('should clear cache on demand', async () => {
205
+ process.env.CACHED_SECRET = 'cached_value';
206
+ const resolver = new SecretResolver({ enableCache: true });
207
+
208
+ await resolver.resolve('CACHED_SECRET');
209
+ process.env.CACHED_SECRET = 'new_value';
210
+
211
+ resolver.clearCache();
212
+ const value = await resolver.resolve('CACHED_SECRET');
213
+
214
+ expect(value).toBe('new_value');
215
+ });
216
+ });
217
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"],
8
+ "exclude": ["src/**/*.test.ts", "src/**/*.spec.ts"],
9
+ "references": [
10
+ { "path": "../detector" },
11
+ { "path": "../github-client" },
12
+ { "path": "../manifest" },
13
+ { "path": "../monitor" }
14
+ ]
15
+ }