@rankcli/agent-runtime 0.0.8 → 0.0.11

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 (49) hide show
  1. package/README.md +90 -196
  2. package/dist/analyzer-GMURJADU.mjs +7 -0
  3. package/dist/chunk-2JADKV3Z.mjs +244 -0
  4. package/dist/chunk-3ZSCLNTW.mjs +557 -0
  5. package/dist/chunk-4E4MQOSP.mjs +374 -0
  6. package/dist/chunk-6BWS3CLP.mjs +16 -0
  7. package/dist/chunk-AK2IC22C.mjs +206 -0
  8. package/dist/chunk-K6VSXDD6.mjs +293 -0
  9. package/dist/chunk-M27NQCWW.mjs +303 -0
  10. package/dist/{chunk-YNZYHEYM.mjs → chunk-PJLNXOLN.mjs} +0 -14
  11. package/dist/chunk-VSQD74I7.mjs +474 -0
  12. package/dist/core-web-vitals-analyzer-TE6LQJMS.mjs +7 -0
  13. package/dist/geo-analyzer-D47LTMMA.mjs +25 -0
  14. package/dist/image-optimization-analyzer-XP4OQGRP.mjs +9 -0
  15. package/dist/index.d.mts +1523 -17
  16. package/dist/index.d.ts +1523 -17
  17. package/dist/index.js +9582 -2664
  18. package/dist/index.mjs +4812 -380
  19. package/dist/internal-linking-analyzer-MRMBV7NM.mjs +9 -0
  20. package/dist/mobile-seo-analyzer-67HNQ7IO.mjs +7 -0
  21. package/dist/security-headers-analyzer-3ZUQARS5.mjs +9 -0
  22. package/dist/structured-data-analyzer-2I4NQAUP.mjs +9 -0
  23. package/package.json +2 -2
  24. package/src/analyzers/core-web-vitals-analyzer.test.ts +236 -0
  25. package/src/analyzers/core-web-vitals-analyzer.ts +557 -0
  26. package/src/analyzers/geo-analyzer.test.ts +310 -0
  27. package/src/analyzers/geo-analyzer.ts +814 -0
  28. package/src/analyzers/image-optimization-analyzer.test.ts +145 -0
  29. package/src/analyzers/image-optimization-analyzer.ts +348 -0
  30. package/src/analyzers/index.ts +233 -0
  31. package/src/analyzers/internal-linking-analyzer.test.ts +141 -0
  32. package/src/analyzers/internal-linking-analyzer.ts +419 -0
  33. package/src/analyzers/mobile-seo-analyzer.test.ts +140 -0
  34. package/src/analyzers/mobile-seo-analyzer.ts +455 -0
  35. package/src/analyzers/security-headers-analyzer.test.ts +115 -0
  36. package/src/analyzers/security-headers-analyzer.ts +318 -0
  37. package/src/analyzers/structured-data-analyzer.test.ts +210 -0
  38. package/src/analyzers/structured-data-analyzer.ts +590 -0
  39. package/src/audit/engine.ts +3 -3
  40. package/src/audit/types.ts +3 -2
  41. package/src/fixer/framework-fixes.test.ts +489 -0
  42. package/src/fixer/framework-fixes.ts +3418 -0
  43. package/src/fixer/index.ts +1 -0
  44. package/src/fixer/schemas.ts +971 -0
  45. package/src/frameworks/detector.ts +642 -114
  46. package/src/frameworks/suggestion-engine.ts +38 -1
  47. package/src/index.ts +6 -0
  48. package/src/types.ts +15 -1
  49. package/dist/analyzer-2CSWIQGD.mjs +0 -6
@@ -3,11 +3,15 @@
3
3
  *
4
4
  * Detects web frameworks from:
5
5
  * 1. HTML signatures (data attributes, meta tags, scripts)
6
- * 2. package.json dependencies
6
+ * 2. package.json / Gemfile / requirements.txt / composer.json / etc.
7
7
  * 3. File structure patterns
8
+ * 4. HTTP response headers
9
+ *
10
+ * Supports 25+ frameworks across JavaScript, Ruby, Python, PHP, Java, C#, Go, and Elixir.
8
11
  */
9
12
 
10
13
  export type Framework =
14
+ // JavaScript/TypeScript
11
15
  | 'react'
12
16
  | 'nextjs'
13
17
  | 'vue'
@@ -18,193 +22,359 @@ export type Framework =
18
22
  | 'astro'
19
23
  | 'remix'
20
24
  | 'gatsby'
25
+ | 'solidjs'
26
+ | 'qwik'
27
+ | 'fresh'
28
+ // Ruby
21
29
  | 'rails'
22
- | 'laravel'
30
+ // Python
23
31
  | 'django'
32
+ | 'flask'
33
+ | 'fastapi'
34
+ // PHP
35
+ | 'laravel'
36
+ | 'symfony'
24
37
  | 'wordpress'
38
+ // Java
39
+ | 'spring'
40
+ // C#
41
+ | 'aspnet'
42
+ // Go
43
+ | 'go'
44
+ // Elixir
45
+ | 'phoenix'
46
+ // Hypermedia
47
+ | 'htmx'
48
+ | 'hotwire'
49
+ // Static Site Generators
50
+ | 'hugo'
51
+ | 'jekyll'
52
+ | 'eleventy'
53
+ | 'pelican'
54
+ // Unknown
25
55
  | 'unknown';
26
56
 
57
+ export type Language = 'javascript' | 'typescript' | 'ruby' | 'python' | 'php' | 'java' | 'csharp' | 'go' | 'elixir' | 'rust' | 'unknown';
58
+
27
59
  export interface FrameworkInfo {
28
60
  framework: Framework;
29
61
  version?: string;
62
+ language: Language;
30
63
  confidence: 'high' | 'medium' | 'low';
31
- detected_from: 'html' | 'package.json' | 'files' | 'headers';
64
+ detected_from: 'html' | 'package.json' | 'files' | 'headers' | 'gemfile' | 'requirements' | 'composer' | 'pom' | 'csproj' | 'go.mod' | 'mix.exs';
32
65
  meta_framework?: Framework; // e.g., Next.js for React
33
66
  }
34
67
 
35
68
  interface HtmlSignature {
36
69
  framework: Framework;
70
+ language: Language;
37
71
  patterns: RegExp[];
38
72
  confidence: 'high' | 'medium' | 'low';
39
73
  }
40
74
 
41
75
  const HTML_SIGNATURES: HtmlSignature[] = [
76
+ // ============ JavaScript/TypeScript Frameworks ============
77
+
42
78
  // Next.js
43
79
  {
44
80
  framework: 'nextjs',
45
- patterns: [
46
- /__next/,
47
- /_next\//,
48
- /data-nscript/,
49
- /__NEXT_DATA__/,
50
- /next\/script/,
51
- ],
81
+ language: 'typescript',
82
+ patterns: [/__next/, /_next\//, /data-nscript/, /__NEXT_DATA__/, /next\/script/],
52
83
  confidence: 'high',
53
84
  },
54
85
  // Nuxt
55
86
  {
56
87
  framework: 'nuxt',
57
- patterns: [
58
- /__nuxt/,
59
- /_nuxt\//,
60
- /data-n-head/,
61
- /nuxt-link/,
62
- /__NUXT__/,
63
- ],
88
+ language: 'typescript',
89
+ patterns: [/__nuxt/, /_nuxt\//, /data-n-head/, /nuxt-link/, /__NUXT__/],
64
90
  confidence: 'high',
65
91
  },
66
92
  // Gatsby
67
93
  {
68
94
  framework: 'gatsby',
69
- patterns: [
70
- /___gatsby/,
71
- /gatsby-/,
72
- /gatsby-image/,
73
- /data-gatsby/,
74
- ],
95
+ language: 'javascript',
96
+ patterns: [/___gatsby/, /gatsby-/, /gatsby-image/, /data-gatsby/],
75
97
  confidence: 'high',
76
98
  },
77
99
  // Astro
78
100
  {
79
101
  framework: 'astro',
80
- patterns: [
81
- /astro-/,
82
- /data-astro/,
83
- /_astro\//,
84
- ],
102
+ language: 'typescript',
103
+ patterns: [/astro-/, /data-astro/, /_astro\//],
85
104
  confidence: 'high',
86
105
  },
87
106
  // SvelteKit
88
107
  {
89
108
  framework: 'sveltekit',
90
- patterns: [
91
- /__sveltekit/,
92
- /_app\/immutable/,
93
- /svelte-kit/,
94
- ],
109
+ language: 'typescript',
110
+ patterns: [/__sveltekit/, /_app\/immutable/, /svelte-kit/],
95
111
  confidence: 'high',
96
112
  },
97
113
  // Svelte (standalone)
98
114
  {
99
115
  framework: 'svelte',
100
- patterns: [
101
- /svelte-\w+/,
102
- /__svelte/,
103
- ],
116
+ language: 'javascript',
117
+ patterns: [/svelte-\w+/, /__svelte/],
104
118
  confidence: 'medium',
105
119
  },
106
120
  // Remix
107
121
  {
108
122
  framework: 'remix',
109
- patterns: [
110
- /__remix/,
111
- /remix-/,
112
- /data-remix/,
113
- ],
123
+ language: 'typescript',
124
+ patterns: [/__remix/, /remix-/, /data-remix/],
125
+ confidence: 'high',
126
+ },
127
+ // Solid.js
128
+ {
129
+ framework: 'solidjs',
130
+ language: 'typescript',
131
+ patterns: [/solid-/, /data-solid/, /_solid/],
114
132
  confidence: 'high',
115
133
  },
116
- // React (generic - lower priority than Next/Gatsby/Remix)
134
+ // Qwik
135
+ {
136
+ framework: 'qwik',
137
+ language: 'typescript',
138
+ patterns: [/q:/, /qwik/, /q-/],
139
+ confidence: 'high',
140
+ },
141
+ // Fresh (Deno)
142
+ {
143
+ framework: 'fresh',
144
+ language: 'typescript',
145
+ patterns: [/fresh-/, /_frsh\//, /data-fresh/],
146
+ confidence: 'high',
147
+ },
148
+ // React (generic - lower priority)
117
149
  {
118
150
  framework: 'react',
119
- patterns: [
120
- /data-reactroot/,
121
- /data-react-helmet/,
122
- /_react/,
123
- /react-app/,
124
- ],
151
+ language: 'javascript',
152
+ patterns: [/data-reactroot/, /data-react-helmet/, /_react/, /react-app/],
125
153
  confidence: 'medium',
126
154
  },
127
- // Vue (generic - lower priority than Nuxt)
155
+ // Vue (generic - lower priority)
128
156
  {
129
157
  framework: 'vue',
130
- patterns: [
131
- /data-v-[a-f0-9]+/,
132
- /v-cloak/,
133
- /__vue/,
134
- ],
158
+ language: 'javascript',
159
+ patterns: [/data-v-[a-f0-9]+/, /v-cloak/, /__vue/],
135
160
  confidence: 'medium',
136
161
  },
137
162
  // Angular
138
163
  {
139
164
  framework: 'angular',
140
- patterns: [
141
- /ng-version/,
142
- /_ngcontent/,
143
- /ng-\w+=/,
144
- /\[ng/,
145
- ],
165
+ language: 'typescript',
166
+ patterns: [/ng-version/, /_ngcontent/, /ng-\w+=/, /\[ng/],
146
167
  confidence: 'high',
147
168
  },
169
+
170
+ // ============ Hypermedia Frameworks ============
171
+
172
+ // HTMX
173
+ {
174
+ framework: 'htmx',
175
+ language: 'unknown',
176
+ patterns: [/hx-get/, /hx-post/, /hx-trigger/, /hx-swap/, /htmx\.org/],
177
+ confidence: 'high',
178
+ },
179
+ // Hotwire/Turbo
180
+ {
181
+ framework: 'hotwire',
182
+ language: 'ruby',
183
+ patterns: [/turbo-frame/, /turbo-stream/, /data-turbo/, /stimulus/],
184
+ confidence: 'high',
185
+ },
186
+
187
+ // ============ PHP Frameworks ============
188
+
148
189
  // WordPress
149
190
  {
150
191
  framework: 'wordpress',
151
- patterns: [
152
- /wp-content/,
153
- /wp-includes/,
154
- /wp-json/,
155
- /wordpress/i,
156
- ],
192
+ language: 'php',
193
+ patterns: [/wp-content/, /wp-includes/, /wp-json/, /wordpress/i],
157
194
  confidence: 'high',
158
195
  },
159
196
  // Laravel
160
197
  {
161
198
  framework: 'laravel',
162
- patterns: [
163
- /laravel_session/,
164
- /XSRF-TOKEN/,
165
- /@csrf/,
166
- ],
199
+ language: 'php',
200
+ patterns: [/laravel_session/, /XSRF-TOKEN/, /@csrf/, /laravel/i],
167
201
  confidence: 'medium',
168
202
  },
203
+ // Symfony
204
+ {
205
+ framework: 'symfony',
206
+ language: 'php',
207
+ patterns: [/symfony/, /sf2/, /_sf_/, /profiler\.css/],
208
+ confidence: 'medium',
209
+ },
210
+
211
+ // ============ Ruby Frameworks ============
212
+
169
213
  // Rails
170
214
  {
171
215
  framework: 'rails',
172
- patterns: [
173
- /csrf-token/,
174
- /turbolinks/,
175
- /data-turbo/,
176
- /rails-ujs/,
177
- ],
216
+ language: 'ruby',
217
+ patterns: [/csrf-token/, /turbolinks/, /data-turbo/, /rails-ujs/, /action-cable/],
178
218
  confidence: 'medium',
179
219
  },
220
+
221
+ // ============ Python Frameworks ============
222
+
180
223
  // Django
181
224
  {
182
225
  framework: 'django',
183
- patterns: [
184
- /csrfmiddlewaretoken/,
185
- /django/,
186
- ],
226
+ language: 'python',
227
+ patterns: [/csrfmiddlewaretoken/, /django/, /__debug__\//],
228
+ confidence: 'medium',
229
+ },
230
+ // Flask
231
+ {
232
+ framework: 'flask',
233
+ language: 'python',
234
+ patterns: [/flask/, /werkzeug/i],
235
+ confidence: 'low',
236
+ },
237
+ // FastAPI
238
+ {
239
+ framework: 'fastapi',
240
+ language: 'python',
241
+ patterns: [/fastapi/, /openapi\.json/],
242
+ confidence: 'low',
243
+ },
244
+
245
+ // ============ Java Frameworks ============
246
+
247
+ // Spring Boot
248
+ {
249
+ framework: 'spring',
250
+ language: 'java',
251
+ patterns: [/spring/, /thymeleaf/, /th:/, /_csrf/],
252
+ confidence: 'medium',
253
+ },
254
+
255
+ // ============ C# Frameworks ============
256
+
257
+ // ASP.NET Core
258
+ {
259
+ framework: 'aspnet',
260
+ language: 'csharp',
261
+ patterns: [/aspnetcore/, /__RequestVerificationToken/, /blazor/, /_framework\/blazor/],
262
+ confidence: 'medium',
263
+ },
264
+
265
+ // ============ Elixir Frameworks ============
266
+
267
+ // Phoenix
268
+ {
269
+ framework: 'phoenix',
270
+ language: 'elixir',
271
+ patterns: [/phx-/, /phoenix/, /live-socket/, /data-phx/],
272
+ confidence: 'high',
273
+ },
274
+
275
+ // ============ Static Site Generators ============
276
+
277
+ // Hugo
278
+ {
279
+ framework: 'hugo',
280
+ language: 'go',
281
+ patterns: [/hugo/, /generator.*hugo/i],
282
+ confidence: 'medium',
283
+ },
284
+ // Jekyll
285
+ {
286
+ framework: 'jekyll',
287
+ language: 'ruby',
288
+ patterns: [/jekyll/, /generator.*jekyll/i],
289
+ confidence: 'medium',
290
+ },
291
+ // Eleventy
292
+ {
293
+ framework: 'eleventy',
294
+ language: 'javascript',
295
+ patterns: [/11ty/, /eleventy/, /generator.*eleventy/i],
296
+ confidence: 'medium',
297
+ },
298
+ // Pelican
299
+ {
300
+ framework: 'pelican',
301
+ language: 'python',
302
+ patterns: [/pelican/, /generator.*pelican/i],
187
303
  confidence: 'medium',
188
304
  },
189
305
  ];
190
306
 
191
307
  interface PackageDependency {
192
308
  framework: Framework;
309
+ language: Language;
193
310
  packages: string[];
194
311
  meta_framework?: Framework;
195
312
  }
196
313
 
314
+ // NPM package.json dependencies
197
315
  const PACKAGE_DEPENDENCIES: PackageDependency[] = [
198
- { framework: 'nextjs', packages: ['next'], meta_framework: 'react' },
199
- { framework: 'gatsby', packages: ['gatsby'], meta_framework: 'react' },
200
- { framework: 'remix', packages: ['@remix-run/react', '@remix-run/node'], meta_framework: 'react' },
201
- { framework: 'nuxt', packages: ['nuxt', 'nuxt3'], meta_framework: 'vue' },
202
- { framework: 'sveltekit', packages: ['@sveltejs/kit'], meta_framework: 'svelte' },
203
- { framework: 'astro', packages: ['astro'] },
204
- { framework: 'react', packages: ['react', 'react-dom'] },
205
- { framework: 'vue', packages: ['vue'] },
206
- { framework: 'angular', packages: ['@angular/core'] },
207
- { framework: 'svelte', packages: ['svelte'] },
316
+ // Meta-frameworks (check first - more specific)
317
+ { framework: 'nextjs', language: 'typescript', packages: ['next'], meta_framework: 'react' },
318
+ { framework: 'gatsby', language: 'javascript', packages: ['gatsby'], meta_framework: 'react' },
319
+ { framework: 'remix', language: 'typescript', packages: ['@remix-run/react', '@remix-run/node'], meta_framework: 'react' },
320
+ { framework: 'nuxt', language: 'typescript', packages: ['nuxt', 'nuxt3'], meta_framework: 'vue' },
321
+ { framework: 'sveltekit', language: 'typescript', packages: ['@sveltejs/kit'], meta_framework: 'svelte' },
322
+ { framework: 'astro', language: 'typescript', packages: ['astro'] },
323
+ { framework: 'solidjs', language: 'typescript', packages: ['solid-js', 'solid-start'] },
324
+ { framework: 'qwik', language: 'typescript', packages: ['@builder.io/qwik'] },
325
+ { framework: 'eleventy', language: 'javascript', packages: ['@11ty/eleventy'] },
326
+
327
+ // Base frameworks
328
+ { framework: 'react', language: 'javascript', packages: ['react', 'react-dom'] },
329
+ { framework: 'vue', language: 'javascript', packages: ['vue'] },
330
+ { framework: 'angular', language: 'typescript', packages: ['@angular/core'] },
331
+ { framework: 'svelte', language: 'javascript', packages: ['svelte'] },
332
+
333
+ // HTMX (can be used with any backend)
334
+ { framework: 'htmx', language: 'unknown', packages: ['htmx.org'] },
335
+ ];
336
+
337
+ // Ruby Gemfile dependencies
338
+ const GEMFILE_DEPENDENCIES: PackageDependency[] = [
339
+ { framework: 'rails', language: 'ruby', packages: ['rails'] },
340
+ { framework: 'hotwire', language: 'ruby', packages: ['turbo-rails', 'stimulus-rails'], meta_framework: 'rails' },
341
+ { framework: 'jekyll', language: 'ruby', packages: ['jekyll'] },
342
+ ];
343
+
344
+ // Python requirements.txt / pyproject.toml dependencies
345
+ const PYTHON_DEPENDENCIES: PackageDependency[] = [
346
+ { framework: 'django', language: 'python', packages: ['django', 'Django'] },
347
+ { framework: 'flask', language: 'python', packages: ['flask', 'Flask'] },
348
+ { framework: 'fastapi', language: 'python', packages: ['fastapi', 'FastAPI'] },
349
+ { framework: 'pelican', language: 'python', packages: ['pelican', 'Pelican'] },
350
+ ];
351
+
352
+ // PHP composer.json dependencies
353
+ const COMPOSER_DEPENDENCIES: PackageDependency[] = [
354
+ { framework: 'laravel', language: 'php', packages: ['laravel/framework'] },
355
+ { framework: 'symfony', language: 'php', packages: ['symfony/framework-bundle', 'symfony/symfony'] },
356
+ { framework: 'wordpress', language: 'php', packages: ['johnpbloch/wordpress-core'] },
357
+ ];
358
+
359
+ // Java pom.xml / build.gradle dependencies
360
+ const JAVA_DEPENDENCIES: PackageDependency[] = [
361
+ { framework: 'spring', language: 'java', packages: ['spring-boot-starter-web', 'org.springframework.boot'] },
362
+ ];
363
+
364
+ // C# .csproj dependencies
365
+ const DOTNET_DEPENDENCIES: PackageDependency[] = [
366
+ { framework: 'aspnet', language: 'csharp', packages: ['Microsoft.AspNetCore', 'Microsoft.NET.Sdk.Web'] },
367
+ ];
368
+
369
+ // Go go.mod dependencies
370
+ const GO_DEPENDENCIES: PackageDependency[] = [
371
+ { framework: 'hugo', language: 'go', packages: ['github.com/gohugoio/hugo'] },
372
+ { framework: 'go', language: 'go', packages: ['github.com/gin-gonic/gin', 'github.com/labstack/echo', 'github.com/gofiber/fiber'] },
373
+ ];
374
+
375
+ // Elixir mix.exs dependencies
376
+ const ELIXIR_DEPENDENCIES: PackageDependency[] = [
377
+ { framework: 'phoenix', language: 'elixir', packages: [':phoenix', 'phoenix'] },
208
378
  ];
209
379
 
210
380
  /**
@@ -216,6 +386,7 @@ export function detectFromHtml(html: string): FrameworkInfo | null {
216
386
  if (pattern.test(html)) {
217
387
  return {
218
388
  framework: signature.framework,
389
+ language: signature.language,
219
390
  confidence: signature.confidence,
220
391
  detected_from: 'html',
221
392
  };
@@ -226,7 +397,7 @@ export function detectFromHtml(html: string): FrameworkInfo | null {
226
397
  }
227
398
 
228
399
  /**
229
- * Detect framework from package.json content
400
+ * Detect framework from package.json content (JavaScript/TypeScript)
230
401
  */
231
402
  export function detectFromPackageJson(packageJson: {
232
403
  dependencies?: Record<string, string>;
@@ -242,6 +413,7 @@ export function detectFromPackageJson(packageJson: {
242
413
  if (allDeps[pkg]) {
243
414
  return {
244
415
  framework: dep.framework,
416
+ language: dep.language,
245
417
  version: allDeps[pkg],
246
418
  confidence: 'high',
247
419
  detected_from: 'package.json',
@@ -254,29 +426,234 @@ export function detectFromPackageJson(packageJson: {
254
426
  return null;
255
427
  }
256
428
 
429
+ /**
430
+ * Detect framework from Gemfile content (Ruby)
431
+ */
432
+ export function detectFromGemfile(gemfileContent: string): FrameworkInfo | null {
433
+ const gemPattern = /gem\s+['"]([^'"]+)['"]/g;
434
+ const gems: string[] = [];
435
+ let match;
436
+
437
+ while ((match = gemPattern.exec(gemfileContent)) !== null) {
438
+ gems.push(match[1]);
439
+ }
440
+
441
+ for (const dep of GEMFILE_DEPENDENCIES) {
442
+ for (const pkg of dep.packages) {
443
+ if (gems.includes(pkg)) {
444
+ return {
445
+ framework: dep.framework,
446
+ language: dep.language,
447
+ confidence: 'high',
448
+ detected_from: 'gemfile',
449
+ meta_framework: dep.meta_framework,
450
+ };
451
+ }
452
+ }
453
+ }
454
+
455
+ return null;
456
+ }
457
+
458
+ /**
459
+ * Detect framework from requirements.txt or pyproject.toml (Python)
460
+ */
461
+ export function detectFromPythonDeps(content: string): FrameworkInfo | null {
462
+ for (const dep of PYTHON_DEPENDENCIES) {
463
+ for (const pkg of dep.packages) {
464
+ // Match package name at start of line or after whitespace, ignoring version specs
465
+ const pattern = new RegExp(`(^|\\s)${pkg.toLowerCase()}[\\s=<>~!]`, 'im');
466
+ if (pattern.test(content.toLowerCase())) {
467
+ return {
468
+ framework: dep.framework,
469
+ language: dep.language,
470
+ confidence: 'high',
471
+ detected_from: 'requirements',
472
+ };
473
+ }
474
+ }
475
+ }
476
+
477
+ return null;
478
+ }
479
+
480
+ /**
481
+ * Detect framework from composer.json (PHP)
482
+ */
483
+ export function detectFromComposerJson(composerJson: {
484
+ require?: Record<string, string>;
485
+ 'require-dev'?: Record<string, string>;
486
+ }): FrameworkInfo | null {
487
+ const allDeps = {
488
+ ...composerJson.require,
489
+ ...composerJson['require-dev'],
490
+ };
491
+
492
+ for (const dep of COMPOSER_DEPENDENCIES) {
493
+ for (const pkg of dep.packages) {
494
+ if (allDeps[pkg]) {
495
+ return {
496
+ framework: dep.framework,
497
+ language: dep.language,
498
+ version: allDeps[pkg],
499
+ confidence: 'high',
500
+ detected_from: 'composer',
501
+ };
502
+ }
503
+ }
504
+ }
505
+
506
+ return null;
507
+ }
508
+
509
+ /**
510
+ * Detect framework from pom.xml content (Java/Maven)
511
+ */
512
+ export function detectFromPomXml(pomContent: string): FrameworkInfo | null {
513
+ for (const dep of JAVA_DEPENDENCIES) {
514
+ for (const pkg of dep.packages) {
515
+ if (pomContent.includes(pkg)) {
516
+ return {
517
+ framework: dep.framework,
518
+ language: dep.language,
519
+ confidence: 'high',
520
+ detected_from: 'pom',
521
+ };
522
+ }
523
+ }
524
+ }
525
+
526
+ return null;
527
+ }
528
+
529
+ /**
530
+ * Detect framework from .csproj content (C#/.NET)
531
+ */
532
+ export function detectFromCsproj(csprojContent: string): FrameworkInfo | null {
533
+ for (const dep of DOTNET_DEPENDENCIES) {
534
+ for (const pkg of dep.packages) {
535
+ if (csprojContent.includes(pkg)) {
536
+ return {
537
+ framework: dep.framework,
538
+ language: dep.language,
539
+ confidence: 'high',
540
+ detected_from: 'csproj',
541
+ };
542
+ }
543
+ }
544
+ }
545
+
546
+ return null;
547
+ }
548
+
549
+ /**
550
+ * Detect framework from go.mod content (Go)
551
+ */
552
+ export function detectFromGoMod(goModContent: string): FrameworkInfo | null {
553
+ for (const dep of GO_DEPENDENCIES) {
554
+ for (const pkg of dep.packages) {
555
+ if (goModContent.includes(pkg)) {
556
+ return {
557
+ framework: dep.framework,
558
+ language: dep.language,
559
+ confidence: 'high',
560
+ detected_from: 'go.mod',
561
+ };
562
+ }
563
+ }
564
+ }
565
+
566
+ return null;
567
+ }
568
+
569
+ /**
570
+ * Detect framework from mix.exs content (Elixir)
571
+ */
572
+ export function detectFromMixExs(mixContent: string): FrameworkInfo | null {
573
+ for (const dep of ELIXIR_DEPENDENCIES) {
574
+ for (const pkg of dep.packages) {
575
+ if (mixContent.includes(pkg)) {
576
+ return {
577
+ framework: dep.framework,
578
+ language: dep.language,
579
+ confidence: 'high',
580
+ detected_from: 'mix.exs',
581
+ };
582
+ }
583
+ }
584
+ }
585
+
586
+ return null;
587
+ }
588
+
257
589
  /**
258
590
  * Detect framework from HTTP response headers
259
591
  */
260
592
  export function detectFromHeaders(headers: Record<string, string>): FrameworkInfo | null {
261
593
  const poweredBy = headers['x-powered-by']?.toLowerCase() || '';
262
594
  const server = headers['server']?.toLowerCase() || '';
595
+ const generator = headers['x-generator']?.toLowerCase() || '';
263
596
 
597
+ // JavaScript/TypeScript
264
598
  if (poweredBy.includes('next.js') || poweredBy.includes('next')) {
265
- return { framework: 'nextjs', confidence: 'high', detected_from: 'headers' };
599
+ return { framework: 'nextjs', language: 'typescript', confidence: 'high', detected_from: 'headers' };
266
600
  }
267
601
  if (poweredBy.includes('nuxt')) {
268
- return { framework: 'nuxt', confidence: 'high', detected_from: 'headers' };
602
+ return { framework: 'nuxt', language: 'typescript', confidence: 'high', detected_from: 'headers' };
603
+ }
604
+ if (poweredBy.includes('remix')) {
605
+ return { framework: 'remix', language: 'typescript', confidence: 'high', detected_from: 'headers' };
269
606
  }
270
- if (poweredBy.includes('express') || poweredBy.includes('koa')) {
271
- // Node.js but unclear which framework
607
+
608
+ // Ruby
609
+ if (poweredBy.includes('phusion') || poweredBy.includes('passenger') || poweredBy.includes('puma') || poweredBy.includes('unicorn')) {
610
+ return { framework: 'rails', language: 'ruby', confidence: 'medium', detected_from: 'headers' };
611
+ }
612
+
613
+ // Python
614
+ if (poweredBy.includes('gunicorn') || poweredBy.includes('uvicorn') || poweredBy.includes('daphne')) {
615
+ // Python but unclear which framework - needs HTML detection
272
616
  return null;
273
617
  }
274
- if (poweredBy.includes('php') || server.includes('php')) {
275
- // Could be WordPress or Laravel
618
+
619
+ // PHP
620
+ if (poweredBy.includes('php')) {
621
+ // Could be WordPress, Laravel, or Symfony - needs HTML detection
276
622
  return null;
277
623
  }
278
- if (poweredBy.includes('phusion') || poweredBy.includes('passenger')) {
279
- return { framework: 'rails', confidence: 'medium', detected_from: 'headers' };
624
+
625
+ // Java
626
+ if (server.includes('tomcat') || server.includes('jetty') || poweredBy.includes('spring')) {
627
+ return { framework: 'spring', language: 'java', confidence: 'medium', detected_from: 'headers' };
628
+ }
629
+
630
+ // .NET
631
+ if (poweredBy.includes('asp.net') || server.includes('kestrel')) {
632
+ return { framework: 'aspnet', language: 'csharp', confidence: 'high', detected_from: 'headers' };
633
+ }
634
+
635
+ // Elixir
636
+ if (poweredBy.includes('phoenix') || poweredBy.includes('cowboy')) {
637
+ return { framework: 'phoenix', language: 'elixir', confidence: 'medium', detected_from: 'headers' };
638
+ }
639
+
640
+ // Go
641
+ if (server.includes('gin') || server.includes('echo') || server.includes('fiber')) {
642
+ return { framework: 'go', language: 'go', confidence: 'medium', detected_from: 'headers' };
643
+ }
644
+
645
+ // Static site generators (from generator meta/header)
646
+ if (generator.includes('hugo')) {
647
+ return { framework: 'hugo', language: 'go', confidence: 'high', detected_from: 'headers' };
648
+ }
649
+ if (generator.includes('jekyll')) {
650
+ return { framework: 'jekyll', language: 'ruby', confidence: 'high', detected_from: 'headers' };
651
+ }
652
+ if (generator.includes('eleventy') || generator.includes('11ty')) {
653
+ return { framework: 'eleventy', language: 'javascript', confidence: 'high', detected_from: 'headers' };
654
+ }
655
+ if (generator.includes('pelican')) {
656
+ return { framework: 'pelican', language: 'python', confidence: 'high', detected_from: 'headers' };
280
657
  }
281
658
 
282
659
  return null;
@@ -288,15 +665,66 @@ export function detectFromHeaders(headers: Record<string, string>): FrameworkInf
288
665
  export function detectFramework(options: {
289
666
  html?: string;
290
667
  packageJson?: { dependencies?: Record<string, string>; devDependencies?: Record<string, string> };
668
+ composerJson?: { require?: Record<string, string>; 'require-dev'?: Record<string, string> };
669
+ gemfileContent?: string;
670
+ requirementsContent?: string;
671
+ pomXmlContent?: string;
672
+ csprojContent?: string;
673
+ goModContent?: string;
674
+ mixExsContent?: string;
291
675
  headers?: Record<string, string>;
292
676
  files?: string[];
293
677
  }): FrameworkInfo {
294
- // Try package.json first (most reliable)
678
+ // Try dependency files first (most reliable)
679
+
680
+ // JavaScript/TypeScript
295
681
  if (options.packageJson) {
296
682
  const result = detectFromPackageJson(options.packageJson);
297
683
  if (result) return result;
298
684
  }
299
685
 
686
+ // Ruby
687
+ if (options.gemfileContent) {
688
+ const result = detectFromGemfile(options.gemfileContent);
689
+ if (result) return result;
690
+ }
691
+
692
+ // Python
693
+ if (options.requirementsContent) {
694
+ const result = detectFromPythonDeps(options.requirementsContent);
695
+ if (result) return result;
696
+ }
697
+
698
+ // PHP
699
+ if (options.composerJson) {
700
+ const result = detectFromComposerJson(options.composerJson);
701
+ if (result) return result;
702
+ }
703
+
704
+ // Java
705
+ if (options.pomXmlContent) {
706
+ const result = detectFromPomXml(options.pomXmlContent);
707
+ if (result) return result;
708
+ }
709
+
710
+ // C#
711
+ if (options.csprojContent) {
712
+ const result = detectFromCsproj(options.csprojContent);
713
+ if (result) return result;
714
+ }
715
+
716
+ // Go
717
+ if (options.goModContent) {
718
+ const result = detectFromGoMod(options.goModContent);
719
+ if (result) return result;
720
+ }
721
+
722
+ // Elixir
723
+ if (options.mixExsContent) {
724
+ const result = detectFromMixExs(options.mixExsContent);
725
+ if (result) return result;
726
+ }
727
+
300
728
  // Try headers
301
729
  if (options.headers) {
302
730
  const result = detectFromHeaders(options.headers);
@@ -311,39 +739,83 @@ export function detectFramework(options: {
311
739
 
312
740
  // Check file patterns
313
741
  if (options.files) {
742
+ // JavaScript/TypeScript
314
743
  if (options.files.some(f => f.includes('next.config'))) {
315
- return { framework: 'nextjs', confidence: 'high', detected_from: 'files' };
744
+ return { framework: 'nextjs', language: 'typescript', confidence: 'high', detected_from: 'files' };
316
745
  }
317
746
  if (options.files.some(f => f.includes('nuxt.config'))) {
318
- return { framework: 'nuxt', confidence: 'high', detected_from: 'files' };
747
+ return { framework: 'nuxt', language: 'typescript', confidence: 'high', detected_from: 'files' };
319
748
  }
320
749
  if (options.files.some(f => f.includes('astro.config'))) {
321
- return { framework: 'astro', confidence: 'high', detected_from: 'files' };
750
+ return { framework: 'astro', language: 'typescript', confidence: 'high', detected_from: 'files' };
322
751
  }
323
752
  if (options.files.some(f => f.includes('svelte.config'))) {
324
- return { framework: 'sveltekit', confidence: 'high', detected_from: 'files' };
753
+ return { framework: 'sveltekit', language: 'typescript', confidence: 'high', detected_from: 'files' };
325
754
  }
326
755
  if (options.files.some(f => f.includes('angular.json'))) {
327
- return { framework: 'angular', confidence: 'high', detected_from: 'files' };
756
+ return { framework: 'angular', language: 'typescript', confidence: 'high', detected_from: 'files' };
328
757
  }
329
758
  if (options.files.some(f => f.includes('gatsby-config'))) {
330
- return { framework: 'gatsby', confidence: 'high', detected_from: 'files' };
759
+ return { framework: 'gatsby', language: 'javascript', confidence: 'high', detected_from: 'files' };
760
+ }
761
+ if (options.files.some(f => f.includes('remix.config'))) {
762
+ return { framework: 'remix', language: 'typescript', confidence: 'high', detected_from: 'files' };
763
+ }
764
+ if (options.files.some(f => f.includes('.eleventy.js') || f.includes('eleventy.config'))) {
765
+ return { framework: 'eleventy', language: 'javascript', confidence: 'high', detected_from: 'files' };
331
766
  }
767
+
768
+ // PHP
332
769
  if (options.files.some(f => f === 'wp-config.php')) {
333
- return { framework: 'wordpress', confidence: 'high', detected_from: 'files' };
770
+ return { framework: 'wordpress', language: 'php', confidence: 'high', detected_from: 'files' };
334
771
  }
335
772
  if (options.files.some(f => f === 'artisan')) {
336
- return { framework: 'laravel', confidence: 'high', detected_from: 'files' };
773
+ return { framework: 'laravel', language: 'php', confidence: 'high', detected_from: 'files' };
337
774
  }
775
+ if (options.files.some(f => f.includes('symfony.lock') || f.includes('config/bundles.php'))) {
776
+ return { framework: 'symfony', language: 'php', confidence: 'high', detected_from: 'files' };
777
+ }
778
+
779
+ // Python
338
780
  if (options.files.some(f => f === 'manage.py')) {
339
- return { framework: 'django', confidence: 'high', detected_from: 'files' };
781
+ return { framework: 'django', language: 'python', confidence: 'high', detected_from: 'files' };
782
+ }
783
+ if (options.files.some(f => f.includes('pelicanconf.py'))) {
784
+ return { framework: 'pelican', language: 'python', confidence: 'high', detected_from: 'files' };
340
785
  }
786
+
787
+ // Ruby
341
788
  if (options.files.some(f => f === 'Gemfile') && options.files.some(f => f.includes('config/routes.rb'))) {
342
- return { framework: 'rails', confidence: 'high', detected_from: 'files' };
789
+ return { framework: 'rails', language: 'ruby', confidence: 'high', detected_from: 'files' };
790
+ }
791
+ if (options.files.some(f => f === '_config.yml') && options.files.some(f => f === 'Gemfile')) {
792
+ return { framework: 'jekyll', language: 'ruby', confidence: 'high', detected_from: 'files' };
793
+ }
794
+
795
+ // Go
796
+ if (options.files.some(f => f.includes('hugo.toml') || f.includes('hugo.yaml') || f.includes('config.toml'))) {
797
+ return { framework: 'hugo', language: 'go', confidence: 'high', detected_from: 'files' };
798
+ }
799
+
800
+ // Java
801
+ if (options.files.some(f => f.includes('pom.xml') || f.includes('build.gradle'))) {
802
+ if (options.files.some(f => f.includes('src/main/resources/application'))) {
803
+ return { framework: 'spring', language: 'java', confidence: 'high', detected_from: 'files' };
804
+ }
805
+ }
806
+
807
+ // C#
808
+ if (options.files.some(f => f.endsWith('.csproj'))) {
809
+ return { framework: 'aspnet', language: 'csharp', confidence: 'medium', detected_from: 'files' };
810
+ }
811
+
812
+ // Elixir
813
+ if (options.files.some(f => f === 'mix.exs') && options.files.some(f => f.includes('lib') && f.includes('_web'))) {
814
+ return { framework: 'phoenix', language: 'elixir', confidence: 'high', detected_from: 'files' };
343
815
  }
344
816
  }
345
817
 
346
- return { framework: 'unknown', confidence: 'low', detected_from: 'html' };
818
+ return { framework: 'unknown', language: 'unknown', confidence: 'low', detected_from: 'html' };
347
819
  }
348
820
 
349
821
  /**
@@ -351,6 +823,7 @@ export function detectFramework(options: {
351
823
  */
352
824
  export function getFrameworkDisplayName(framework: Framework): string {
353
825
  const names: Record<Framework, string> = {
826
+ // JavaScript/TypeScript
354
827
  react: 'React',
355
828
  nextjs: 'Next.js',
356
829
  vue: 'Vue.js',
@@ -361,11 +834,66 @@ export function getFrameworkDisplayName(framework: Framework): string {
361
834
  astro: 'Astro',
362
835
  remix: 'Remix',
363
836
  gatsby: 'Gatsby',
837
+ solidjs: 'Solid.js',
838
+ qwik: 'Qwik',
839
+ fresh: 'Fresh (Deno)',
840
+
841
+ // Ruby
364
842
  rails: 'Ruby on Rails',
365
- laravel: 'Laravel',
843
+
844
+ // Python
366
845
  django: 'Django',
846
+ flask: 'Flask',
847
+ fastapi: 'FastAPI',
848
+
849
+ // PHP
850
+ laravel: 'Laravel',
851
+ symfony: 'Symfony',
367
852
  wordpress: 'WordPress',
853
+
854
+ // Java
855
+ spring: 'Spring Boot',
856
+
857
+ // C#
858
+ aspnet: 'ASP.NET Core',
859
+
860
+ // Go
861
+ go: 'Go (Gin/Echo/Fiber)',
862
+
863
+ // Elixir
864
+ phoenix: 'Phoenix',
865
+
866
+ // Hypermedia
867
+ htmx: 'HTMX',
868
+ hotwire: 'Hotwire/Turbo',
869
+
870
+ // Static Site Generators
871
+ hugo: 'Hugo',
872
+ jekyll: 'Jekyll',
873
+ eleventy: 'Eleventy (11ty)',
874
+ pelican: 'Pelican',
875
+
368
876
  unknown: 'Unknown',
369
877
  };
370
878
  return names[framework];
371
879
  }
880
+
881
+ /**
882
+ * Get language display name
883
+ */
884
+ export function getLanguageDisplayName(language: Language): string {
885
+ const names: Record<Language, string> = {
886
+ javascript: 'JavaScript',
887
+ typescript: 'TypeScript',
888
+ ruby: 'Ruby',
889
+ python: 'Python',
890
+ php: 'PHP',
891
+ java: 'Java',
892
+ csharp: 'C#',
893
+ go: 'Go',
894
+ elixir: 'Elixir',
895
+ rust: 'Rust',
896
+ unknown: 'Unknown',
897
+ };
898
+ return names[language];
899
+ }