@oculum/scanner 1.0.0
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/dist/formatters/cli-terminal.d.ts +27 -0
- package/dist/formatters/cli-terminal.d.ts.map +1 -0
- package/dist/formatters/cli-terminal.js +412 -0
- package/dist/formatters/cli-terminal.js.map +1 -0
- package/dist/formatters/github-comment.d.ts +41 -0
- package/dist/formatters/github-comment.d.ts.map +1 -0
- package/dist/formatters/github-comment.js +306 -0
- package/dist/formatters/github-comment.js.map +1 -0
- package/dist/formatters/grouping.d.ts +52 -0
- package/dist/formatters/grouping.d.ts.map +1 -0
- package/dist/formatters/grouping.js +152 -0
- package/dist/formatters/grouping.js.map +1 -0
- package/dist/formatters/index.d.ts +9 -0
- package/dist/formatters/index.d.ts.map +1 -0
- package/dist/formatters/index.js +35 -0
- package/dist/formatters/index.js.map +1 -0
- package/dist/formatters/vscode-diagnostic.d.ts +103 -0
- package/dist/formatters/vscode-diagnostic.d.ts.map +1 -0
- package/dist/formatters/vscode-diagnostic.js +151 -0
- package/dist/formatters/vscode-diagnostic.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +648 -0
- package/dist/index.js.map +1 -0
- package/dist/layer1/comments.d.ts +8 -0
- package/dist/layer1/comments.d.ts.map +1 -0
- package/dist/layer1/comments.js +203 -0
- package/dist/layer1/comments.js.map +1 -0
- package/dist/layer1/config-audit.d.ts +8 -0
- package/dist/layer1/config-audit.d.ts.map +1 -0
- package/dist/layer1/config-audit.js +252 -0
- package/dist/layer1/config-audit.js.map +1 -0
- package/dist/layer1/entropy.d.ts +8 -0
- package/dist/layer1/entropy.d.ts.map +1 -0
- package/dist/layer1/entropy.js +500 -0
- package/dist/layer1/entropy.js.map +1 -0
- package/dist/layer1/file-flags.d.ts +7 -0
- package/dist/layer1/file-flags.d.ts.map +1 -0
- package/dist/layer1/file-flags.js +112 -0
- package/dist/layer1/file-flags.js.map +1 -0
- package/dist/layer1/index.d.ts +36 -0
- package/dist/layer1/index.d.ts.map +1 -0
- package/dist/layer1/index.js +132 -0
- package/dist/layer1/index.js.map +1 -0
- package/dist/layer1/patterns.d.ts +8 -0
- package/dist/layer1/patterns.d.ts.map +1 -0
- package/dist/layer1/patterns.js +482 -0
- package/dist/layer1/patterns.js.map +1 -0
- package/dist/layer1/urls.d.ts +8 -0
- package/dist/layer1/urls.d.ts.map +1 -0
- package/dist/layer1/urls.js +296 -0
- package/dist/layer1/urls.js.map +1 -0
- package/dist/layer1/weak-crypto.d.ts +7 -0
- package/dist/layer1/weak-crypto.d.ts.map +1 -0
- package/dist/layer1/weak-crypto.js +291 -0
- package/dist/layer1/weak-crypto.js.map +1 -0
- package/dist/layer2/ai-agent-tools.d.ts +19 -0
- package/dist/layer2/ai-agent-tools.d.ts.map +1 -0
- package/dist/layer2/ai-agent-tools.js +528 -0
- package/dist/layer2/ai-agent-tools.js.map +1 -0
- package/dist/layer2/ai-endpoint-protection.d.ts +36 -0
- package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -0
- package/dist/layer2/ai-endpoint-protection.js +332 -0
- package/dist/layer2/ai-endpoint-protection.js.map +1 -0
- package/dist/layer2/ai-execution-sinks.d.ts +18 -0
- package/dist/layer2/ai-execution-sinks.d.ts.map +1 -0
- package/dist/layer2/ai-execution-sinks.js +496 -0
- package/dist/layer2/ai-execution-sinks.js.map +1 -0
- package/dist/layer2/ai-fingerprinting.d.ts +7 -0
- package/dist/layer2/ai-fingerprinting.d.ts.map +1 -0
- package/dist/layer2/ai-fingerprinting.js +654 -0
- package/dist/layer2/ai-fingerprinting.js.map +1 -0
- package/dist/layer2/ai-prompt-hygiene.d.ts +19 -0
- package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -0
- package/dist/layer2/ai-prompt-hygiene.js +356 -0
- package/dist/layer2/ai-prompt-hygiene.js.map +1 -0
- package/dist/layer2/ai-rag-safety.d.ts +21 -0
- package/dist/layer2/ai-rag-safety.d.ts.map +1 -0
- package/dist/layer2/ai-rag-safety.js +459 -0
- package/dist/layer2/ai-rag-safety.js.map +1 -0
- package/dist/layer2/ai-schema-validation.d.ts +25 -0
- package/dist/layer2/ai-schema-validation.d.ts.map +1 -0
- package/dist/layer2/ai-schema-validation.js +375 -0
- package/dist/layer2/ai-schema-validation.js.map +1 -0
- package/dist/layer2/auth-antipatterns.d.ts +20 -0
- package/dist/layer2/auth-antipatterns.d.ts.map +1 -0
- package/dist/layer2/auth-antipatterns.js +333 -0
- package/dist/layer2/auth-antipatterns.js.map +1 -0
- package/dist/layer2/byok-patterns.d.ts +12 -0
- package/dist/layer2/byok-patterns.d.ts.map +1 -0
- package/dist/layer2/byok-patterns.js +299 -0
- package/dist/layer2/byok-patterns.js.map +1 -0
- package/dist/layer2/dangerous-functions.d.ts +7 -0
- package/dist/layer2/dangerous-functions.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions.js +1375 -0
- package/dist/layer2/dangerous-functions.js.map +1 -0
- package/dist/layer2/data-exposure.d.ts +16 -0
- package/dist/layer2/data-exposure.d.ts.map +1 -0
- package/dist/layer2/data-exposure.js +279 -0
- package/dist/layer2/data-exposure.js.map +1 -0
- package/dist/layer2/framework-checks.d.ts +7 -0
- package/dist/layer2/framework-checks.d.ts.map +1 -0
- package/dist/layer2/framework-checks.js +388 -0
- package/dist/layer2/framework-checks.js.map +1 -0
- package/dist/layer2/index.d.ts +58 -0
- package/dist/layer2/index.d.ts.map +1 -0
- package/dist/layer2/index.js +380 -0
- package/dist/layer2/index.js.map +1 -0
- package/dist/layer2/logic-gates.d.ts +7 -0
- package/dist/layer2/logic-gates.d.ts.map +1 -0
- package/dist/layer2/logic-gates.js +182 -0
- package/dist/layer2/logic-gates.js.map +1 -0
- package/dist/layer2/risky-imports.d.ts +7 -0
- package/dist/layer2/risky-imports.d.ts.map +1 -0
- package/dist/layer2/risky-imports.js +161 -0
- package/dist/layer2/risky-imports.js.map +1 -0
- package/dist/layer2/variables.d.ts +8 -0
- package/dist/layer2/variables.d.ts.map +1 -0
- package/dist/layer2/variables.js +152 -0
- package/dist/layer2/variables.js.map +1 -0
- package/dist/layer3/anthropic.d.ts +83 -0
- package/dist/layer3/anthropic.d.ts.map +1 -0
- package/dist/layer3/anthropic.js +1745 -0
- package/dist/layer3/anthropic.js.map +1 -0
- package/dist/layer3/index.d.ts +24 -0
- package/dist/layer3/index.d.ts.map +1 -0
- package/dist/layer3/index.js +119 -0
- package/dist/layer3/index.js.map +1 -0
- package/dist/layer3/openai.d.ts +25 -0
- package/dist/layer3/openai.d.ts.map +1 -0
- package/dist/layer3/openai.js +238 -0
- package/dist/layer3/openai.js.map +1 -0
- package/dist/layer3/package-check.d.ts +63 -0
- package/dist/layer3/package-check.d.ts.map +1 -0
- package/dist/layer3/package-check.js +508 -0
- package/dist/layer3/package-check.js.map +1 -0
- package/dist/modes/incremental.d.ts +66 -0
- package/dist/modes/incremental.d.ts.map +1 -0
- package/dist/modes/incremental.js +200 -0
- package/dist/modes/incremental.js.map +1 -0
- package/dist/tiers.d.ts +125 -0
- package/dist/tiers.d.ts.map +1 -0
- package/dist/tiers.js +234 -0
- package/dist/tiers.js.map +1 -0
- package/dist/types.d.ts +175 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +50 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/auth-helper-detector.d.ts +56 -0
- package/dist/utils/auth-helper-detector.d.ts.map +1 -0
- package/dist/utils/auth-helper-detector.js +360 -0
- package/dist/utils/auth-helper-detector.js.map +1 -0
- package/dist/utils/context-helpers.d.ts +96 -0
- package/dist/utils/context-helpers.d.ts.map +1 -0
- package/dist/utils/context-helpers.js +493 -0
- package/dist/utils/context-helpers.js.map +1 -0
- package/dist/utils/diff-detector.d.ts +53 -0
- package/dist/utils/diff-detector.d.ts.map +1 -0
- package/dist/utils/diff-detector.js +104 -0
- package/dist/utils/diff-detector.js.map +1 -0
- package/dist/utils/diff-parser.d.ts +80 -0
- package/dist/utils/diff-parser.d.ts.map +1 -0
- package/dist/utils/diff-parser.js +202 -0
- package/dist/utils/diff-parser.js.map +1 -0
- package/dist/utils/imported-auth-detector.d.ts +37 -0
- package/dist/utils/imported-auth-detector.d.ts.map +1 -0
- package/dist/utils/imported-auth-detector.js +251 -0
- package/dist/utils/imported-auth-detector.js.map +1 -0
- package/dist/utils/middleware-detector.d.ts +55 -0
- package/dist/utils/middleware-detector.d.ts.map +1 -0
- package/dist/utils/middleware-detector.js +260 -0
- package/dist/utils/middleware-detector.js.map +1 -0
- package/dist/utils/oauth-flow-detector.d.ts +41 -0
- package/dist/utils/oauth-flow-detector.d.ts.map +1 -0
- package/dist/utils/oauth-flow-detector.js +202 -0
- package/dist/utils/oauth-flow-detector.js.map +1 -0
- package/dist/utils/path-exclusions.d.ts +55 -0
- package/dist/utils/path-exclusions.d.ts.map +1 -0
- package/dist/utils/path-exclusions.js +222 -0
- package/dist/utils/path-exclusions.js.map +1 -0
- package/dist/utils/project-context-builder.d.ts +119 -0
- package/dist/utils/project-context-builder.d.ts.map +1 -0
- package/dist/utils/project-context-builder.js +534 -0
- package/dist/utils/project-context-builder.js.map +1 -0
- package/dist/utils/registry-clients.d.ts +93 -0
- package/dist/utils/registry-clients.d.ts.map +1 -0
- package/dist/utils/registry-clients.js +273 -0
- package/dist/utils/registry-clients.js.map +1 -0
- package/dist/utils/trpc-analyzer.d.ts +78 -0
- package/dist/utils/trpc-analyzer.d.ts.map +1 -0
- package/dist/utils/trpc-analyzer.js +297 -0
- package/dist/utils/trpc-analyzer.js.map +1 -0
- package/package.json +45 -0
- package/src/__tests__/benchmark/fixtures/false-positives.ts +227 -0
- package/src/__tests__/benchmark/fixtures/index.ts +68 -0
- package/src/__tests__/benchmark/fixtures/layer1/config-audit.ts +364 -0
- package/src/__tests__/benchmark/fixtures/layer1/hardcoded-secrets.ts +173 -0
- package/src/__tests__/benchmark/fixtures/layer1/high-entropy.ts +234 -0
- package/src/__tests__/benchmark/fixtures/layer1/index.ts +31 -0
- package/src/__tests__/benchmark/fixtures/layer1/sensitive-urls.ts +90 -0
- package/src/__tests__/benchmark/fixtures/layer1/weak-crypto.ts +197 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-agent-tools.ts +170 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-endpoint-protection.ts +418 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-execution-sinks.ts +189 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-fingerprinting.ts +316 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-prompt-hygiene.ts +178 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-rag-safety.ts +184 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-schema-validation.ts +434 -0
- package/src/__tests__/benchmark/fixtures/layer2/auth-antipatterns.ts +159 -0
- package/src/__tests__/benchmark/fixtures/layer2/byok-patterns.ts +112 -0
- package/src/__tests__/benchmark/fixtures/layer2/dangerous-functions.ts +246 -0
- package/src/__tests__/benchmark/fixtures/layer2/data-exposure.ts +168 -0
- package/src/__tests__/benchmark/fixtures/layer2/framework-checks.ts +346 -0
- package/src/__tests__/benchmark/fixtures/layer2/index.ts +67 -0
- package/src/__tests__/benchmark/fixtures/layer2/injection-vulnerabilities.ts +239 -0
- package/src/__tests__/benchmark/fixtures/layer2/logic-gates.ts +246 -0
- package/src/__tests__/benchmark/fixtures/layer2/risky-imports.ts +231 -0
- package/src/__tests__/benchmark/fixtures/layer2/variables.ts +167 -0
- package/src/__tests__/benchmark/index.ts +29 -0
- package/src/__tests__/benchmark/run-benchmark.ts +144 -0
- package/src/__tests__/benchmark/run-depth-validation.ts +206 -0
- package/src/__tests__/benchmark/run-real-world-test.ts +243 -0
- package/src/__tests__/benchmark/security-benchmark-script.ts +1737 -0
- package/src/__tests__/benchmark/tier-integration-script.ts +177 -0
- package/src/__tests__/benchmark/types.ts +144 -0
- package/src/__tests__/benchmark/utils/test-runner.ts +475 -0
- package/src/__tests__/regression/known-false-positives.test.ts +467 -0
- package/src/__tests__/snapshots/__snapshots__/scan-depth.test.ts.snap +178 -0
- package/src/__tests__/snapshots/scan-depth.test.ts +258 -0
- package/src/__tests__/validation/analyze-results.ts +542 -0
- package/src/__tests__/validation/extract-for-triage.ts +146 -0
- package/src/__tests__/validation/fp-deep-analysis.ts +327 -0
- package/src/__tests__/validation/run-validation.ts +364 -0
- package/src/__tests__/validation/triage-template.md +132 -0
- package/src/formatters/cli-terminal.ts +446 -0
- package/src/formatters/github-comment.ts +382 -0
- package/src/formatters/grouping.ts +190 -0
- package/src/formatters/index.ts +47 -0
- package/src/formatters/vscode-diagnostic.ts +243 -0
- package/src/index.ts +823 -0
- package/src/layer1/comments.ts +218 -0
- package/src/layer1/config-audit.ts +289 -0
- package/src/layer1/entropy.ts +583 -0
- package/src/layer1/file-flags.ts +127 -0
- package/src/layer1/index.ts +181 -0
- package/src/layer1/patterns.ts +516 -0
- package/src/layer1/urls.ts +334 -0
- package/src/layer1/weak-crypto.ts +328 -0
- package/src/layer2/ai-agent-tools.ts +601 -0
- package/src/layer2/ai-endpoint-protection.ts +387 -0
- package/src/layer2/ai-execution-sinks.ts +580 -0
- package/src/layer2/ai-fingerprinting.ts +758 -0
- package/src/layer2/ai-prompt-hygiene.ts +411 -0
- package/src/layer2/ai-rag-safety.ts +511 -0
- package/src/layer2/ai-schema-validation.ts +421 -0
- package/src/layer2/auth-antipatterns.ts +394 -0
- package/src/layer2/byok-patterns.ts +336 -0
- package/src/layer2/dangerous-functions.ts +1563 -0
- package/src/layer2/data-exposure.ts +315 -0
- package/src/layer2/framework-checks.ts +433 -0
- package/src/layer2/index.ts +473 -0
- package/src/layer2/logic-gates.ts +206 -0
- package/src/layer2/risky-imports.ts +186 -0
- package/src/layer2/variables.ts +166 -0
- package/src/layer3/anthropic.ts +2030 -0
- package/src/layer3/index.ts +130 -0
- package/src/layer3/package-check.ts +604 -0
- package/src/modes/incremental.ts +293 -0
- package/src/tiers.ts +318 -0
- package/src/types.ts +284 -0
- package/src/utils/auth-helper-detector.ts +443 -0
- package/src/utils/context-helpers.ts +535 -0
- package/src/utils/diff-detector.ts +135 -0
- package/src/utils/diff-parser.ts +272 -0
- package/src/utils/imported-auth-detector.ts +320 -0
- package/src/utils/middleware-detector.ts +333 -0
- package/src/utils/oauth-flow-detector.ts +246 -0
- package/src/utils/path-exclusions.ts +266 -0
- package/src/utils/project-context-builder.ts +707 -0
- package/src/utils/registry-clients.ts +351 -0
- package/src/utils/trpc-analyzer.ts +382 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry Clients for Package Metadata
|
|
3
|
+
* Fetches package information from npm and PyPI registries
|
|
4
|
+
* Used by the Hallucination Firewall (Story C) to assess dependency risk
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Cache for package metadata to avoid repeated requests
|
|
8
|
+
const npmMetadataCache = new Map<string, NPMPackageMetadata | null>()
|
|
9
|
+
const pypiMetadataCache = new Map<string, PyPIPackageMetadata | null>()
|
|
10
|
+
|
|
11
|
+
// Rate limiting configuration
|
|
12
|
+
const RATE_LIMIT_DELAY_MS = 100
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* NPM Package Metadata Interface
|
|
16
|
+
*/
|
|
17
|
+
export interface NPMPackageMetadata {
|
|
18
|
+
name: string
|
|
19
|
+
version: string
|
|
20
|
+
description?: string
|
|
21
|
+
maintainers: Array<{ name: string; email?: string }>
|
|
22
|
+
time: {
|
|
23
|
+
created: string
|
|
24
|
+
modified: string
|
|
25
|
+
[version: string]: string
|
|
26
|
+
}
|
|
27
|
+
repository?: {
|
|
28
|
+
type: string
|
|
29
|
+
url: string
|
|
30
|
+
}
|
|
31
|
+
homepage?: string
|
|
32
|
+
license?: string
|
|
33
|
+
downloads?: {
|
|
34
|
+
weekly: number
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* PyPI Package Metadata Interface
|
|
40
|
+
*/
|
|
41
|
+
export interface PyPIPackageMetadata {
|
|
42
|
+
name: string
|
|
43
|
+
version: string
|
|
44
|
+
summary?: string
|
|
45
|
+
author?: string
|
|
46
|
+
authorEmail?: string
|
|
47
|
+
license?: string
|
|
48
|
+
projectUrls?: Record<string, string>
|
|
49
|
+
releaseDate?: string
|
|
50
|
+
requiresPython?: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Extracted dependency information
|
|
55
|
+
*/
|
|
56
|
+
export interface ExtractedDependency {
|
|
57
|
+
name: string
|
|
58
|
+
version?: string
|
|
59
|
+
source: 'dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies' | 'requirements'
|
|
60
|
+
line: number
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Fetch package metadata from npm registry
|
|
65
|
+
* Returns null if package doesn't exist
|
|
66
|
+
*/
|
|
67
|
+
export async function fetchNPMMetadata(packageName: string): Promise<NPMPackageMetadata | null> {
|
|
68
|
+
// Check cache first
|
|
69
|
+
if (npmMetadataCache.has(packageName)) {
|
|
70
|
+
return npmMetadataCache.get(packageName) || null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
// Fetch package info from npm registry
|
|
75
|
+
const registryResponse = await fetch(
|
|
76
|
+
`https://registry.npmjs.org/${encodeURIComponent(packageName)}`,
|
|
77
|
+
{
|
|
78
|
+
headers: {
|
|
79
|
+
'Accept': 'application/json',
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if (!registryResponse.ok) {
|
|
85
|
+
if (registryResponse.status === 404) {
|
|
86
|
+
npmMetadataCache.set(packageName, null)
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
// Non-404 error - don't cache, might be transient
|
|
90
|
+
console.warn(`[Registry] npm registry error for ${packageName}: ${registryResponse.status}`)
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const data = await registryResponse.json() as Record<string, unknown>
|
|
95
|
+
|
|
96
|
+
// Fetch download counts separately (different API)
|
|
97
|
+
let weeklyDownloads = 0
|
|
98
|
+
try {
|
|
99
|
+
const downloadsResponse = await fetch(
|
|
100
|
+
`https://api.npmjs.org/downloads/point/last-week/${encodeURIComponent(packageName)}`
|
|
101
|
+
)
|
|
102
|
+
if (downloadsResponse.ok) {
|
|
103
|
+
const downloadsData = await downloadsResponse.json() as Record<string, unknown>
|
|
104
|
+
weeklyDownloads = (downloadsData.downloads as number) || 0
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
// Download count is optional, don't fail
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const metadata: NPMPackageMetadata = {
|
|
111
|
+
name: data.name as string,
|
|
112
|
+
version: ((data['dist-tags'] as Record<string, string>)?.latest) || '',
|
|
113
|
+
description: data.description as string | undefined,
|
|
114
|
+
maintainers: (data.maintainers as Array<{ name: string; email?: string }>) || [],
|
|
115
|
+
time: (data.time as NPMPackageMetadata['time']) || { created: '', modified: '' },
|
|
116
|
+
repository: data.repository as NPMPackageMetadata['repository'],
|
|
117
|
+
homepage: data.homepage as string | undefined,
|
|
118
|
+
license: data.license as string | undefined,
|
|
119
|
+
downloads: { weekly: weeklyDownloads },
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
npmMetadataCache.set(packageName, metadata)
|
|
123
|
+
return metadata
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.warn(`[Registry] Failed to fetch npm metadata for ${packageName}:`, error)
|
|
126
|
+
// Don't cache network errors
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Fetch package metadata from PyPI registry
|
|
133
|
+
* Returns null if package doesn't exist
|
|
134
|
+
*/
|
|
135
|
+
export async function fetchPyPIMetadata(packageName: string): Promise<PyPIPackageMetadata | null> {
|
|
136
|
+
// Check cache first
|
|
137
|
+
if (pypiMetadataCache.has(packageName)) {
|
|
138
|
+
return pypiMetadataCache.get(packageName) || null
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const response = await fetch(
|
|
143
|
+
`https://pypi.org/pypi/${encodeURIComponent(packageName)}/json`,
|
|
144
|
+
{
|
|
145
|
+
headers: {
|
|
146
|
+
'Accept': 'application/json',
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
if (response.status === 404) {
|
|
153
|
+
pypiMetadataCache.set(packageName, null)
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
console.warn(`[Registry] PyPI registry error for ${packageName}: ${response.status}`)
|
|
157
|
+
return null
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const data = await response.json() as Record<string, unknown>
|
|
161
|
+
const info = (data.info || {}) as Record<string, unknown>
|
|
162
|
+
|
|
163
|
+
// Get release date from the latest version
|
|
164
|
+
let releaseDate: string | undefined
|
|
165
|
+
const releases = (data.releases as Record<string, Array<{ upload_time?: string }>>)?.[info.version as string]
|
|
166
|
+
if (releases && releases.length > 0) {
|
|
167
|
+
releaseDate = releases[0].upload_time
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const metadata: PyPIPackageMetadata = {
|
|
171
|
+
name: info.name as string,
|
|
172
|
+
version: info.version as string,
|
|
173
|
+
summary: info.summary as string | undefined,
|
|
174
|
+
author: info.author as string | undefined,
|
|
175
|
+
authorEmail: info.author_email as string | undefined,
|
|
176
|
+
license: info.license as string | undefined,
|
|
177
|
+
projectUrls: info.project_urls as Record<string, string> | undefined,
|
|
178
|
+
releaseDate,
|
|
179
|
+
requiresPython: info.requires_python as string | undefined,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
pypiMetadataCache.set(packageName, metadata)
|
|
183
|
+
return metadata
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.warn(`[Registry] Failed to fetch PyPI metadata for ${packageName}:`, error)
|
|
186
|
+
return null
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Extract dependencies from package.json content
|
|
192
|
+
*/
|
|
193
|
+
export function extractNpmDependencies(content: string): ExtractedDependency[] {
|
|
194
|
+
try {
|
|
195
|
+
const pkg = JSON.parse(content)
|
|
196
|
+
const deps: ExtractedDependency[] = []
|
|
197
|
+
const lines = content.split('\n')
|
|
198
|
+
|
|
199
|
+
const depSections = [
|
|
200
|
+
{ key: 'dependencies', source: 'dependencies' as const },
|
|
201
|
+
{ key: 'devDependencies', source: 'devDependencies' as const },
|
|
202
|
+
{ key: 'peerDependencies', source: 'peerDependencies' as const },
|
|
203
|
+
{ key: 'optionalDependencies', source: 'optionalDependencies' as const },
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
for (const { key, source } of depSections) {
|
|
207
|
+
const depsObj = pkg[key]
|
|
208
|
+
if (!depsObj || typeof depsObj !== 'object') continue
|
|
209
|
+
|
|
210
|
+
for (const [name, version] of Object.entries(depsObj)) {
|
|
211
|
+
// Find the line number for this dependency
|
|
212
|
+
const lineIndex = lines.findIndex(l => l.includes(`"${name}"`))
|
|
213
|
+
deps.push({
|
|
214
|
+
name,
|
|
215
|
+
version: version as string,
|
|
216
|
+
source,
|
|
217
|
+
line: lineIndex >= 0 ? lineIndex + 1 : 1,
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return deps
|
|
223
|
+
} catch {
|
|
224
|
+
return []
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Extract dependencies from requirements.txt content
|
|
230
|
+
*/
|
|
231
|
+
export function extractPythonRequirements(content: string): ExtractedDependency[] {
|
|
232
|
+
const deps: ExtractedDependency[] = []
|
|
233
|
+
const lines = content.split('\n')
|
|
234
|
+
|
|
235
|
+
lines.forEach((line, index) => {
|
|
236
|
+
// Skip comments and empty lines
|
|
237
|
+
const trimmed = line.trim()
|
|
238
|
+
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('-')) {
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Parse package name and optional version
|
|
243
|
+
// Formats: package, package==1.0.0, package>=1.0.0, package[extra], etc.
|
|
244
|
+
const match = trimmed.match(/^([a-zA-Z0-9_-]+)(?:\[.*?\])?(?:[=<>~!]+(.+))?/)
|
|
245
|
+
if (match) {
|
|
246
|
+
deps.push({
|
|
247
|
+
name: match[1],
|
|
248
|
+
version: match[2],
|
|
249
|
+
source: 'requirements',
|
|
250
|
+
line: index + 1,
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
return deps
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Extract dependencies from pyproject.toml content
|
|
260
|
+
*/
|
|
261
|
+
export function extractPyprojectDependencies(content: string): ExtractedDependency[] {
|
|
262
|
+
const deps: ExtractedDependency[] = []
|
|
263
|
+
const lines = content.split('\n')
|
|
264
|
+
|
|
265
|
+
let inDependencies = false
|
|
266
|
+
|
|
267
|
+
lines.forEach((line, index) => {
|
|
268
|
+
const trimmed = line.trim()
|
|
269
|
+
|
|
270
|
+
// Check for dependencies section
|
|
271
|
+
if (trimmed === '[project.dependencies]' || trimmed === 'dependencies = [') {
|
|
272
|
+
inDependencies = true
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Exit dependencies section
|
|
277
|
+
if (inDependencies && (trimmed.startsWith('[') || trimmed === ']')) {
|
|
278
|
+
inDependencies = false
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (inDependencies) {
|
|
283
|
+
// Parse dependency line: "package>=1.0.0", or package = ">=1.0.0"
|
|
284
|
+
const match = trimmed.match(/^["']?([a-zA-Z0-9_-]+)(?:\[.*?\])?(?:[=<>~!]+)?/)
|
|
285
|
+
if (match && match[1]) {
|
|
286
|
+
deps.push({
|
|
287
|
+
name: match[1],
|
|
288
|
+
version: undefined,
|
|
289
|
+
source: 'requirements',
|
|
290
|
+
line: index + 1,
|
|
291
|
+
})
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
return deps
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Determine the package file type from path
|
|
301
|
+
*/
|
|
302
|
+
export function getPackageFileType(filePath: string): 'npm' | 'python' | null {
|
|
303
|
+
const fileName = filePath.split('/').pop()?.toLowerCase() || ''
|
|
304
|
+
|
|
305
|
+
if (fileName === 'package.json' ||
|
|
306
|
+
fileName === 'package-lock.json' ||
|
|
307
|
+
fileName === 'yarn.lock' ||
|
|
308
|
+
fileName === 'pnpm-lock.yaml') {
|
|
309
|
+
return 'npm'
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (fileName === 'requirements.txt' ||
|
|
313
|
+
fileName === 'pyproject.toml' ||
|
|
314
|
+
fileName === 'pipfile' ||
|
|
315
|
+
fileName === 'pipfile.lock') {
|
|
316
|
+
return 'python'
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return null
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Calculate package age in days from creation date
|
|
324
|
+
*/
|
|
325
|
+
export function calculatePackageAgeDays(createdDate: string | undefined): number {
|
|
326
|
+
if (!createdDate) return Infinity // Unknown age, treat as old (safe)
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
const created = new Date(createdDate)
|
|
330
|
+
const now = new Date()
|
|
331
|
+
const diffMs = now.getTime() - created.getTime()
|
|
332
|
+
return Math.floor(diffMs / (1000 * 60 * 60 * 24))
|
|
333
|
+
} catch {
|
|
334
|
+
return Infinity
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Rate limiter helper - adds delay between registry requests
|
|
340
|
+
*/
|
|
341
|
+
export async function rateLimitDelay(): Promise<void> {
|
|
342
|
+
return new Promise(resolve => setTimeout(resolve, RATE_LIMIT_DELAY_MS))
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Clear all caches (useful for testing)
|
|
347
|
+
*/
|
|
348
|
+
export function clearRegistryCaches(): void {
|
|
349
|
+
npmMetadataCache.clear()
|
|
350
|
+
pypiMetadataCache.clear()
|
|
351
|
+
}
|