prjct-cli 0.10.6 → 0.10.9

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.
@@ -1,365 +0,0 @@
1
- /**
2
- * TechDetector - Dynamic Technology Detection
3
- *
4
- * NO HARDCODING - Detects technologies from actual project files
5
- * Works with ANY technology stack (Elixir, Phoenix, Svelte, etc.)
6
- *
7
- * @version 1.0.0
8
- */
9
-
10
- const fs = require('fs').promises
11
- const path = require('path')
12
-
13
- class TechDetector {
14
- constructor(projectPath) {
15
- this.projectPath = projectPath
16
- this.cache = null
17
- }
18
-
19
- /**
20
- * Detect all technologies in the project
21
- * Returns structured data about languages, frameworks, tools
22
- * @returns {Promise<Object>} - { languages: [], frameworks: [], tools: [], packageManagers: [] }
23
- */
24
- async detectAll() {
25
- if (this.cache) {
26
- return this.cache
27
- }
28
-
29
- const detected = {
30
- languages: [],
31
- frameworks: [],
32
- tools: [],
33
- packageManagers: [],
34
- databases: [],
35
- buildTools: [],
36
- testFrameworks: []
37
- }
38
-
39
- // Detect from package managers (most reliable source)
40
- await this.detectFromPackageJson(detected)
41
- await this.detectFromCargoToml(detected)
42
- await this.detectFromGoMod(detected)
43
- await this.detectFromRequirements(detected)
44
- await this.detectFromGemfile(detected)
45
- await this.detectFromMixExs(detected) // Elixir
46
- await this.detectFromPomXml(detected) // Maven/Java
47
- await this.detectFromComposerJson(detected) // PHP
48
-
49
- // Detect from config files
50
- await this.detectFromConfigFiles(detected)
51
-
52
- // Detect from directory structure
53
- await this.detectFromStructure(detected)
54
-
55
- // Cache result
56
- this.cache = detected
57
- return detected
58
- }
59
-
60
- /**
61
- * Detect from package.json (Node.js/JavaScript/TypeScript)
62
- */
63
- async detectFromPackageJson(detected) {
64
- try {
65
- const packagePath = path.join(this.projectPath, 'package.json')
66
- const content = await fs.readFile(packagePath, 'utf-8')
67
- const pkg = JSON.parse(content)
68
-
69
- detected.packageManagers.push('npm')
70
-
71
- // Language
72
- if (pkg.dependencies?.typescript || pkg.devDependencies?.typescript) {
73
- detected.languages.push('TypeScript')
74
- } else {
75
- detected.languages.push('JavaScript')
76
- }
77
-
78
- // Collect all dependencies (no hardcoding - just list them)
79
- const allDeps = {
80
- ...(pkg.dependencies || {}),
81
- ...(pkg.devDependencies || {})
82
- }
83
-
84
- // Frameworks and tools are in dependencies - let Claude decide what's important
85
- // We just collect the names, no assumptions
86
- const depNames = Object.keys(allDeps)
87
-
88
- // Common patterns (but not hardcoded - just helpers)
89
- for (const dep of depNames) {
90
- // Frontend frameworks
91
- if (['react', 'vue', 'angular', 'svelte'].includes(dep.toLowerCase())) {
92
- detected.frameworks.push(dep)
93
- }
94
- // Meta-frameworks
95
- else if (['next', 'nuxt', 'sveltekit', 'remix'].includes(dep.toLowerCase())) {
96
- detected.frameworks.push(dep)
97
- }
98
- // Backend frameworks
99
- else if (['express', 'fastify', 'koa', 'hapi', 'nest'].includes(dep.toLowerCase())) {
100
- detected.frameworks.push(dep)
101
- }
102
- // Build tools
103
- else if (['vite', 'webpack', 'rollup', 'esbuild', 'parcel'].includes(dep.toLowerCase())) {
104
- detected.buildTools.push(dep)
105
- }
106
- // Test frameworks
107
- else if (['jest', 'vitest', 'mocha', 'jasmine', 'cypress', 'playwright'].includes(dep.toLowerCase())) {
108
- detected.testFrameworks.push(dep)
109
- }
110
- // Databases
111
- else if (['mongoose', 'sequelize', 'prisma', 'typeorm'].includes(dep.toLowerCase())) {
112
- detected.databases.push(dep)
113
- }
114
- }
115
-
116
- // Also check for yarn/pnpm
117
- if (await this.fileExists('yarn.lock')) {
118
- detected.packageManagers.push('yarn')
119
- }
120
- if (await this.fileExists('pnpm-lock.yaml')) {
121
- detected.packageManagers.push('pnpm')
122
- }
123
- } catch (error) {
124
- // File doesn't exist or invalid JSON - skip
125
- }
126
- }
127
-
128
- /**
129
- * Detect from Cargo.toml (Rust)
130
- */
131
- async detectFromCargoToml(detected) {
132
- try {
133
- const cargoPath = path.join(this.projectPath, 'Cargo.toml')
134
- await fs.readFile(cargoPath, 'utf-8')
135
-
136
- detected.languages.push('Rust')
137
- detected.packageManagers.push('Cargo')
138
- } catch (error) {
139
- // File doesn't exist - skip
140
- }
141
- }
142
-
143
- /**
144
- * Detect from go.mod (Go)
145
- */
146
- async detectFromGoMod(detected) {
147
- try {
148
- const goModPath = path.join(this.projectPath, 'go.mod')
149
- await fs.readFile(goModPath, 'utf-8')
150
-
151
- detected.languages.push('Go')
152
- detected.packageManagers.push('Go Modules')
153
- } catch (error) {
154
- // File doesn't exist - skip
155
- }
156
- }
157
-
158
- /**
159
- * Detect from requirements.txt (Python)
160
- */
161
- async detectFromRequirements(detected) {
162
- try {
163
- const reqPath = path.join(this.projectPath, 'requirements.txt')
164
- const content = await fs.readFile(reqPath, 'utf-8')
165
-
166
- detected.languages.push('Python')
167
- detected.packageManagers.push('pip')
168
-
169
- // Detect common frameworks
170
- const lines = content.split('\n').map(l => l.trim().toLowerCase())
171
- if (lines.some(l => l.includes('django'))) detected.frameworks.push('Django')
172
- if (lines.some(l => l.includes('flask'))) detected.frameworks.push('Flask')
173
- if (lines.some(l => l.includes('fastapi'))) detected.frameworks.push('FastAPI')
174
- } catch (error) {
175
- // File doesn't exist - skip
176
- }
177
- }
178
-
179
- /**
180
- * Detect from Gemfile (Ruby)
181
- */
182
- async detectFromGemfile(detected) {
183
- try {
184
- const gemfilePath = path.join(this.projectPath, 'Gemfile')
185
- const content = await fs.readFile(gemfilePath, 'utf-8')
186
-
187
- detected.languages.push('Ruby')
188
- detected.packageManagers.push('Bundler')
189
-
190
- if (content.includes('rails')) {
191
- detected.frameworks.push('Rails')
192
- }
193
- } catch (error) {
194
- // File doesn't exist - skip
195
- }
196
- }
197
-
198
- /**
199
- * Detect from mix.exs (Elixir)
200
- */
201
- async detectFromMixExs(detected) {
202
- try {
203
- const mixPath = path.join(this.projectPath, 'mix.exs')
204
- const content = await fs.readFile(mixPath, 'utf-8')
205
-
206
- detected.languages.push('Elixir')
207
- detected.packageManagers.push('Mix')
208
-
209
- if (content.includes('phoenix')) {
210
- detected.frameworks.push('Phoenix')
211
- }
212
- } catch (error) {
213
- // File doesn't exist - skip
214
- }
215
- }
216
-
217
- /**
218
- * Detect from pom.xml (Maven/Java)
219
- */
220
- async detectFromPomXml(detected) {
221
- try {
222
- const pomPath = path.join(this.projectPath, 'pom.xml')
223
- await fs.readFile(pomPath, 'utf-8')
224
-
225
- detected.languages.push('Java')
226
- detected.packageManagers.push('Maven')
227
- } catch (error) {
228
- // File doesn't exist - skip
229
- }
230
- }
231
-
232
- /**
233
- * Detect from composer.json (PHP)
234
- */
235
- async detectFromComposerJson(detected) {
236
- try {
237
- const composerPath = path.join(this.projectPath, 'composer.json')
238
- const content = await fs.readFile(composerPath, 'utf-8')
239
- const composer = JSON.parse(content)
240
-
241
- detected.languages.push('PHP')
242
- detected.packageManagers.push('Composer')
243
-
244
- const allDeps = {
245
- ...(composer.require || {}),
246
- ...(composer['require-dev'] || {})
247
- }
248
-
249
- if (Object.keys(allDeps).some(d => d.includes('laravel'))) {
250
- detected.frameworks.push('Laravel')
251
- }
252
- if (Object.keys(allDeps).some(d => d.includes('symfony'))) {
253
- detected.frameworks.push('Symfony')
254
- }
255
- } catch (error) {
256
- // File doesn't exist - skip
257
- }
258
- }
259
-
260
- /**
261
- * Detect from config files (Docker, etc.)
262
- */
263
- async detectFromConfigFiles(detected) {
264
- // Docker
265
- if (await this.fileExists('Dockerfile')) {
266
- detected.tools.push('Docker')
267
- }
268
- if (await this.fileExists('docker-compose.yml') || await this.fileExists('docker-compose.yaml')) {
269
- detected.tools.push('Docker Compose')
270
- }
271
-
272
- // Kubernetes
273
- if (await this.fileExists('k8s') || await this.fileExists('kubernetes')) {
274
- detected.tools.push('Kubernetes')
275
- }
276
-
277
- // Terraform
278
- if (await this.fileExists('.tf') || await this.findFiles('*.tf')) {
279
- detected.tools.push('Terraform')
280
- }
281
- }
282
-
283
- /**
284
- * Detect from directory structure
285
- */
286
- async detectFromStructure(_detected) {
287
- try {
288
- const entries = await fs.readdir(this.projectPath, { withFileTypes: true })
289
- const dirs = entries.filter(e => e.isDirectory()).map(e => e.name)
290
-
291
- // Common patterns (but not assumptions - just hints)
292
- // Could analyze structure here in the future
293
- if (dirs.includes('src') && dirs.includes('lib')) {
294
- // Could be Elixir, but don't assume
295
- }
296
- } catch (error) {
297
- // Can't read directory - skip
298
- }
299
- }
300
-
301
- /**
302
- * Get a summary string of detected technologies
303
- * Useful for agent generation
304
- */
305
- async getSummary() {
306
- const tech = await this.detectAll()
307
- const parts = []
308
-
309
- if (tech.languages.length > 0) {
310
- parts.push(`Languages: ${tech.languages.join(', ')}`)
311
- }
312
- if (tech.frameworks.length > 0) {
313
- parts.push(`Frameworks: ${tech.frameworks.join(', ')}`)
314
- }
315
- if (tech.tools.length > 0) {
316
- parts.push(`Tools: ${tech.tools.join(', ')}`)
317
- }
318
- if (tech.databases.length > 0) {
319
- parts.push(`Databases: ${tech.databases.join(', ')}`)
320
- }
321
-
322
- return parts.join(' | ')
323
- }
324
-
325
- /**
326
- * Helper: Check if file exists
327
- */
328
- async fileExists(filename) {
329
- try {
330
- await fs.access(path.join(this.projectPath, filename))
331
- return true
332
- } catch {
333
- return false
334
- }
335
- }
336
-
337
- /**
338
- * Helper: Find files matching pattern
339
- */
340
- async findFiles(pattern) {
341
- try {
342
- const { exec } = require('child_process')
343
- const { promisify } = require('util')
344
- const execAsync = promisify(exec)
345
-
346
- const { stdout } = await execAsync(
347
- `find . -name "${pattern}" -type f ! -path "*/node_modules/*" ! -path "*/.git/*" | head -1`,
348
- { cwd: this.projectPath }
349
- )
350
- return stdout.trim().length > 0
351
- } catch {
352
- return false
353
- }
354
- }
355
-
356
- /**
357
- * Clear cache (useful after project changes)
358
- */
359
- clearCache() {
360
- this.cache = null
361
- }
362
- }
363
-
364
- module.exports = TechDetector
365
-