genbox 1.0.2 → 1.0.4
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/commands/create.js +369 -130
- package/dist/commands/db-sync.js +364 -0
- package/dist/commands/destroy.js +5 -10
- package/dist/commands/init.js +669 -402
- package/dist/commands/profiles.js +333 -0
- package/dist/commands/push.js +140 -47
- package/dist/config-loader.js +529 -0
- package/dist/genbox-selector.js +5 -8
- package/dist/index.js +5 -1
- package/dist/profile-resolver.js +547 -0
- package/dist/scanner/compose-parser.js +441 -0
- package/dist/scanner/config-generator.js +620 -0
- package/dist/scanner/env-analyzer.js +503 -0
- package/dist/scanner/framework-detector.js +621 -0
- package/dist/scanner/index.js +424 -0
- package/dist/scanner/runtime-detector.js +330 -0
- package/dist/scanner/structure-detector.js +412 -0
- package/dist/scanner/types.js +7 -0
- package/dist/schema-v3.js +12 -0
- package/package.json +4 -1
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Framework Detector
|
|
4
|
+
*
|
|
5
|
+
* Detects frameworks and their configurations:
|
|
6
|
+
* - Node.js: Next.js, NestJS, Express, etc.
|
|
7
|
+
* - Python: Django, FastAPI, Flask, etc.
|
|
8
|
+
* - Go: Gin, Echo, Fiber, etc.
|
|
9
|
+
* - Ruby: Rails, Sinatra
|
|
10
|
+
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.FrameworkDetector = void 0;
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const FRAMEWORK_SIGNATURES = {
|
|
49
|
+
// Node.js - Fullstack/Frontend
|
|
50
|
+
nextjs: {
|
|
51
|
+
language: 'node',
|
|
52
|
+
detection: [
|
|
53
|
+
{ type: 'file', value: 'next.config.js' },
|
|
54
|
+
{ type: 'file', value: 'next.config.mjs' },
|
|
55
|
+
{ type: 'file', value: 'next.config.ts' },
|
|
56
|
+
{ type: 'dependency', value: 'next' },
|
|
57
|
+
],
|
|
58
|
+
defaults: {
|
|
59
|
+
type: 'fullstack',
|
|
60
|
+
defaultPort: 3000,
|
|
61
|
+
devCommand: 'next dev',
|
|
62
|
+
buildCommand: 'next build',
|
|
63
|
+
startCommand: 'next start',
|
|
64
|
+
outputDir: '.next',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
nuxt: {
|
|
68
|
+
language: 'node',
|
|
69
|
+
detection: [
|
|
70
|
+
{ type: 'file', value: 'nuxt.config.ts' },
|
|
71
|
+
{ type: 'file', value: 'nuxt.config.js' },
|
|
72
|
+
{ type: 'dependency', value: 'nuxt' },
|
|
73
|
+
],
|
|
74
|
+
defaults: {
|
|
75
|
+
type: 'fullstack',
|
|
76
|
+
defaultPort: 3000,
|
|
77
|
+
devCommand: 'nuxt dev',
|
|
78
|
+
buildCommand: 'nuxt build',
|
|
79
|
+
startCommand: 'nuxt start',
|
|
80
|
+
outputDir: '.output',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
remix: {
|
|
84
|
+
language: 'node',
|
|
85
|
+
detection: [
|
|
86
|
+
{ type: 'file', value: 'remix.config.js' },
|
|
87
|
+
{ type: 'dependency', value: '@remix-run/react' },
|
|
88
|
+
],
|
|
89
|
+
defaults: {
|
|
90
|
+
type: 'fullstack',
|
|
91
|
+
defaultPort: 3000,
|
|
92
|
+
devCommand: 'remix dev',
|
|
93
|
+
buildCommand: 'remix build',
|
|
94
|
+
startCommand: 'remix-serve build',
|
|
95
|
+
outputDir: 'build',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
astro: {
|
|
99
|
+
language: 'node',
|
|
100
|
+
detection: [
|
|
101
|
+
{ type: 'file', value: 'astro.config.mjs' },
|
|
102
|
+
{ type: 'file', value: 'astro.config.ts' },
|
|
103
|
+
{ type: 'dependency', value: 'astro' },
|
|
104
|
+
],
|
|
105
|
+
defaults: {
|
|
106
|
+
type: 'frontend',
|
|
107
|
+
defaultPort: 4321,
|
|
108
|
+
devCommand: 'astro dev',
|
|
109
|
+
buildCommand: 'astro build',
|
|
110
|
+
outputDir: 'dist',
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
gatsby: {
|
|
114
|
+
language: 'node',
|
|
115
|
+
detection: [
|
|
116
|
+
{ type: 'file', value: 'gatsby-config.js' },
|
|
117
|
+
{ type: 'file', value: 'gatsby-config.ts' },
|
|
118
|
+
{ type: 'dependency', value: 'gatsby' },
|
|
119
|
+
],
|
|
120
|
+
defaults: {
|
|
121
|
+
type: 'frontend',
|
|
122
|
+
defaultPort: 8000,
|
|
123
|
+
devCommand: 'gatsby develop',
|
|
124
|
+
buildCommand: 'gatsby build',
|
|
125
|
+
outputDir: 'public',
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
sveltekit: {
|
|
129
|
+
language: 'node',
|
|
130
|
+
detection: [
|
|
131
|
+
{ type: 'file', value: 'svelte.config.js' },
|
|
132
|
+
{ type: 'dependency', value: '@sveltejs/kit' },
|
|
133
|
+
],
|
|
134
|
+
defaults: {
|
|
135
|
+
type: 'fullstack',
|
|
136
|
+
defaultPort: 5173,
|
|
137
|
+
devCommand: 'vite dev',
|
|
138
|
+
buildCommand: 'vite build',
|
|
139
|
+
outputDir: 'build',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
react: {
|
|
143
|
+
language: 'node',
|
|
144
|
+
detection: [
|
|
145
|
+
{ type: 'dependency', value: 'react' },
|
|
146
|
+
{ type: 'dependency', value: 'react-dom' },
|
|
147
|
+
],
|
|
148
|
+
defaults: {
|
|
149
|
+
type: 'frontend',
|
|
150
|
+
defaultPort: 3000,
|
|
151
|
+
devCommand: 'vite dev',
|
|
152
|
+
buildCommand: 'vite build',
|
|
153
|
+
outputDir: 'dist',
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
vue: {
|
|
157
|
+
language: 'node',
|
|
158
|
+
detection: [
|
|
159
|
+
{ type: 'dependency', value: 'vue' },
|
|
160
|
+
],
|
|
161
|
+
defaults: {
|
|
162
|
+
type: 'frontend',
|
|
163
|
+
defaultPort: 5173,
|
|
164
|
+
devCommand: 'vite dev',
|
|
165
|
+
buildCommand: 'vite build',
|
|
166
|
+
outputDir: 'dist',
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
angular: {
|
|
170
|
+
language: 'node',
|
|
171
|
+
detection: [
|
|
172
|
+
{ type: 'file', value: 'angular.json' },
|
|
173
|
+
{ type: 'dependency', value: '@angular/core' },
|
|
174
|
+
],
|
|
175
|
+
defaults: {
|
|
176
|
+
type: 'frontend',
|
|
177
|
+
defaultPort: 4200,
|
|
178
|
+
devCommand: 'ng serve',
|
|
179
|
+
buildCommand: 'ng build',
|
|
180
|
+
outputDir: 'dist',
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
svelte: {
|
|
184
|
+
language: 'node',
|
|
185
|
+
detection: [
|
|
186
|
+
{ type: 'dependency', value: 'svelte' },
|
|
187
|
+
],
|
|
188
|
+
defaults: {
|
|
189
|
+
type: 'frontend',
|
|
190
|
+
defaultPort: 5173,
|
|
191
|
+
devCommand: 'vite dev',
|
|
192
|
+
buildCommand: 'vite build',
|
|
193
|
+
outputDir: 'dist',
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
solid: {
|
|
197
|
+
language: 'node',
|
|
198
|
+
detection: [
|
|
199
|
+
{ type: 'dependency', value: 'solid-js' },
|
|
200
|
+
],
|
|
201
|
+
defaults: {
|
|
202
|
+
type: 'frontend',
|
|
203
|
+
defaultPort: 3000,
|
|
204
|
+
devCommand: 'vite dev',
|
|
205
|
+
buildCommand: 'vite build',
|
|
206
|
+
outputDir: 'dist',
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
vite: {
|
|
210
|
+
language: 'node',
|
|
211
|
+
detection: [
|
|
212
|
+
{ type: 'file', value: 'vite.config.ts' },
|
|
213
|
+
{ type: 'file', value: 'vite.config.js' },
|
|
214
|
+
{ type: 'dependency', value: 'vite' },
|
|
215
|
+
],
|
|
216
|
+
defaults: {
|
|
217
|
+
type: 'frontend',
|
|
218
|
+
defaultPort: 5173,
|
|
219
|
+
devCommand: 'vite',
|
|
220
|
+
buildCommand: 'vite build',
|
|
221
|
+
outputDir: 'dist',
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
// Node.js - Backend
|
|
225
|
+
nestjs: {
|
|
226
|
+
language: 'node',
|
|
227
|
+
detection: [
|
|
228
|
+
{ type: 'file', value: 'nest-cli.json' },
|
|
229
|
+
{ type: 'dependency', value: '@nestjs/core' },
|
|
230
|
+
],
|
|
231
|
+
defaults: {
|
|
232
|
+
type: 'backend',
|
|
233
|
+
defaultPort: 3000,
|
|
234
|
+
devCommand: 'nest start --watch',
|
|
235
|
+
buildCommand: 'nest build',
|
|
236
|
+
startCommand: 'node dist/main',
|
|
237
|
+
outputDir: 'dist',
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
express: {
|
|
241
|
+
language: 'node',
|
|
242
|
+
detection: [
|
|
243
|
+
{ type: 'dependency', value: 'express' },
|
|
244
|
+
],
|
|
245
|
+
defaults: {
|
|
246
|
+
type: 'backend',
|
|
247
|
+
defaultPort: 3000,
|
|
248
|
+
devCommand: 'node --watch src/index.js',
|
|
249
|
+
startCommand: 'node src/index.js',
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
fastify: {
|
|
253
|
+
language: 'node',
|
|
254
|
+
detection: [
|
|
255
|
+
{ type: 'dependency', value: 'fastify' },
|
|
256
|
+
],
|
|
257
|
+
defaults: {
|
|
258
|
+
type: 'backend',
|
|
259
|
+
defaultPort: 3000,
|
|
260
|
+
devCommand: 'node --watch src/index.js',
|
|
261
|
+
startCommand: 'node src/index.js',
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
koa: {
|
|
265
|
+
language: 'node',
|
|
266
|
+
detection: [
|
|
267
|
+
{ type: 'dependency', value: 'koa' },
|
|
268
|
+
],
|
|
269
|
+
defaults: {
|
|
270
|
+
type: 'backend',
|
|
271
|
+
defaultPort: 3000,
|
|
272
|
+
devCommand: 'node --watch src/index.js',
|
|
273
|
+
startCommand: 'node src/index.js',
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
hono: {
|
|
277
|
+
language: 'node',
|
|
278
|
+
detection: [
|
|
279
|
+
{ type: 'dependency', value: 'hono' },
|
|
280
|
+
],
|
|
281
|
+
defaults: {
|
|
282
|
+
type: 'backend',
|
|
283
|
+
defaultPort: 3000,
|
|
284
|
+
devCommand: 'node --watch src/index.ts',
|
|
285
|
+
startCommand: 'node dist/index.js',
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
hapi: {
|
|
289
|
+
language: 'node',
|
|
290
|
+
detection: [
|
|
291
|
+
{ type: 'dependency', value: '@hapi/hapi' },
|
|
292
|
+
],
|
|
293
|
+
defaults: {
|
|
294
|
+
type: 'backend',
|
|
295
|
+
defaultPort: 3000,
|
|
296
|
+
devCommand: 'node --watch src/index.js',
|
|
297
|
+
startCommand: 'node src/index.js',
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
// Python
|
|
301
|
+
django: {
|
|
302
|
+
language: 'python',
|
|
303
|
+
detection: [
|
|
304
|
+
{ type: 'file', value: 'manage.py' },
|
|
305
|
+
{ type: 'dependency', value: 'django' },
|
|
306
|
+
{ type: 'dependency', value: 'Django' },
|
|
307
|
+
],
|
|
308
|
+
defaults: {
|
|
309
|
+
type: 'fullstack',
|
|
310
|
+
defaultPort: 8000,
|
|
311
|
+
devCommand: 'python manage.py runserver',
|
|
312
|
+
startCommand: 'gunicorn app.wsgi:application',
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
fastapi: {
|
|
316
|
+
language: 'python',
|
|
317
|
+
detection: [
|
|
318
|
+
{ type: 'dependency', value: 'fastapi' },
|
|
319
|
+
{ type: 'pattern', value: /from\s+fastapi\s+import/ },
|
|
320
|
+
],
|
|
321
|
+
defaults: {
|
|
322
|
+
type: 'backend',
|
|
323
|
+
defaultPort: 8000,
|
|
324
|
+
devCommand: 'uvicorn main:app --reload',
|
|
325
|
+
startCommand: 'uvicorn main:app --host 0.0.0.0',
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
flask: {
|
|
329
|
+
language: 'python',
|
|
330
|
+
detection: [
|
|
331
|
+
{ type: 'dependency', value: 'flask' },
|
|
332
|
+
{ type: 'dependency', value: 'Flask' },
|
|
333
|
+
],
|
|
334
|
+
defaults: {
|
|
335
|
+
type: 'backend',
|
|
336
|
+
defaultPort: 5000,
|
|
337
|
+
devCommand: 'flask run --debug',
|
|
338
|
+
startCommand: 'gunicorn app:app',
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
starlette: {
|
|
342
|
+
language: 'python',
|
|
343
|
+
detection: [
|
|
344
|
+
{ type: 'dependency', value: 'starlette' },
|
|
345
|
+
],
|
|
346
|
+
defaults: {
|
|
347
|
+
type: 'backend',
|
|
348
|
+
defaultPort: 8000,
|
|
349
|
+
devCommand: 'uvicorn main:app --reload',
|
|
350
|
+
startCommand: 'uvicorn main:app',
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
// Go
|
|
354
|
+
gin: {
|
|
355
|
+
language: 'go',
|
|
356
|
+
detection: [
|
|
357
|
+
{ type: 'dependency', value: 'github.com/gin-gonic/gin' },
|
|
358
|
+
],
|
|
359
|
+
defaults: {
|
|
360
|
+
type: 'backend',
|
|
361
|
+
defaultPort: 8080,
|
|
362
|
+
devCommand: 'go run .',
|
|
363
|
+
buildCommand: 'go build -o app',
|
|
364
|
+
startCommand: './app',
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
echo: {
|
|
368
|
+
language: 'go',
|
|
369
|
+
detection: [
|
|
370
|
+
{ type: 'dependency', value: 'github.com/labstack/echo' },
|
|
371
|
+
],
|
|
372
|
+
defaults: {
|
|
373
|
+
type: 'backend',
|
|
374
|
+
defaultPort: 8080,
|
|
375
|
+
devCommand: 'go run .',
|
|
376
|
+
buildCommand: 'go build -o app',
|
|
377
|
+
startCommand: './app',
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
fiber: {
|
|
381
|
+
language: 'go',
|
|
382
|
+
detection: [
|
|
383
|
+
{ type: 'dependency', value: 'github.com/gofiber/fiber' },
|
|
384
|
+
],
|
|
385
|
+
defaults: {
|
|
386
|
+
type: 'backend',
|
|
387
|
+
defaultPort: 3000,
|
|
388
|
+
devCommand: 'go run .',
|
|
389
|
+
buildCommand: 'go build -o app',
|
|
390
|
+
startCommand: './app',
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
chi: {
|
|
394
|
+
language: 'go',
|
|
395
|
+
detection: [
|
|
396
|
+
{ type: 'dependency', value: 'github.com/go-chi/chi' },
|
|
397
|
+
],
|
|
398
|
+
defaults: {
|
|
399
|
+
type: 'backend',
|
|
400
|
+
defaultPort: 8080,
|
|
401
|
+
devCommand: 'go run .',
|
|
402
|
+
buildCommand: 'go build -o app',
|
|
403
|
+
startCommand: './app',
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
// Ruby
|
|
407
|
+
rails: {
|
|
408
|
+
language: 'ruby',
|
|
409
|
+
detection: [
|
|
410
|
+
{ type: 'file', value: 'config/routes.rb' },
|
|
411
|
+
{ type: 'dependency', value: 'rails' },
|
|
412
|
+
],
|
|
413
|
+
defaults: {
|
|
414
|
+
type: 'fullstack',
|
|
415
|
+
defaultPort: 3000,
|
|
416
|
+
devCommand: 'rails server',
|
|
417
|
+
startCommand: 'rails server -e production',
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
sinatra: {
|
|
421
|
+
language: 'ruby',
|
|
422
|
+
detection: [
|
|
423
|
+
{ type: 'dependency', value: 'sinatra' },
|
|
424
|
+
],
|
|
425
|
+
defaults: {
|
|
426
|
+
type: 'backend',
|
|
427
|
+
defaultPort: 4567,
|
|
428
|
+
devCommand: 'ruby app.rb',
|
|
429
|
+
startCommand: 'ruby app.rb -e production',
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
// Priority order for detection (more specific frameworks first)
|
|
434
|
+
const DETECTION_PRIORITY = [
|
|
435
|
+
// Node.js fullstack (most specific)
|
|
436
|
+
'nextjs', 'nuxt', 'remix', 'sveltekit', 'gatsby', 'astro',
|
|
437
|
+
// Node.js backend
|
|
438
|
+
'nestjs', 'fastify', 'hono', 'hapi', 'koa', 'express',
|
|
439
|
+
// Node.js frontend
|
|
440
|
+
'angular', 'vue', 'svelte', 'solid', 'react', 'vite',
|
|
441
|
+
// Python
|
|
442
|
+
'django', 'fastapi', 'flask', 'starlette',
|
|
443
|
+
// Go
|
|
444
|
+
'gin', 'echo', 'fiber', 'chi',
|
|
445
|
+
// Ruby
|
|
446
|
+
'rails', 'sinatra',
|
|
447
|
+
];
|
|
448
|
+
class FrameworkDetector {
|
|
449
|
+
/**
|
|
450
|
+
* Detect frameworks in the project
|
|
451
|
+
*/
|
|
452
|
+
async detect(root, runtimes) {
|
|
453
|
+
const frameworks = [];
|
|
454
|
+
const detectedLanguages = new Set(runtimes.map(r => r.language));
|
|
455
|
+
const alreadyDetected = new Set();
|
|
456
|
+
// Check frameworks in priority order
|
|
457
|
+
for (const framework of DETECTION_PRIORITY) {
|
|
458
|
+
if (alreadyDetected.has(framework))
|
|
459
|
+
continue;
|
|
460
|
+
const signature = FRAMEWORK_SIGNATURES[framework];
|
|
461
|
+
// Skip if language not detected
|
|
462
|
+
if (!detectedLanguages.has(signature.language))
|
|
463
|
+
continue;
|
|
464
|
+
// Check detection rules
|
|
465
|
+
const matches = await this.matchesSignature(root, signature.detection);
|
|
466
|
+
if (matches) {
|
|
467
|
+
// For generic frameworks (react, vue, express), check they're not part of a fullstack framework
|
|
468
|
+
if (this.isGenericFramework(framework)) {
|
|
469
|
+
const hasFullstackFramework = frameworks.some(f => FRAMEWORK_SIGNATURES[f.name].defaults.type === 'fullstack');
|
|
470
|
+
if (hasFullstackFramework)
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
const version = await this.getFrameworkVersion(root, framework);
|
|
474
|
+
frameworks.push({
|
|
475
|
+
name: framework,
|
|
476
|
+
version,
|
|
477
|
+
...signature.defaults,
|
|
478
|
+
});
|
|
479
|
+
alreadyDetected.add(framework);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return frameworks;
|
|
483
|
+
}
|
|
484
|
+
isGenericFramework(framework) {
|
|
485
|
+
return ['react', 'vue', 'svelte', 'solid', 'express', 'vite'].includes(framework);
|
|
486
|
+
}
|
|
487
|
+
async matchesSignature(root, rules) {
|
|
488
|
+
for (const rule of rules) {
|
|
489
|
+
const matches = await this.matchRule(root, rule);
|
|
490
|
+
if (matches)
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
async matchRule(root, rule) {
|
|
496
|
+
switch (rule.type) {
|
|
497
|
+
case 'file':
|
|
498
|
+
return fs.existsSync(path.join(root, rule.value));
|
|
499
|
+
case 'dependency':
|
|
500
|
+
return this.hasDependency(root, rule.value);
|
|
501
|
+
case 'pattern':
|
|
502
|
+
return this.matchesPattern(root, rule.value);
|
|
503
|
+
default:
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
hasDependency(root, dep) {
|
|
508
|
+
// Check Node.js package.json
|
|
509
|
+
const pkgPath = path.join(root, 'package.json');
|
|
510
|
+
if (fs.existsSync(pkgPath)) {
|
|
511
|
+
try {
|
|
512
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
513
|
+
const allDeps = {
|
|
514
|
+
...(pkg.dependencies || {}),
|
|
515
|
+
...(pkg.devDependencies || {}),
|
|
516
|
+
...(pkg.peerDependencies || {}),
|
|
517
|
+
};
|
|
518
|
+
if (allDeps[dep])
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
catch { }
|
|
522
|
+
}
|
|
523
|
+
// Check Python requirements
|
|
524
|
+
const reqPath = path.join(root, 'requirements.txt');
|
|
525
|
+
if (fs.existsSync(reqPath)) {
|
|
526
|
+
try {
|
|
527
|
+
const content = fs.readFileSync(reqPath, 'utf8').toLowerCase();
|
|
528
|
+
if (content.includes(dep.toLowerCase()))
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
catch { }
|
|
532
|
+
}
|
|
533
|
+
// Check Python pyproject.toml
|
|
534
|
+
const pyprojectPath = path.join(root, 'pyproject.toml');
|
|
535
|
+
if (fs.existsSync(pyprojectPath)) {
|
|
536
|
+
try {
|
|
537
|
+
const content = fs.readFileSync(pyprojectPath, 'utf8').toLowerCase();
|
|
538
|
+
if (content.includes(dep.toLowerCase()))
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
catch { }
|
|
542
|
+
}
|
|
543
|
+
// Check Go go.mod
|
|
544
|
+
const goModPath = path.join(root, 'go.mod');
|
|
545
|
+
if (fs.existsSync(goModPath)) {
|
|
546
|
+
try {
|
|
547
|
+
const content = fs.readFileSync(goModPath, 'utf8');
|
|
548
|
+
if (content.includes(dep))
|
|
549
|
+
return true;
|
|
550
|
+
}
|
|
551
|
+
catch { }
|
|
552
|
+
}
|
|
553
|
+
// Check Ruby Gemfile
|
|
554
|
+
const gemfilePath = path.join(root, 'Gemfile');
|
|
555
|
+
if (fs.existsSync(gemfilePath)) {
|
|
556
|
+
try {
|
|
557
|
+
const content = fs.readFileSync(gemfilePath, 'utf8').toLowerCase();
|
|
558
|
+
if (content.includes(dep.toLowerCase()))
|
|
559
|
+
return true;
|
|
560
|
+
}
|
|
561
|
+
catch { }
|
|
562
|
+
}
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
matchesPattern(root, pattern) {
|
|
566
|
+
// Search common source files for the pattern
|
|
567
|
+
const sourceFiles = [
|
|
568
|
+
'main.py', 'app.py', 'src/main.py', 'src/app.py',
|
|
569
|
+
'main.go', 'cmd/main.go',
|
|
570
|
+
'app.rb', 'config.ru',
|
|
571
|
+
];
|
|
572
|
+
for (const file of sourceFiles) {
|
|
573
|
+
const filePath = path.join(root, file);
|
|
574
|
+
if (fs.existsSync(filePath)) {
|
|
575
|
+
try {
|
|
576
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
577
|
+
if (pattern.test(content))
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
catch { }
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
async getFrameworkVersion(root, framework) {
|
|
586
|
+
const signature = FRAMEWORK_SIGNATURES[framework];
|
|
587
|
+
if (signature.language === 'node') {
|
|
588
|
+
// Get from package.json
|
|
589
|
+
const pkgPath = path.join(root, 'package.json');
|
|
590
|
+
if (fs.existsSync(pkgPath)) {
|
|
591
|
+
try {
|
|
592
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
593
|
+
const allDeps = {
|
|
594
|
+
...(pkg.dependencies || {}),
|
|
595
|
+
...(pkg.devDependencies || {}),
|
|
596
|
+
};
|
|
597
|
+
// Find the main dependency
|
|
598
|
+
for (const rule of signature.detection) {
|
|
599
|
+
if (rule.type === 'dependency') {
|
|
600
|
+
const version = allDeps[rule.value];
|
|
601
|
+
if (version) {
|
|
602
|
+
// Strip semver operators
|
|
603
|
+
return version.replace(/^[\^~>=<]+/, '');
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
catch { }
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return undefined;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Detect framework for a specific app
|
|
615
|
+
*/
|
|
616
|
+
async detectForApp(appPath, runtime) {
|
|
617
|
+
const frameworks = await this.detect(appPath, [runtime]);
|
|
618
|
+
return frameworks[0];
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
exports.FrameworkDetector = FrameworkDetector;
|