@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,181 @@
1
+ /**
2
+ * Generate Action
3
+ * Initial manifest generation by analyzing the repository
4
+ */
5
+
6
+ import * as core from '@actions/core';
7
+ import { join } from 'node:path';
8
+ import { Detector, GitHubCopilotProvider } from '@dependabit/detector';
9
+ import { writeManifest, type DependencyManifest } from '@dependabit/manifest';
10
+ import { createLogger, withTiming } from '../logger.js';
11
+ import { parseGenerateInputs } from '../utils/inputs.js';
12
+ import {
13
+ setGenerateOutputs,
14
+ createGenerateSummary,
15
+ createDependencyListSummary
16
+ } from '../utils/outputs.js';
17
+
18
+ /**
19
+ * Main entry point for the generate action wrapped for error handling
20
+ */
21
+ export async function run(): Promise<void> {
22
+ try {
23
+ await generateAction();
24
+ } catch (error) {
25
+ core.setFailed(error instanceof Error ? error.message : String(error));
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Generate action implementation
31
+ */
32
+ async function generateAction(): Promise<void> {
33
+ const logger = createLogger({ enableDebug: true });
34
+
35
+ try {
36
+ logger.startGroup('📋 Parsing Action Inputs');
37
+ const inputs = parseGenerateInputs();
38
+ logger.info('Action inputs parsed', {
39
+ repoPath: inputs.repoPath,
40
+ llmProvider: inputs.llmProvider,
41
+ llmModel: inputs.llmModel || 'default',
42
+ manifestPath: inputs.manifestPath
43
+ });
44
+ logger.endGroup();
45
+
46
+ // Initialize LLM provider
47
+ logger.startGroup('🤖 Initializing LLM Provider');
48
+ const llmProvider = new GitHubCopilotProvider({
49
+ ...(inputs.llmApiKey && { apiKey: inputs.llmApiKey }),
50
+ ...(inputs.llmModel && { model: inputs.llmModel })
51
+ });
52
+ logger.info('LLM provider initialized', {
53
+ provider: inputs.llmProvider,
54
+ model: inputs.llmModel || 'gpt-4',
55
+ hasApiKey: !!inputs.llmApiKey
56
+ });
57
+ logger.endGroup();
58
+
59
+ // Create detector
60
+ logger.startGroup('🔍 Detecting Dependencies');
61
+ const detector = new Detector({
62
+ repoPath: inputs.repoPath,
63
+ llmProvider
64
+ });
65
+
66
+ const result = await withTiming(logger, 'dependency-detection', async () => {
67
+ return await detector.detectDependencies();
68
+ });
69
+
70
+ logger.info('Detection complete', {
71
+ dependencyCount: result.dependencies.length,
72
+ filesScanned: result.statistics.filesScanned,
73
+ urlsFound: result.statistics.urlsFound,
74
+ llmCalls: result.statistics.llmCalls,
75
+ totalTokens: result.statistics.totalTokens
76
+ });
77
+ logger.endGroup();
78
+
79
+ // Create manifest
80
+ logger.startGroup('📄 Creating Manifest');
81
+ const manifest = await createManifest(inputs.repoPath, result.dependencies, inputs.llmProvider);
82
+
83
+ const manifestPath = join(inputs.repoPath, inputs.manifestPath);
84
+ await writeManifest(manifestPath, manifest);
85
+
86
+ logger.info('Manifest written', {
87
+ path: manifestPath,
88
+ dependencyCount: manifest.dependencies.length
89
+ });
90
+ logger.endGroup();
91
+
92
+ // Set outputs
93
+ logger.startGroup('📊 Setting Outputs');
94
+ setGenerateOutputs(manifest, inputs.manifestPath, result.statistics);
95
+ logger.endGroup();
96
+
97
+ // Create summary
98
+ logger.startGroup('📝 Creating Summary');
99
+ await createGenerateSummary(manifest, result.statistics);
100
+ await createDependencyListSummary(
101
+ manifest.dependencies.map((dep) => ({
102
+ name: dep.name,
103
+ url: dep.url,
104
+ type: dep.type,
105
+ confidence: dep.detectionConfidence
106
+ }))
107
+ );
108
+ logger.endGroup();
109
+
110
+ logger.info('✅ Generate action completed successfully');
111
+ } catch (error) {
112
+ logger.error('Generate action failed', {
113
+ error: error instanceof Error ? error.message : String(error),
114
+ stack: error instanceof Error ? error.stack : undefined
115
+ });
116
+
117
+ core.setFailed(error instanceof Error ? error.message : String(error));
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Create the manifest structure
123
+ */
124
+ async function createManifest(
125
+ repoPath: string,
126
+ dependencies: any[],
127
+ llmProvider: string
128
+ ): Promise<DependencyManifest> {
129
+ // Get repository info from GitHub context or git
130
+ const owner = process.env['GITHUB_REPOSITORY']?.split('/')[0] || 'unknown';
131
+ const name = process.env['GITHUB_REPOSITORY']?.split('/')[1] || 'unknown';
132
+ const branch = process.env['GITHUB_REF_NAME'] || 'main';
133
+ const commit = process.env['GITHUB_SHA'] || 'unknown';
134
+
135
+ // Calculate statistics
136
+ const byType: Record<string, number> = {};
137
+ const byAccessMethod: Record<string, number> = {};
138
+ const byDetectionMethod: Record<string, number> = {};
139
+ let totalConfidence = 0;
140
+
141
+ for (const dep of dependencies) {
142
+ byType[dep.type] = (byType[dep.type] || 0) + 1;
143
+ byAccessMethod[dep.accessMethod] = (byAccessMethod[dep.accessMethod] || 0) + 1;
144
+ byDetectionMethod[dep.detectionMethod] = (byDetectionMethod[dep.detectionMethod] || 0) + 1;
145
+ totalConfidence += dep.detectionConfidence;
146
+ }
147
+
148
+ const averageConfidence = dependencies.length > 0 ? totalConfidence / dependencies.length : 0;
149
+
150
+ const manifest: DependencyManifest = {
151
+ version: '1.0.0',
152
+ generatedAt: new Date().toISOString(),
153
+ generatedBy: {
154
+ action: 'dependabit',
155
+ version: '1.0.0',
156
+ llmProvider: llmProvider,
157
+ llmModel: 'gpt-4'
158
+ },
159
+ repository: {
160
+ owner,
161
+ name,
162
+ branch,
163
+ commit
164
+ },
165
+ dependencies,
166
+ statistics: {
167
+ totalDependencies: dependencies.length,
168
+ byType,
169
+ byAccessMethod,
170
+ byDetectionMethod,
171
+ averageConfidence
172
+ }
173
+ };
174
+
175
+ return manifest;
176
+ }
177
+
178
+ // Run the action
179
+ if (import.meta.url === `file://${process.argv[1]}`) {
180
+ run();
181
+ }
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Update Action
3
+ * Analyze commits and update manifest with new/removed dependencies
4
+ */
5
+
6
+ import * as core from '@actions/core';
7
+ import { join } from 'node:path';
8
+ import { existsSync } from 'node:fs';
9
+ import { readFile } from 'node:fs/promises';
10
+ import { Detector, GitHubCopilotProvider, extractDependencyChanges } from '@dependabit/detector';
11
+ import {
12
+ readManifest,
13
+ writeManifest,
14
+ mergeManifests,
15
+ type DependencyManifest
16
+ } from '@dependabit/manifest';
17
+ import { createGitHubClient, getCommitDiff } from '@dependabit/github-client';
18
+ import { createLogger, withTiming } from '../logger.js';
19
+ import { parseUpdateInputs } from '../utils/inputs.js';
20
+ import { setUpdateOutputs, createUpdateSummary } from '../utils/outputs.js';
21
+
22
+ /**
23
+ * Main entry point for the update action
24
+ */
25
+ export async function run(): Promise<void> {
26
+ const logger = createLogger({ enableDebug: true });
27
+
28
+ try {
29
+ logger.startGroup('📋 Parsing Action Inputs');
30
+ const inputs = parseUpdateInputs();
31
+ logger.info('Action inputs parsed', {
32
+ repoPath: inputs.repoPath,
33
+ manifestPath: inputs.manifestPath,
34
+ commits: inputs.commits.length > 0 ? inputs.commits : 'auto-detect'
35
+ });
36
+ logger.endGroup();
37
+
38
+ // Get repository information from environment
39
+ const repository = process.env['GITHUB_REPOSITORY'];
40
+ if (!repository) {
41
+ throw new Error('GITHUB_REPOSITORY environment variable not set');
42
+ }
43
+ const [owner, repo] = repository.split('/');
44
+ if (!owner || !repo) {
45
+ throw new Error(`Invalid GITHUB_REPOSITORY format: ${repository}`);
46
+ }
47
+
48
+ // Check if manifest exists
49
+ const manifestPath = join(inputs.repoPath, inputs.manifestPath);
50
+ if (!existsSync(manifestPath)) {
51
+ logger.info('⚠️ No existing manifest found. Run generate action first.');
52
+ core.setOutput('changes_detected', false);
53
+ core.setOutput('dependencies_added', 0);
54
+ core.setOutput('dependencies_removed', 0);
55
+ core.setOutput('total_dependencies', 0);
56
+ core.setOutput('files_analyzed', 0);
57
+ return;
58
+ }
59
+
60
+ // Read existing manifest
61
+ logger.startGroup('📄 Reading Existing Manifest');
62
+ const existingManifest = await readManifest(manifestPath);
63
+ logger.info('Manifest loaded', {
64
+ dependencyCount: existingManifest.dependencies.length,
65
+ version: existingManifest.version
66
+ });
67
+ logger.endGroup();
68
+
69
+ // Initialize GitHub client
70
+ logger.startGroup('🔗 Initializing GitHub Client');
71
+ const githubToken = process.env['GITHUB_TOKEN'];
72
+ if (!githubToken) {
73
+ throw new Error('GITHUB_TOKEN environment variable not set');
74
+ }
75
+ const client = createGitHubClient({ auth: githubToken });
76
+ logger.info('GitHub client initialized');
77
+ logger.endGroup();
78
+
79
+ // Determine commits to analyze
80
+ logger.startGroup('📊 Analyzing Commits');
81
+ let commitsToAnalyze = inputs.commits;
82
+
83
+ if (commitsToAnalyze.length === 0) {
84
+ // Auto-detect commits from the push event
85
+ const headRef = process.env['GITHUB_SHA'];
86
+
87
+ if (headRef) {
88
+ // For push events, get commits from the push payload
89
+ const eventPath = process.env['GITHUB_EVENT_PATH'];
90
+ if (eventPath) {
91
+ try {
92
+ const eventContent = await readFile(eventPath, 'utf-8');
93
+ const event = JSON.parse(eventContent);
94
+ if (event.commits && Array.isArray(event.commits)) {
95
+ commitsToAnalyze = event.commits.map((c: any) => c.id || c.sha);
96
+ logger.info('Detected commits from push event', { count: commitsToAnalyze.length });
97
+ }
98
+ } catch (error) {
99
+ logger.warning('Failed to parse GitHub event payload', {
100
+ error: String(error),
101
+ eventPath
102
+ });
103
+ }
104
+ }
105
+
106
+ // Fallback: analyze the last commit
107
+ if (commitsToAnalyze.length === 0) {
108
+ commitsToAnalyze = [headRef];
109
+ logger.info('Using HEAD commit', { sha: headRef });
110
+ }
111
+ }
112
+ }
113
+
114
+ if (commitsToAnalyze.length === 0) {
115
+ logger.info('⚠️ No commits to analyze');
116
+ core.setOutput('changes_detected', false);
117
+ return;
118
+ }
119
+
120
+ logger.info('Commits to analyze', {
121
+ count: commitsToAnalyze.length,
122
+ shas: commitsToAnalyze.slice(0, 5)
123
+ });
124
+ logger.endGroup();
125
+
126
+ // Fetch and analyze commit diffs
127
+ logger.startGroup('🔍 Analyzing Commit Diffs');
128
+ const allChangedFiles: string[] = [];
129
+ const allAddedUrls: Set<string> = new Set();
130
+ const allRemovedUrls: Set<string> = new Set();
131
+
132
+ for (const sha of commitsToAnalyze) {
133
+ const diff = await withTiming(logger, `fetch-commit-${sha.substring(0, 7)}`, async () => {
134
+ return await getCommitDiff(client, owner, repo, sha);
135
+ });
136
+
137
+ const changes = extractDependencyChanges(diff.files);
138
+
139
+ // Track changed files
140
+ for (const file of changes.changedFiles.relevantFiles) {
141
+ if (!allChangedFiles.includes(file)) {
142
+ allChangedFiles.push(file);
143
+ }
144
+ }
145
+
146
+ // Track URL changes
147
+ changes.addedUrls.forEach((url) => allAddedUrls.add(url));
148
+ changes.removedUrls.forEach((url) => allRemovedUrls.add(url));
149
+
150
+ logger.info('Commit analyzed', {
151
+ sha: sha.substring(0, 7),
152
+ filesChanged: diff.files.length,
153
+ relevantFiles: changes.changedFiles.relevantFiles.length,
154
+ addedUrls: changes.addedUrls.length,
155
+ removedUrls: changes.removedUrls.length
156
+ });
157
+ }
158
+
159
+ logger.info('All commits analyzed', {
160
+ totalChangedFiles: allChangedFiles.length,
161
+ totalAddedUrls: allAddedUrls.size,
162
+ totalRemovedUrls: allRemovedUrls.size
163
+ });
164
+ logger.endGroup();
165
+
166
+ // Re-analyze changed files if any
167
+ logger.startGroup('🔍 Re-analyzing Changed Files');
168
+ let newDependencies: DependencyManifest['dependencies'] = [];
169
+
170
+ if (allChangedFiles.length > 0) {
171
+ // Initialize LLM provider for selective analysis
172
+ const llmProvider = new GitHubCopilotProvider({
173
+ apiKey: githubToken
174
+ });
175
+
176
+ // Create detector
177
+ const detector = new Detector({
178
+ repoPath: inputs.repoPath,
179
+ llmProvider
180
+ });
181
+
182
+ const result = await withTiming(logger, 'selective-analysis', async () => {
183
+ return await detector.analyzeFiles(allChangedFiles);
184
+ });
185
+
186
+ newDependencies = result.dependencies;
187
+
188
+ logger.info('Selective analysis complete', {
189
+ filesAnalyzed: result.statistics.filesScanned,
190
+ dependenciesFound: newDependencies.length,
191
+ llmCalls: result.statistics.llmCalls
192
+ });
193
+ }
194
+ logger.endGroup();
195
+
196
+ // Create updated manifest
197
+ logger.startGroup('🔄 Merging Manifests');
198
+ const updatedManifest: DependencyManifest = {
199
+ ...existingManifest,
200
+ generatedAt: new Date().toISOString(),
201
+ generatedBy: {
202
+ action: 'dependabit-update',
203
+ version: '1.0.0',
204
+ llmProvider: 'github-copilot',
205
+ llmModel: 'gpt-4'
206
+ },
207
+ repository: {
208
+ owner,
209
+ name: repo,
210
+ branch: process.env['GITHUB_REF_NAME'] || existingManifest.repository.branch || 'main',
211
+ commit: process.env['GITHUB_SHA'] || existingManifest.repository.commit || 'unknown'
212
+ },
213
+ dependencies: newDependencies
214
+ };
215
+
216
+ // Merge with existing manifest (preserves manual entries)
217
+ const merged = mergeManifests(existingManifest, updatedManifest, {
218
+ preserveManual: true,
219
+ preserveHistory: true
220
+ });
221
+
222
+ // Mark removed dependencies
223
+ const removedUrls = Array.from(allRemovedUrls);
224
+ for (const dep of merged.dependencies) {
225
+ if (removedUrls.includes(dep.url)) {
226
+ // Mark as potentially removed (could be a false positive)
227
+ logger.info('Dependency potentially removed', {
228
+ name: dep.name,
229
+ url: dep.url
230
+ });
231
+ }
232
+ }
233
+
234
+ const dependenciesAdded = merged.dependencies.length - existingManifest.dependencies.length;
235
+ const changesDetected = dependenciesAdded !== 0 || removedUrls.length > 0;
236
+
237
+ logger.info('Manifests merged', {
238
+ before: existingManifest.dependencies.length,
239
+ after: merged.dependencies.length,
240
+ added: Math.max(0, dependenciesAdded),
241
+ manualPreserved: merged.dependencies.filter((d) => d.detectionMethod === 'manual').length
242
+ });
243
+ logger.endGroup();
244
+
245
+ // Write updated manifest
246
+ logger.startGroup('💾 Writing Updated Manifest');
247
+ await writeManifest(manifestPath, merged);
248
+ logger.info('Manifest updated', { path: manifestPath });
249
+ logger.endGroup();
250
+
251
+ // Set outputs
252
+ logger.startGroup('📊 Setting Outputs');
253
+ setUpdateOutputs(merged, existingManifest, allChangedFiles.length);
254
+ logger.endGroup();
255
+
256
+ // Create summary
257
+ logger.startGroup('📝 Creating Summary');
258
+ await createUpdateSummary(existingManifest, merged, {
259
+ commitsAnalyzed: commitsToAnalyze.length,
260
+ filesChanged: allChangedFiles.length,
261
+ urlsAdded: allAddedUrls.size,
262
+ urlsRemoved: allRemovedUrls.size
263
+ });
264
+ logger.endGroup();
265
+
266
+ if (changesDetected) {
267
+ logger.info('✅ Update action completed with changes');
268
+ } else {
269
+ logger.info('✅ Update action completed - no changes detected');
270
+ }
271
+ } catch (error) {
272
+ logger.error('Update action failed', {
273
+ error: error instanceof Error ? error.message : String(error),
274
+ stack: error instanceof Error ? error.stack : undefined
275
+ });
276
+
277
+ core.setFailed(error instanceof Error ? error.message : String(error));
278
+ }
279
+ }
280
+
281
+ // Run the action
282
+ if (import.meta.url === `file://${process.argv[1]}`) {
283
+ run();
284
+ }