prjct-cli 0.44.1 → 0.45.3
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/CHANGELOG.md +114 -0
- package/bin/prjct.ts +131 -10
- package/core/__tests__/agentic/memory-system.test.ts +39 -26
- package/core/__tests__/agentic/plan-mode.test.ts +64 -46
- package/core/__tests__/agentic/prompt-builder.test.ts +14 -14
- package/core/__tests__/services/project-index.test.ts +353 -0
- package/core/__tests__/types/fs.test.ts +3 -3
- package/core/__tests__/utils/date-helper.test.ts +10 -10
- package/core/__tests__/utils/output.test.ts +9 -6
- package/core/__tests__/utils/project-commands.test.ts +5 -6
- package/core/agentic/agent-router.ts +9 -10
- package/core/agentic/chain-of-thought.ts +16 -4
- package/core/agentic/command-executor.ts +66 -40
- package/core/agentic/context-builder.ts +8 -5
- package/core/agentic/ground-truth.ts +15 -9
- package/core/agentic/index.ts +145 -152
- package/core/agentic/loop-detector.ts +40 -11
- package/core/agentic/memory-system.ts +98 -35
- package/core/agentic/orchestrator-executor.ts +135 -71
- package/core/agentic/plan-mode.ts +46 -16
- package/core/agentic/prompt-builder.ts +108 -42
- package/core/agentic/services.ts +10 -9
- package/core/agentic/skill-loader.ts +9 -15
- package/core/agentic/smart-context.ts +129 -79
- package/core/agentic/template-executor.ts +13 -12
- package/core/agentic/template-loader.ts +7 -4
- package/core/agentic/tool-registry.ts +16 -13
- package/core/agents/index.ts +1 -1
- package/core/agents/performance.ts +10 -27
- package/core/ai-tools/formatters.ts +8 -6
- package/core/ai-tools/generator.ts +4 -4
- package/core/ai-tools/index.ts +1 -1
- package/core/ai-tools/registry.ts +21 -11
- package/core/bus/bus.ts +23 -16
- package/core/bus/index.ts +2 -2
- package/core/cli/linear.ts +3 -5
- package/core/cli/start.ts +28 -25
- package/core/commands/analysis.ts +287 -29
- package/core/commands/analytics.ts +52 -44
- package/core/commands/base.ts +15 -13
- package/core/commands/cleanup.ts +6 -13
- package/core/commands/command-data.ts +49 -8
- package/core/commands/commands.ts +60 -23
- package/core/commands/context.ts +4 -4
- package/core/commands/design.ts +3 -10
- package/core/commands/index.ts +5 -8
- package/core/commands/maintenance.ts +7 -4
- package/core/commands/planning.ts +179 -56
- package/core/commands/register.ts +14 -9
- package/core/commands/registry.ts +15 -14
- package/core/commands/setup.ts +26 -14
- package/core/commands/shipping.ts +11 -16
- package/core/commands/snapshots.ts +16 -32
- package/core/commands/uninstall.ts +541 -0
- package/core/commands/workflow.ts +24 -28
- package/core/constants/index.ts +10 -22
- package/core/context/generator.ts +82 -33
- package/core/context-tools/files-tool.ts +583 -0
- package/core/context-tools/imports-tool.ts +403 -0
- package/core/context-tools/index.ts +433 -0
- package/core/context-tools/recent-tool.ts +307 -0
- package/core/context-tools/signatures-tool.ts +501 -0
- package/core/context-tools/summary-tool.ts +307 -0
- package/core/context-tools/token-counter.ts +284 -0
- package/core/context-tools/types.ts +253 -0
- package/core/domain/agent-generator.ts +7 -5
- package/core/domain/agent-loader.ts +2 -2
- package/core/domain/analyzer.ts +19 -16
- package/core/domain/architecture-generator.ts +6 -3
- package/core/domain/context-estimator.ts +3 -4
- package/core/domain/snapshot-manager.ts +25 -22
- package/core/domain/task-stack.ts +24 -14
- package/core/errors.ts +1 -1
- package/core/events/events.ts +2 -4
- package/core/events/index.ts +1 -2
- package/core/index.ts +28 -12
- package/core/infrastructure/agent-detector.ts +3 -3
- package/core/infrastructure/ai-provider.ts +23 -20
- package/core/infrastructure/author-detector.ts +16 -10
- package/core/infrastructure/capability-installer.ts +2 -2
- package/core/infrastructure/claude-agent.ts +6 -6
- package/core/infrastructure/command-installer.ts +22 -17
- package/core/infrastructure/config-manager.ts +18 -14
- package/core/infrastructure/editors-config.ts +8 -4
- package/core/infrastructure/path-manager.ts +8 -6
- package/core/infrastructure/permission-manager.ts +20 -17
- package/core/infrastructure/setup.ts +42 -38
- package/core/infrastructure/update-checker.ts +5 -5
- package/core/integrations/issue-tracker/enricher.ts +8 -19
- package/core/integrations/issue-tracker/index.ts +2 -2
- package/core/integrations/issue-tracker/manager.ts +15 -15
- package/core/integrations/issue-tracker/types.ts +5 -22
- package/core/integrations/jira/client.ts +67 -59
- package/core/integrations/jira/index.ts +11 -14
- package/core/integrations/jira/mcp-adapter.ts +5 -10
- package/core/integrations/jira/service.ts +10 -10
- package/core/integrations/linear/client.ts +27 -18
- package/core/integrations/linear/index.ts +9 -12
- package/core/integrations/linear/service.ts +11 -11
- package/core/integrations/linear/sync.ts +8 -8
- package/core/outcomes/analyzer.ts +5 -18
- package/core/outcomes/index.ts +2 -2
- package/core/outcomes/recorder.ts +3 -3
- package/core/plugin/builtin/webhook.ts +19 -15
- package/core/plugin/hooks.ts +29 -21
- package/core/plugin/index.ts +7 -7
- package/core/plugin/loader.ts +19 -19
- package/core/plugin/registry.ts +12 -23
- package/core/schemas/agents.ts +1 -1
- package/core/schemas/analysis.ts +1 -1
- package/core/schemas/enriched-task.ts +62 -49
- package/core/schemas/ideas.ts +13 -13
- package/core/schemas/index.ts +17 -27
- package/core/schemas/issues.ts +40 -25
- package/core/schemas/metrics.ts +143 -0
- package/core/schemas/outcomes.ts +70 -62
- package/core/schemas/permissions.ts +15 -12
- package/core/schemas/prd.ts +27 -14
- package/core/schemas/project.ts +3 -3
- package/core/schemas/roadmap.ts +47 -34
- package/core/schemas/schemas.ts +3 -4
- package/core/schemas/shipped.ts +3 -3
- package/core/schemas/state.ts +43 -29
- package/core/server/index.ts +5 -6
- package/core/server/routes-extended.ts +68 -72
- package/core/server/routes.ts +3 -3
- package/core/server/server.ts +31 -26
- package/core/services/agent-generator.ts +237 -0
- package/core/services/agent-service.ts +2 -2
- package/core/services/breakdown-service.ts +2 -4
- package/core/services/context-generator.ts +299 -0
- package/core/services/context-selector.ts +420 -0
- package/core/services/doctor-service.ts +426 -0
- package/core/services/file-categorizer.ts +448 -0
- package/core/services/file-scorer.ts +270 -0
- package/core/services/git-analyzer.ts +267 -0
- package/core/services/index.ts +27 -10
- package/core/services/memory-service.ts +3 -4
- package/core/services/project-index.ts +911 -0
- package/core/services/project-service.ts +4 -4
- package/core/services/skill-installer.ts +14 -17
- package/core/services/skill-lock.ts +3 -3
- package/core/services/skill-service.ts +12 -6
- package/core/services/stack-detector.ts +245 -0
- package/core/services/sync-service.ts +170 -329
- package/core/services/watch-service.ts +294 -0
- package/core/session/compaction.ts +23 -31
- package/core/session/index.ts +11 -5
- package/core/session/log-migration.ts +3 -3
- package/core/session/metrics.ts +19 -14
- package/core/session/session-log-manager.ts +12 -17
- package/core/session/task-session-manager.ts +25 -25
- package/core/session/utils.ts +1 -1
- package/core/storage/ideas-storage.ts +41 -57
- package/core/storage/index-storage.ts +514 -0
- package/core/storage/index.ts +41 -13
- package/core/storage/metrics-storage.ts +320 -0
- package/core/storage/queue-storage.ts +35 -45
- package/core/storage/shipped-storage.ts +17 -20
- package/core/storage/state-storage.ts +50 -30
- package/core/storage/storage-manager.ts +6 -6
- package/core/storage/storage.ts +18 -15
- package/core/sync/auth-config.ts +3 -3
- package/core/sync/index.ts +13 -19
- package/core/sync/oauth-handler.ts +3 -3
- package/core/sync/sync-client.ts +4 -9
- package/core/sync/sync-manager.ts +12 -14
- package/core/types/commands.ts +42 -7
- package/core/types/index.ts +284 -302
- package/core/types/integrations.ts +3 -3
- package/core/types/storage.ts +49 -0
- package/core/types/utils.ts +3 -3
- package/core/utils/agent-stream.ts +3 -1
- package/core/utils/animations.ts +14 -11
- package/core/utils/branding.ts +7 -7
- package/core/utils/cache.ts +1 -3
- package/core/utils/collection-filters.ts +3 -15
- package/core/utils/date-helper.ts +2 -7
- package/core/utils/file-helper.ts +13 -8
- package/core/utils/jsonl-helper.ts +13 -10
- package/core/utils/keychain.ts +4 -8
- package/core/utils/logger.ts +1 -1
- package/core/utils/next-steps.ts +3 -3
- package/core/utils/output.ts +58 -11
- package/core/utils/project-commands.ts +6 -6
- package/core/utils/project-credentials.ts +5 -12
- package/core/utils/runtime.ts +2 -2
- package/core/utils/session-helper.ts +3 -4
- package/core/utils/version.ts +3 -3
- package/core/wizard/index.ts +13 -0
- package/core/wizard/onboarding.ts +633 -0
- package/core/workflow/state-machine.ts +7 -7
- package/dist/bin/prjct.mjs +18907 -13189
- package/dist/core/infrastructure/command-installer.js +96 -111
- package/dist/core/infrastructure/editors-config.js +6 -6
- package/dist/core/infrastructure/setup.js +256 -257
- package/dist/core/utils/version.js +9 -9
- package/package.json +11 -12
- package/scripts/build.js +3 -3
- package/scripts/postinstall.js +2 -2
- package/templates/mcp-config.json +6 -1
- package/templates/permissions/permissive.jsonc +1 -1
- package/templates/permissions/strict.jsonc +5 -9
- package/templates/global/docs/agents.md +0 -88
- package/templates/global/docs/architecture.md +0 -103
- package/templates/global/docs/commands.md +0 -96
- package/templates/global/docs/validation.md +0 -95
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* Handles project initialization detection, author management, and directory analysis.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { ProjectError } from '../errors'
|
|
8
|
+
import authorDetector from '../infrastructure/author-detector'
|
|
7
9
|
import configManager from '../infrastructure/config-manager'
|
|
8
10
|
import pathManager from '../infrastructure/path-manager'
|
|
9
|
-
import
|
|
11
|
+
import type { Author, CommandResult } from '../types'
|
|
12
|
+
import { isNotFoundError } from '../types/fs'
|
|
10
13
|
import * as fileHelper from '../utils/file-helper'
|
|
11
14
|
import out from '../utils/output'
|
|
12
|
-
import { isNotFoundError } from '../types/fs'
|
|
13
|
-
import type { Author, CommandResult } from '../types'
|
|
14
|
-
import { ProjectError } from '../errors'
|
|
15
15
|
|
|
16
16
|
export class ProjectService {
|
|
17
17
|
private currentAuthor: Author | null = null
|
|
@@ -12,15 +12,14 @@
|
|
|
12
12
|
* @version 1.0.0
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import os from 'os'
|
|
18
|
-
import
|
|
19
|
-
import {
|
|
15
|
+
import { exec as execCallback } from 'node:child_process'
|
|
16
|
+
import fs from 'node:fs/promises'
|
|
17
|
+
import os from 'node:os'
|
|
18
|
+
import path from 'node:path'
|
|
19
|
+
import { promisify } from 'node:util'
|
|
20
20
|
import { glob } from 'glob'
|
|
21
|
-
|
|
22
|
-
import { skillLock } from './skill-lock'
|
|
23
21
|
import type { SkillLockEntry } from './skill-lock'
|
|
22
|
+
import { skillLock } from './skill-lock'
|
|
24
23
|
|
|
25
24
|
const exec = promisify(execCallback)
|
|
26
25
|
|
|
@@ -103,7 +102,9 @@ export function parseSource(source: string): ParsedSource {
|
|
|
103
102
|
}
|
|
104
103
|
}
|
|
105
104
|
|
|
106
|
-
throw new Error(
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Invalid source format: "${source}". Expected "owner/repo", "owner/repo@skill-name", or "./local-path"`
|
|
107
|
+
)
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
// =============================================================================
|
|
@@ -131,7 +132,7 @@ async function discoverSkills(dir: string): Promise<Array<{ name: string; filePa
|
|
|
131
132
|
for (const filePath of subdirSkills) {
|
|
132
133
|
const name = path.basename(path.dirname(filePath))
|
|
133
134
|
// Avoid duplicate if already found as root
|
|
134
|
-
if (!skills.some(s => s.name === name)) {
|
|
135
|
+
if (!skills.some((s) => s.name === name)) {
|
|
135
136
|
skills.push({ name, filePath })
|
|
136
137
|
}
|
|
137
138
|
}
|
|
@@ -140,7 +141,7 @@ async function discoverSkills(dir: string): Promise<Array<{ name: string; filePa
|
|
|
140
141
|
const nestedSkills = await glob('skills/*/SKILL.md', { cwd: dir, absolute: true })
|
|
141
142
|
for (const filePath of nestedSkills) {
|
|
142
143
|
const name = path.basename(path.dirname(filePath))
|
|
143
|
-
if (!skills.some(s => s.name === name)) {
|
|
144
|
+
if (!skills.some((s) => s.name === name)) {
|
|
144
145
|
skills.push({ name, filePath })
|
|
145
146
|
}
|
|
146
147
|
}
|
|
@@ -155,11 +156,7 @@ async function discoverSkills(dir: string): Promise<Array<{ name: string; filePa
|
|
|
155
156
|
/**
|
|
156
157
|
* Add _prjct source tracking metadata to a skill's frontmatter
|
|
157
158
|
*/
|
|
158
|
-
function injectSourceMetadata(
|
|
159
|
-
content: string,
|
|
160
|
-
source: ParsedSource,
|
|
161
|
-
sha?: string
|
|
162
|
-
): string {
|
|
159
|
+
function injectSourceMetadata(content: string, source: ParsedSource, sha?: string): string {
|
|
163
160
|
const now = new Date().toISOString()
|
|
164
161
|
const prjctBlock = [
|
|
165
162
|
`_prjct:`,
|
|
@@ -181,7 +178,7 @@ function injectSourceMetadata(
|
|
|
181
178
|
frontmatter = frontmatter.replace(/\n?_prjct:[\s\S]*?(?=\n[a-zA-Z]|\n---|\s*$)/g, '')
|
|
182
179
|
|
|
183
180
|
// Append _prjct block
|
|
184
|
-
const updatedFrontmatter = frontmatter.trimEnd()
|
|
181
|
+
const updatedFrontmatter = `${frontmatter.trimEnd()}\n${prjctBlock.join('\n')}`
|
|
185
182
|
return content.replace(frontmatterRegex, `---\n${updatedFrontmatter}\n---`)
|
|
186
183
|
}
|
|
187
184
|
|
|
@@ -264,7 +261,7 @@ async function installFromGitHub(source: ParsedSource): Promise<InstallResult> {
|
|
|
264
261
|
|
|
265
262
|
// Filter to specific skill if requested
|
|
266
263
|
const skillsToInstall = source.skillName
|
|
267
|
-
? discoveredSkills.filter(s => s.name === source.skillName)
|
|
264
|
+
? discoveredSkills.filter((s) => s.name === source.skillName)
|
|
268
265
|
: discoveredSkills
|
|
269
266
|
|
|
270
267
|
if (source.skillName && skillsToInstall.length === 0) {
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* @version 1.0.0
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import fs from 'fs/promises'
|
|
13
|
-
import
|
|
14
|
-
import
|
|
12
|
+
import fs from 'node:fs/promises'
|
|
13
|
+
import os from 'node:os'
|
|
14
|
+
import path from 'node:path'
|
|
15
15
|
|
|
16
16
|
// =============================================================================
|
|
17
17
|
// Types
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
* @version 1.1.0
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import fs from 'fs/promises'
|
|
20
|
-
import path from 'path'
|
|
19
|
+
import fs from 'node:fs/promises'
|
|
20
|
+
import path from 'node:path'
|
|
21
21
|
import { glob } from 'glob'
|
|
22
22
|
|
|
23
|
-
import type {
|
|
23
|
+
import type { Skill, SkillMetadata, SkillSearchResult } from '../types'
|
|
24
24
|
import type { AIProviderName } from '../types/provider'
|
|
25
25
|
|
|
26
26
|
/**
|
|
@@ -46,7 +46,10 @@ function parseFrontmatter(content: string): { metadata: Record<string, unknown>;
|
|
|
46
46
|
|
|
47
47
|
// Handle arrays [item1, item2]
|
|
48
48
|
if (typeof value === 'string' && value.startsWith('[') && value.endsWith(']')) {
|
|
49
|
-
value = value
|
|
49
|
+
value = value
|
|
50
|
+
.slice(1, -1)
|
|
51
|
+
.split(',')
|
|
52
|
+
.map((s) => s.trim().replace(/['"]/g, ''))
|
|
50
53
|
}
|
|
51
54
|
// Remove quotes
|
|
52
55
|
else if (typeof value === 'string' && (value.startsWith('"') || value.startsWith("'"))) {
|
|
@@ -83,7 +86,10 @@ class SkillService {
|
|
|
83
86
|
/**
|
|
84
87
|
* Get all skill directories in order of priority
|
|
85
88
|
*/
|
|
86
|
-
private getSkillDirs(
|
|
89
|
+
private getSkillDirs(
|
|
90
|
+
projectPath?: string,
|
|
91
|
+
provider?: AIProviderName
|
|
92
|
+
): Array<{ dir: string; source: Skill['source'] }> {
|
|
87
93
|
const homeDir = process.env.HOME || process.env.USERPROFILE || '~'
|
|
88
94
|
const dirs: Array<{ dir: string; source: Skill['source'] }> = []
|
|
89
95
|
|
|
@@ -237,7 +243,7 @@ class SkillService {
|
|
|
237
243
|
}
|
|
238
244
|
|
|
239
245
|
// Tag match
|
|
240
|
-
if (skill.metadata.tags?.some(t => t.toLowerCase().includes(queryLower))) {
|
|
246
|
+
if (skill.metadata.tags?.some((t) => t.toLowerCase().includes(queryLower))) {
|
|
241
247
|
relevance += 3
|
|
242
248
|
}
|
|
243
249
|
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StackDetector - Detects project technology stack
|
|
3
|
+
*
|
|
4
|
+
* Analyzes the project to detect:
|
|
5
|
+
* - Frontend frameworks (React, Vue, Svelte, Angular)
|
|
6
|
+
* - Backend frameworks (Express, Fastify, Hono, etc.)
|
|
7
|
+
* - Database usage (Prisma, Mongoose, etc.)
|
|
8
|
+
* - Docker configuration
|
|
9
|
+
* - Testing frameworks
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from 'node:fs/promises'
|
|
13
|
+
import path from 'node:path'
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// TYPES
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
export interface StackDetection {
|
|
20
|
+
hasFrontend: boolean
|
|
21
|
+
hasBackend: boolean
|
|
22
|
+
hasDatabase: boolean
|
|
23
|
+
hasDocker: boolean
|
|
24
|
+
hasTesting: boolean
|
|
25
|
+
frontendType: 'web' | 'mobile' | 'both' | null
|
|
26
|
+
frameworks: string[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface PackageJson {
|
|
30
|
+
dependencies?: Record<string, string>
|
|
31
|
+
devDependencies?: Record<string, string>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// STACK DETECTOR
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
export class StackDetector {
|
|
39
|
+
private projectPath: string
|
|
40
|
+
|
|
41
|
+
constructor(projectPath: string) {
|
|
42
|
+
this.projectPath = projectPath
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Detect the full technology stack of the project
|
|
47
|
+
*/
|
|
48
|
+
async detect(): Promise<StackDetection> {
|
|
49
|
+
const stack: StackDetection = {
|
|
50
|
+
hasFrontend: false,
|
|
51
|
+
hasBackend: false,
|
|
52
|
+
hasDatabase: false,
|
|
53
|
+
hasDocker: false,
|
|
54
|
+
hasTesting: false,
|
|
55
|
+
frontendType: null,
|
|
56
|
+
frameworks: [],
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Try to read package.json for JS/TS projects
|
|
60
|
+
const pkg = await this.readPackageJson()
|
|
61
|
+
|
|
62
|
+
if (pkg) {
|
|
63
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
64
|
+
|
|
65
|
+
// Detect each category
|
|
66
|
+
this.detectFrontend(deps, stack)
|
|
67
|
+
this.detectBackend(deps, stack)
|
|
68
|
+
this.detectDatabase(deps, stack)
|
|
69
|
+
this.detectTesting(deps, pkg, stack)
|
|
70
|
+
this.collectFrameworks(deps, stack)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Docker detection (file-based)
|
|
74
|
+
stack.hasDocker = await this.detectDocker()
|
|
75
|
+
|
|
76
|
+
return stack
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ==========================================================================
|
|
80
|
+
// DETECTION METHODS
|
|
81
|
+
// ==========================================================================
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Detect frontend frameworks and type (web/mobile/both)
|
|
85
|
+
*/
|
|
86
|
+
private detectFrontend(deps: Record<string, string>, stack: StackDetection): void {
|
|
87
|
+
// Web frameworks
|
|
88
|
+
if (deps.react || deps.vue || deps.svelte || deps['@angular/core']) {
|
|
89
|
+
stack.hasFrontend = true
|
|
90
|
+
stack.frontendType = 'web'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Mobile frameworks
|
|
94
|
+
if (deps['react-native'] || deps.expo) {
|
|
95
|
+
stack.hasFrontend = true
|
|
96
|
+
stack.frontendType = stack.frontendType === 'web' ? 'both' : 'mobile'
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Detect backend frameworks
|
|
102
|
+
*/
|
|
103
|
+
private detectBackend(deps: Record<string, string>, stack: StackDetection): void {
|
|
104
|
+
const backendFrameworks = [
|
|
105
|
+
'express',
|
|
106
|
+
'fastify',
|
|
107
|
+
'hono',
|
|
108
|
+
'koa',
|
|
109
|
+
'@nestjs/core',
|
|
110
|
+
'nest',
|
|
111
|
+
'@hapi/hapi',
|
|
112
|
+
'restify',
|
|
113
|
+
'polka',
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
if (backendFrameworks.some((fw) => deps[fw])) {
|
|
117
|
+
stack.hasBackend = true
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Detect database/ORM usage
|
|
123
|
+
*/
|
|
124
|
+
private detectDatabase(deps: Record<string, string>, stack: StackDetection): void {
|
|
125
|
+
const databaseLibs = [
|
|
126
|
+
'prisma',
|
|
127
|
+
'@prisma/client',
|
|
128
|
+
'mongoose',
|
|
129
|
+
'pg',
|
|
130
|
+
'mysql2',
|
|
131
|
+
'sequelize',
|
|
132
|
+
'typeorm',
|
|
133
|
+
'drizzle-orm',
|
|
134
|
+
'knex',
|
|
135
|
+
'better-sqlite3',
|
|
136
|
+
'mongodb',
|
|
137
|
+
'redis',
|
|
138
|
+
'ioredis',
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
if (databaseLibs.some((lib) => deps[lib])) {
|
|
142
|
+
stack.hasDatabase = true
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Detect testing frameworks
|
|
148
|
+
*/
|
|
149
|
+
private detectTesting(
|
|
150
|
+
deps: Record<string, string>,
|
|
151
|
+
pkg: PackageJson,
|
|
152
|
+
stack: StackDetection
|
|
153
|
+
): void {
|
|
154
|
+
const testingFrameworks = [
|
|
155
|
+
'jest',
|
|
156
|
+
'vitest',
|
|
157
|
+
'mocha',
|
|
158
|
+
'@testing-library/react',
|
|
159
|
+
'@testing-library/vue',
|
|
160
|
+
'cypress',
|
|
161
|
+
'playwright',
|
|
162
|
+
'@playwright/test',
|
|
163
|
+
'ava',
|
|
164
|
+
'tap',
|
|
165
|
+
'bun-types', // Bun's built-in test runner
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
if (testingFrameworks.some((fw) => deps[fw] || pkg.devDependencies?.[fw])) {
|
|
169
|
+
stack.hasTesting = true
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Detect Docker configuration
|
|
175
|
+
*/
|
|
176
|
+
private async detectDocker(): Promise<boolean> {
|
|
177
|
+
const dockerFiles = ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml', '.dockerignore']
|
|
178
|
+
|
|
179
|
+
for (const file of dockerFiles) {
|
|
180
|
+
if (await this.fileExists(file)) {
|
|
181
|
+
return true
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return false
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Collect detected frameworks into the frameworks array
|
|
190
|
+
*/
|
|
191
|
+
private collectFrameworks(deps: Record<string, string>, stack: StackDetection): void {
|
|
192
|
+
// Frontend frameworks
|
|
193
|
+
if (deps.react) stack.frameworks.push('React')
|
|
194
|
+
if (deps.next) stack.frameworks.push('Next.js')
|
|
195
|
+
if (deps.vue) stack.frameworks.push('Vue')
|
|
196
|
+
if (deps.nuxt) stack.frameworks.push('Nuxt')
|
|
197
|
+
if (deps.svelte) stack.frameworks.push('Svelte')
|
|
198
|
+
if (deps['@angular/core']) stack.frameworks.push('Angular')
|
|
199
|
+
if (deps['react-native']) stack.frameworks.push('React Native')
|
|
200
|
+
if (deps.expo) stack.frameworks.push('Expo')
|
|
201
|
+
|
|
202
|
+
// Backend frameworks
|
|
203
|
+
if (deps.express) stack.frameworks.push('Express')
|
|
204
|
+
if (deps.fastify) stack.frameworks.push('Fastify')
|
|
205
|
+
if (deps.hono) stack.frameworks.push('Hono')
|
|
206
|
+
if (deps.koa) stack.frameworks.push('Koa')
|
|
207
|
+
if (deps['@nestjs/core'] || deps.nest) stack.frameworks.push('NestJS')
|
|
208
|
+
|
|
209
|
+
// Meta-frameworks
|
|
210
|
+
if (deps.astro) stack.frameworks.push('Astro')
|
|
211
|
+
if (deps.remix) stack.frameworks.push('Remix')
|
|
212
|
+
if (deps.gatsby) stack.frameworks.push('Gatsby')
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ==========================================================================
|
|
216
|
+
// HELPERS
|
|
217
|
+
// ==========================================================================
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Read and parse package.json
|
|
221
|
+
*/
|
|
222
|
+
private async readPackageJson(): Promise<PackageJson | null> {
|
|
223
|
+
try {
|
|
224
|
+
const pkgPath = path.join(this.projectPath, 'package.json')
|
|
225
|
+
const content = await fs.readFile(pkgPath, 'utf-8')
|
|
226
|
+
return JSON.parse(content)
|
|
227
|
+
} catch {
|
|
228
|
+
return null
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Check if a file exists in the project
|
|
234
|
+
*/
|
|
235
|
+
private async fileExists(filename: string): Promise<boolean> {
|
|
236
|
+
try {
|
|
237
|
+
await fs.access(path.join(this.projectPath, filename))
|
|
238
|
+
return true
|
|
239
|
+
} catch {
|
|
240
|
+
return false
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export default StackDetector
|