claude-cli-advanced-starter-pack 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/OVERVIEW.md +597 -0
- package/README.md +439 -0
- package/bin/gtask.js +282 -0
- package/bin/postinstall.js +53 -0
- package/package.json +69 -0
- package/src/agents/phase-dev-templates.js +1011 -0
- package/src/agents/templates.js +668 -0
- package/src/analysis/checklist-parser.js +414 -0
- package/src/analysis/codebase.js +481 -0
- package/src/cli/menu.js +958 -0
- package/src/commands/claude-audit.js +1482 -0
- package/src/commands/claude-settings.js +2243 -0
- package/src/commands/create-agent.js +681 -0
- package/src/commands/create-command.js +337 -0
- package/src/commands/create-hook.js +262 -0
- package/src/commands/create-phase-dev/codebase-analyzer.js +813 -0
- package/src/commands/create-phase-dev/documentation-generator.js +352 -0
- package/src/commands/create-phase-dev/post-completion.js +404 -0
- package/src/commands/create-phase-dev/scale-calculator.js +344 -0
- package/src/commands/create-phase-dev/wizard.js +492 -0
- package/src/commands/create-phase-dev.js +481 -0
- package/src/commands/create-skill.js +313 -0
- package/src/commands/create.js +446 -0
- package/src/commands/decompose.js +392 -0
- package/src/commands/detect-tech-stack.js +768 -0
- package/src/commands/explore-mcp/claude-md-updater.js +252 -0
- package/src/commands/explore-mcp/mcp-installer.js +346 -0
- package/src/commands/explore-mcp/mcp-registry.js +438 -0
- package/src/commands/explore-mcp.js +638 -0
- package/src/commands/gtask-init.js +641 -0
- package/src/commands/help.js +128 -0
- package/src/commands/init.js +1890 -0
- package/src/commands/install.js +250 -0
- package/src/commands/list.js +116 -0
- package/src/commands/roadmap.js +750 -0
- package/src/commands/setup-wizard.js +482 -0
- package/src/commands/setup.js +351 -0
- package/src/commands/sync.js +534 -0
- package/src/commands/test-run.js +456 -0
- package/src/commands/test-setup.js +456 -0
- package/src/commands/validate.js +67 -0
- package/src/config/tech-stack.defaults.json +182 -0
- package/src/config/tech-stack.schema.json +502 -0
- package/src/github/client.js +359 -0
- package/src/index.js +84 -0
- package/src/templates/claude-command.js +244 -0
- package/src/templates/issue-body.js +284 -0
- package/src/testing/config.js +411 -0
- package/src/utils/template-engine.js +398 -0
- package/src/utils/validate-templates.js +223 -0
- package/src/utils.js +396 -0
- package/templates/commands/ccasp-setup.template.md +113 -0
- package/templates/commands/context-audit.template.md +97 -0
- package/templates/commands/create-task-list.template.md +382 -0
- package/templates/commands/deploy-full.template.md +261 -0
- package/templates/commands/github-task-start.template.md +99 -0
- package/templates/commands/github-update.template.md +69 -0
- package/templates/commands/happy-start.template.md +117 -0
- package/templates/commands/phase-track.template.md +142 -0
- package/templates/commands/tunnel-start.template.md +127 -0
- package/templates/commands/tunnel-stop.template.md +106 -0
- package/templates/hooks/context-guardian.template.js +173 -0
- package/templates/hooks/deployment-orchestrator.template.js +219 -0
- package/templates/hooks/github-progress-hook.template.js +197 -0
- package/templates/hooks/happy-checkpoint-manager.template.js +222 -0
- package/templates/hooks/phase-dev-enforcer.template.js +183 -0
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tech Stack Detection
|
|
3
|
+
*
|
|
4
|
+
* Auto-detects the project's technology stack by scanning:
|
|
5
|
+
* - Package files (package.json, requirements.txt, Cargo.toml, etc.)
|
|
6
|
+
* - Config files (vite.config.ts, next.config.js, etc.)
|
|
7
|
+
* - Source directories and file patterns
|
|
8
|
+
* - Git remote URLs
|
|
9
|
+
* - Existing .claude configurations
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import chalk from 'chalk';
|
|
13
|
+
import ora from 'ora';
|
|
14
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
15
|
+
import { join, basename } from 'path';
|
|
16
|
+
import { execSync } from 'child_process';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Detection patterns for various technologies
|
|
20
|
+
*/
|
|
21
|
+
const DETECTION_PATTERNS = {
|
|
22
|
+
// Frontend frameworks
|
|
23
|
+
frontend: {
|
|
24
|
+
react: {
|
|
25
|
+
packages: ['react', 'react-dom'],
|
|
26
|
+
files: ['src/App.tsx', 'src/App.jsx', 'src/index.tsx'],
|
|
27
|
+
configFiles: ['vite.config.ts', 'vite.config.js', 'craco.config.js'],
|
|
28
|
+
},
|
|
29
|
+
vue: {
|
|
30
|
+
packages: ['vue'],
|
|
31
|
+
files: ['src/App.vue', 'src/main.ts'],
|
|
32
|
+
configFiles: ['vue.config.js', 'vite.config.ts'],
|
|
33
|
+
},
|
|
34
|
+
angular: {
|
|
35
|
+
packages: ['@angular/core'],
|
|
36
|
+
files: ['src/app/app.component.ts'],
|
|
37
|
+
configFiles: ['angular.json'],
|
|
38
|
+
},
|
|
39
|
+
svelte: {
|
|
40
|
+
packages: ['svelte'],
|
|
41
|
+
files: ['src/App.svelte'],
|
|
42
|
+
configFiles: ['svelte.config.js'],
|
|
43
|
+
},
|
|
44
|
+
nextjs: {
|
|
45
|
+
packages: ['next'],
|
|
46
|
+
configFiles: ['next.config.js', 'next.config.mjs', 'next.config.ts'],
|
|
47
|
+
},
|
|
48
|
+
nuxt: {
|
|
49
|
+
packages: ['nuxt'],
|
|
50
|
+
configFiles: ['nuxt.config.ts', 'nuxt.config.js'],
|
|
51
|
+
},
|
|
52
|
+
astro: {
|
|
53
|
+
packages: ['astro'],
|
|
54
|
+
configFiles: ['astro.config.mjs'],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// Build tools
|
|
59
|
+
buildTool: {
|
|
60
|
+
vite: {
|
|
61
|
+
packages: ['vite'],
|
|
62
|
+
configFiles: ['vite.config.ts', 'vite.config.js'],
|
|
63
|
+
},
|
|
64
|
+
webpack: {
|
|
65
|
+
packages: ['webpack'],
|
|
66
|
+
configFiles: ['webpack.config.js', 'webpack.config.ts'],
|
|
67
|
+
},
|
|
68
|
+
esbuild: {
|
|
69
|
+
packages: ['esbuild'],
|
|
70
|
+
},
|
|
71
|
+
parcel: {
|
|
72
|
+
packages: ['parcel'],
|
|
73
|
+
},
|
|
74
|
+
turbopack: {
|
|
75
|
+
packages: ['turbo'],
|
|
76
|
+
configFiles: ['turbo.json'],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// State managers
|
|
81
|
+
stateManager: {
|
|
82
|
+
zustand: { packages: ['zustand'] },
|
|
83
|
+
redux: { packages: ['@reduxjs/toolkit', 'redux'] },
|
|
84
|
+
mobx: { packages: ['mobx'] },
|
|
85
|
+
jotai: { packages: ['jotai'] },
|
|
86
|
+
recoil: { packages: ['recoil'] },
|
|
87
|
+
pinia: { packages: ['pinia'] },
|
|
88
|
+
vuex: { packages: ['vuex'] },
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
// Backend languages/frameworks
|
|
92
|
+
backend: {
|
|
93
|
+
fastapi: {
|
|
94
|
+
pythonPackages: ['fastapi'],
|
|
95
|
+
files: ['main.py', 'run_api.py', 'app/main.py'],
|
|
96
|
+
},
|
|
97
|
+
express: {
|
|
98
|
+
packages: ['express'],
|
|
99
|
+
files: ['server.js', 'app.js', 'index.js'],
|
|
100
|
+
},
|
|
101
|
+
nestjs: {
|
|
102
|
+
packages: ['@nestjs/core'],
|
|
103
|
+
files: ['src/main.ts'],
|
|
104
|
+
},
|
|
105
|
+
django: {
|
|
106
|
+
pythonPackages: ['django'],
|
|
107
|
+
files: ['manage.py'],
|
|
108
|
+
},
|
|
109
|
+
flask: {
|
|
110
|
+
pythonPackages: ['flask'],
|
|
111
|
+
files: ['app.py', 'wsgi.py'],
|
|
112
|
+
},
|
|
113
|
+
rails: {
|
|
114
|
+
gemPackages: ['rails'],
|
|
115
|
+
files: ['Gemfile', 'config/routes.rb'],
|
|
116
|
+
},
|
|
117
|
+
gin: {
|
|
118
|
+
goPackages: ['github.com/gin-gonic/gin'],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
// Databases
|
|
123
|
+
database: {
|
|
124
|
+
postgresql: {
|
|
125
|
+
envPatterns: ['DATABASE_URL.*postgres', 'POSTGRES_'],
|
|
126
|
+
packages: ['pg', 'psycopg2', 'asyncpg'],
|
|
127
|
+
},
|
|
128
|
+
mysql: {
|
|
129
|
+
envPatterns: ['DATABASE_URL.*mysql', 'MYSQL_'],
|
|
130
|
+
packages: ['mysql2', 'mysqlclient'],
|
|
131
|
+
},
|
|
132
|
+
mongodb: {
|
|
133
|
+
packages: ['mongodb', 'mongoose', 'pymongo'],
|
|
134
|
+
},
|
|
135
|
+
sqlite: {
|
|
136
|
+
files: ['*.db', '*.sqlite', '*.sqlite3'],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
// ORMs
|
|
141
|
+
orm: {
|
|
142
|
+
prisma: {
|
|
143
|
+
packages: ['prisma', '@prisma/client'],
|
|
144
|
+
configFiles: ['prisma/schema.prisma'],
|
|
145
|
+
},
|
|
146
|
+
drizzle: {
|
|
147
|
+
packages: ['drizzle-orm'],
|
|
148
|
+
},
|
|
149
|
+
typeorm: {
|
|
150
|
+
packages: ['typeorm'],
|
|
151
|
+
},
|
|
152
|
+
sqlalchemy: {
|
|
153
|
+
pythonPackages: ['sqlalchemy'],
|
|
154
|
+
},
|
|
155
|
+
sequelize: {
|
|
156
|
+
packages: ['sequelize'],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
// Testing frameworks
|
|
161
|
+
e2eFramework: {
|
|
162
|
+
playwright: {
|
|
163
|
+
packages: ['@playwright/test', 'playwright'],
|
|
164
|
+
configFiles: ['playwright.config.ts', 'playwright.config.js'],
|
|
165
|
+
},
|
|
166
|
+
cypress: {
|
|
167
|
+
packages: ['cypress'],
|
|
168
|
+
configFiles: ['cypress.config.js', 'cypress.config.ts'],
|
|
169
|
+
},
|
|
170
|
+
puppeteer: {
|
|
171
|
+
packages: ['puppeteer'],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
unitFramework: {
|
|
176
|
+
vitest: {
|
|
177
|
+
packages: ['vitest'],
|
|
178
|
+
configFiles: ['vitest.config.ts'],
|
|
179
|
+
},
|
|
180
|
+
jest: {
|
|
181
|
+
packages: ['jest'],
|
|
182
|
+
configFiles: ['jest.config.js', 'jest.config.ts'],
|
|
183
|
+
},
|
|
184
|
+
mocha: {
|
|
185
|
+
packages: ['mocha'],
|
|
186
|
+
},
|
|
187
|
+
pytest: {
|
|
188
|
+
pythonPackages: ['pytest'],
|
|
189
|
+
configFiles: ['pytest.ini', 'pyproject.toml'],
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
// Deployment platforms (detected from config files)
|
|
194
|
+
deployment: {
|
|
195
|
+
vercel: {
|
|
196
|
+
configFiles: ['vercel.json'],
|
|
197
|
+
},
|
|
198
|
+
netlify: {
|
|
199
|
+
configFiles: ['netlify.toml'],
|
|
200
|
+
},
|
|
201
|
+
cloudflare: {
|
|
202
|
+
configFiles: ['wrangler.toml', 'wrangler.json'],
|
|
203
|
+
},
|
|
204
|
+
railway: {
|
|
205
|
+
configFiles: ['railway.json', 'railway.toml'],
|
|
206
|
+
},
|
|
207
|
+
docker: {
|
|
208
|
+
configFiles: ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml'],
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
// Tunnel services
|
|
213
|
+
tunnel: {
|
|
214
|
+
ngrok: {
|
|
215
|
+
configFiles: ['ngrok.yml', 'ngrok.yaml'],
|
|
216
|
+
processPatterns: ['ngrok'],
|
|
217
|
+
},
|
|
218
|
+
cloudflare: {
|
|
219
|
+
configFiles: ['.cloudflared/'],
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Read and parse package.json
|
|
226
|
+
*/
|
|
227
|
+
function readPackageJson(projectRoot) {
|
|
228
|
+
const pkgPath = join(projectRoot, 'package.json');
|
|
229
|
+
if (!existsSync(pkgPath)) return null;
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
return JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
233
|
+
} catch {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Read Python requirements
|
|
240
|
+
*/
|
|
241
|
+
function readPythonRequirements(projectRoot) {
|
|
242
|
+
const files = ['requirements.txt', 'pyproject.toml', 'Pipfile'];
|
|
243
|
+
const packages = [];
|
|
244
|
+
|
|
245
|
+
for (const file of files) {
|
|
246
|
+
const filePath = join(projectRoot, file);
|
|
247
|
+
if (existsSync(filePath)) {
|
|
248
|
+
const content = readFileSync(filePath, 'utf8');
|
|
249
|
+
// Extract package names (simplified)
|
|
250
|
+
const matches = content.match(/^[a-zA-Z][a-zA-Z0-9_-]*/gm);
|
|
251
|
+
if (matches) packages.push(...matches);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return packages;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Check if any files match patterns
|
|
260
|
+
*/
|
|
261
|
+
function fileExists(projectRoot, patterns) {
|
|
262
|
+
if (!patterns) return false;
|
|
263
|
+
|
|
264
|
+
for (const pattern of patterns) {
|
|
265
|
+
const filePath = join(projectRoot, pattern);
|
|
266
|
+
if (existsSync(filePath)) return true;
|
|
267
|
+
}
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Check if packages are installed
|
|
273
|
+
*/
|
|
274
|
+
function hasPackages(pkgJson, packages) {
|
|
275
|
+
if (!pkgJson || !packages) return false;
|
|
276
|
+
|
|
277
|
+
const allDeps = {
|
|
278
|
+
...(pkgJson.dependencies || {}),
|
|
279
|
+
...(pkgJson.devDependencies || {}),
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
return packages.some((pkg) => allDeps[pkg]);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Detect port from various config files
|
|
287
|
+
*/
|
|
288
|
+
function detectPort(projectRoot, type) {
|
|
289
|
+
// Check vite.config.ts/js
|
|
290
|
+
const viteConfigs = ['vite.config.ts', 'vite.config.js'];
|
|
291
|
+
for (const config of viteConfigs) {
|
|
292
|
+
const configPath = join(projectRoot, config);
|
|
293
|
+
if (existsSync(configPath)) {
|
|
294
|
+
const content = readFileSync(configPath, 'utf8');
|
|
295
|
+
const portMatch = content.match(/port:\s*(\d+)/);
|
|
296
|
+
if (portMatch) return parseInt(portMatch[1]);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Check package.json scripts
|
|
301
|
+
const pkgJson = readPackageJson(projectRoot);
|
|
302
|
+
if (pkgJson?.scripts?.dev) {
|
|
303
|
+
const portMatch = pkgJson.scripts.dev.match(/--port[=\s]+(\d+)/);
|
|
304
|
+
if (portMatch) return parseInt(portMatch[1]);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Default ports
|
|
308
|
+
return type === 'frontend' ? 5173 : 8000;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Detect Git remote info
|
|
313
|
+
*/
|
|
314
|
+
function detectGitInfo(projectRoot) {
|
|
315
|
+
try {
|
|
316
|
+
const remoteUrl = execSync('git config --get remote.origin.url', {
|
|
317
|
+
cwd: projectRoot,
|
|
318
|
+
encoding: 'utf8',
|
|
319
|
+
}).trim();
|
|
320
|
+
|
|
321
|
+
// Parse GitHub URL
|
|
322
|
+
const githubMatch = remoteUrl.match(
|
|
323
|
+
/github\.com[:/]([^/]+)\/([^/.]+)/
|
|
324
|
+
);
|
|
325
|
+
if (githubMatch) {
|
|
326
|
+
return {
|
|
327
|
+
provider: 'github',
|
|
328
|
+
owner: githubMatch[1],
|
|
329
|
+
repo: githubMatch[2].replace('.git', ''),
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Parse GitLab URL
|
|
334
|
+
const gitlabMatch = remoteUrl.match(
|
|
335
|
+
/gitlab\.com[:/]([^/]+)\/([^/.]+)/
|
|
336
|
+
);
|
|
337
|
+
if (gitlabMatch) {
|
|
338
|
+
return {
|
|
339
|
+
provider: 'gitlab',
|
|
340
|
+
owner: gitlabMatch[1],
|
|
341
|
+
repo: gitlabMatch[2].replace('.git', ''),
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return null;
|
|
346
|
+
} catch {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Detect default branch
|
|
353
|
+
*/
|
|
354
|
+
function detectDefaultBranch(projectRoot) {
|
|
355
|
+
try {
|
|
356
|
+
// Try to get from git
|
|
357
|
+
const branch = execSync('git symbolic-ref refs/remotes/origin/HEAD', {
|
|
358
|
+
cwd: projectRoot,
|
|
359
|
+
encoding: 'utf8',
|
|
360
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
361
|
+
}).trim();
|
|
362
|
+
return branch.replace('refs/remotes/origin/', '');
|
|
363
|
+
} catch {
|
|
364
|
+
// Check if main or master exists
|
|
365
|
+
try {
|
|
366
|
+
execSync('git rev-parse --verify main', {
|
|
367
|
+
cwd: projectRoot,
|
|
368
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
369
|
+
});
|
|
370
|
+
return 'main';
|
|
371
|
+
} catch {
|
|
372
|
+
return 'master';
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Detect selectors from existing test files
|
|
379
|
+
*/
|
|
380
|
+
function detectSelectors(projectRoot) {
|
|
381
|
+
const testDirs = ['tests', 'test', 'e2e', '__tests__', 'playwright'];
|
|
382
|
+
const defaultSelectors = {
|
|
383
|
+
strategy: 'data-testid',
|
|
384
|
+
username: '[data-testid="username-input"]',
|
|
385
|
+
password: '[data-testid="password-input"]',
|
|
386
|
+
loginButton: '[data-testid="login-submit"]',
|
|
387
|
+
loginSuccess: '[data-testid="dashboard"]',
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// Scan for existing test files
|
|
391
|
+
for (const dir of testDirs) {
|
|
392
|
+
const testDir = join(projectRoot, dir);
|
|
393
|
+
if (!existsSync(testDir)) continue;
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
const files = readdirSync(testDir, { recursive: true });
|
|
397
|
+
for (const file of files) {
|
|
398
|
+
if (!file.toString().match(/\.(ts|js|spec|test)/)) continue;
|
|
399
|
+
|
|
400
|
+
const filePath = join(testDir, file.toString());
|
|
401
|
+
if (!statSync(filePath).isFile()) continue;
|
|
402
|
+
|
|
403
|
+
const content = readFileSync(filePath, 'utf8');
|
|
404
|
+
|
|
405
|
+
// Look for data-testid patterns
|
|
406
|
+
const testIdMatch = content.match(
|
|
407
|
+
/\[data-testid=["']([^"']+)["']\]/
|
|
408
|
+
);
|
|
409
|
+
if (testIdMatch) {
|
|
410
|
+
defaultSelectors.strategy = 'data-testid';
|
|
411
|
+
// Try to find specific patterns
|
|
412
|
+
const usernameMatch = content.match(
|
|
413
|
+
/\[data-testid=["']([^"']*(?:user|login|email)[^"']*)["']\]/i
|
|
414
|
+
);
|
|
415
|
+
const passwordMatch = content.match(
|
|
416
|
+
/\[data-testid=["']([^"']*(?:pass|pwd)[^"']*)["']\]/i
|
|
417
|
+
);
|
|
418
|
+
const submitMatch = content.match(
|
|
419
|
+
/\[data-testid=["']([^"']*(?:submit|login|signin)[^"']*)["']\]/i
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
if (usernameMatch) defaultSelectors.username = `[data-testid="${usernameMatch[1]}"]`;
|
|
423
|
+
if (passwordMatch) defaultSelectors.password = `[data-testid="${passwordMatch[1]}"]`;
|
|
424
|
+
if (submitMatch) defaultSelectors.loginButton = `[data-testid="${submitMatch[1]}"]`;
|
|
425
|
+
|
|
426
|
+
return defaultSelectors;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Look for name attribute patterns
|
|
430
|
+
const nameMatch = content.match(/name=["'](\w+)["']/);
|
|
431
|
+
if (nameMatch) {
|
|
432
|
+
defaultSelectors.strategy = 'name';
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
} catch {
|
|
436
|
+
// Continue if directory scan fails
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return defaultSelectors;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Detect existing ngrok or tunnel configuration
|
|
445
|
+
*/
|
|
446
|
+
function detectTunnelConfig(projectRoot) {
|
|
447
|
+
// Check for ngrok.yml
|
|
448
|
+
const ngrokConfig = join(projectRoot, 'ngrok.yml');
|
|
449
|
+
if (existsSync(ngrokConfig)) {
|
|
450
|
+
const content = readFileSync(ngrokConfig, 'utf8');
|
|
451
|
+
const subdomainMatch = content.match(/subdomain:\s*(\S+)/);
|
|
452
|
+
return {
|
|
453
|
+
service: 'ngrok',
|
|
454
|
+
subdomain: subdomainMatch?.[1] || null,
|
|
455
|
+
startCommand: 'ngrok http {{FRONTEND_PORT}}',
|
|
456
|
+
adminPort: 4040,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Check for localtunnel in package.json
|
|
461
|
+
const pkgJson = readPackageJson(projectRoot);
|
|
462
|
+
if (pkgJson?.devDependencies?.localtunnel) {
|
|
463
|
+
return {
|
|
464
|
+
service: 'localtunnel',
|
|
465
|
+
startCommand: 'lt --port {{FRONTEND_PORT}}',
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return {
|
|
470
|
+
service: 'none',
|
|
471
|
+
url: null,
|
|
472
|
+
subdomain: null,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Main detection function
|
|
478
|
+
*/
|
|
479
|
+
export async function detectTechStack(projectRoot, options = {}) {
|
|
480
|
+
const spinner = options.silent ? null : ora('Detecting tech stack...').start();
|
|
481
|
+
const result = {
|
|
482
|
+
version: '1.0.0',
|
|
483
|
+
project: {
|
|
484
|
+
name: basename(projectRoot),
|
|
485
|
+
rootPath: '.',
|
|
486
|
+
},
|
|
487
|
+
frontend: {},
|
|
488
|
+
backend: {},
|
|
489
|
+
database: {},
|
|
490
|
+
deployment: { frontend: {}, backend: {} },
|
|
491
|
+
devEnvironment: {},
|
|
492
|
+
testing: { e2e: {}, unit: {}, selectors: {}, credentials: {} },
|
|
493
|
+
versionControl: {},
|
|
494
|
+
urls: { local: {}, tunnel: {}, production: {} },
|
|
495
|
+
_detected: [], // Track what was auto-detected
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
// Read package.json
|
|
499
|
+
const pkgJson = readPackageJson(projectRoot);
|
|
500
|
+
const pythonPackages = readPythonRequirements(projectRoot);
|
|
501
|
+
|
|
502
|
+
// --- FRONTEND DETECTION ---
|
|
503
|
+
spinner?.text = 'Detecting frontend framework...';
|
|
504
|
+
|
|
505
|
+
for (const [framework, patterns] of Object.entries(DETECTION_PATTERNS.frontend)) {
|
|
506
|
+
if (
|
|
507
|
+
hasPackages(pkgJson, patterns.packages) ||
|
|
508
|
+
fileExists(projectRoot, patterns.files) ||
|
|
509
|
+
fileExists(projectRoot, patterns.configFiles)
|
|
510
|
+
) {
|
|
511
|
+
result.frontend.framework = framework;
|
|
512
|
+
result._detected.push(`frontend.framework: ${framework}`);
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Detect build tool
|
|
518
|
+
for (const [tool, patterns] of Object.entries(DETECTION_PATTERNS.buildTool)) {
|
|
519
|
+
if (
|
|
520
|
+
hasPackages(pkgJson, patterns.packages) ||
|
|
521
|
+
fileExists(projectRoot, patterns.configFiles)
|
|
522
|
+
) {
|
|
523
|
+
result.frontend.buildTool = tool;
|
|
524
|
+
result._detected.push(`frontend.buildTool: ${tool}`);
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Detect state manager
|
|
530
|
+
for (const [manager, patterns] of Object.entries(DETECTION_PATTERNS.stateManager)) {
|
|
531
|
+
if (hasPackages(pkgJson, patterns.packages)) {
|
|
532
|
+
result.frontend.stateManager = manager;
|
|
533
|
+
result._detected.push(`frontend.stateManager: ${manager}`);
|
|
534
|
+
break;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Detect frontend port
|
|
539
|
+
result.frontend.port = detectPort(projectRoot, 'frontend');
|
|
540
|
+
result._detected.push(`frontend.port: ${result.frontend.port}`);
|
|
541
|
+
|
|
542
|
+
// Detect styling
|
|
543
|
+
if (hasPackages(pkgJson, ['tailwindcss'])) {
|
|
544
|
+
result.frontend.styling = 'tailwind';
|
|
545
|
+
} else if (hasPackages(pkgJson, ['styled-components'])) {
|
|
546
|
+
result.frontend.styling = 'styled-components';
|
|
547
|
+
} else if (hasPackages(pkgJson, ['@emotion/react'])) {
|
|
548
|
+
result.frontend.styling = 'emotion';
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// --- BACKEND DETECTION ---
|
|
552
|
+
spinner?.text = 'Detecting backend framework...';
|
|
553
|
+
|
|
554
|
+
for (const [framework, patterns] of Object.entries(DETECTION_PATTERNS.backend)) {
|
|
555
|
+
const hasPkg = hasPackages(pkgJson, patterns.packages);
|
|
556
|
+
const hasPyPkg = patterns.pythonPackages?.some((p) =>
|
|
557
|
+
pythonPackages.includes(p)
|
|
558
|
+
);
|
|
559
|
+
const hasFiles = fileExists(projectRoot, patterns.files);
|
|
560
|
+
|
|
561
|
+
if (hasPkg || hasPyPkg || hasFiles) {
|
|
562
|
+
result.backend.framework = framework;
|
|
563
|
+
result._detected.push(`backend.framework: ${framework}`);
|
|
564
|
+
|
|
565
|
+
// Set language based on framework
|
|
566
|
+
if (['fastapi', 'django', 'flask'].includes(framework)) {
|
|
567
|
+
result.backend.language = 'python';
|
|
568
|
+
} else if (['express', 'nestjs'].includes(framework)) {
|
|
569
|
+
result.backend.language = 'node';
|
|
570
|
+
} else if (framework === 'rails') {
|
|
571
|
+
result.backend.language = 'ruby';
|
|
572
|
+
} else if (framework === 'gin') {
|
|
573
|
+
result.backend.language = 'go';
|
|
574
|
+
}
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
result.backend.port = detectPort(projectRoot, 'backend');
|
|
580
|
+
result.backend.healthEndpoint = '/api/health';
|
|
581
|
+
|
|
582
|
+
// --- DATABASE DETECTION ---
|
|
583
|
+
spinner?.text = 'Detecting database...';
|
|
584
|
+
|
|
585
|
+
for (const [db, patterns] of Object.entries(DETECTION_PATTERNS.database)) {
|
|
586
|
+
if (hasPackages(pkgJson, patterns.packages)) {
|
|
587
|
+
result.database.primary = db;
|
|
588
|
+
result._detected.push(`database.primary: ${db}`);
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Detect ORM
|
|
594
|
+
for (const [orm, patterns] of Object.entries(DETECTION_PATTERNS.orm)) {
|
|
595
|
+
if (
|
|
596
|
+
hasPackages(pkgJson, patterns.packages) ||
|
|
597
|
+
fileExists(projectRoot, patterns.configFiles)
|
|
598
|
+
) {
|
|
599
|
+
result.database.orm = orm;
|
|
600
|
+
result._detected.push(`database.orm: ${orm}`);
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// --- TESTING DETECTION ---
|
|
606
|
+
spinner?.text = 'Detecting testing frameworks...';
|
|
607
|
+
|
|
608
|
+
for (const [framework, patterns] of Object.entries(DETECTION_PATTERNS.e2eFramework)) {
|
|
609
|
+
if (
|
|
610
|
+
hasPackages(pkgJson, patterns.packages) ||
|
|
611
|
+
fileExists(projectRoot, patterns.configFiles)
|
|
612
|
+
) {
|
|
613
|
+
result.testing.e2e.framework = framework;
|
|
614
|
+
result._detected.push(`testing.e2e.framework: ${framework}`);
|
|
615
|
+
|
|
616
|
+
// Set config file
|
|
617
|
+
if (patterns.configFiles) {
|
|
618
|
+
for (const cfg of patterns.configFiles) {
|
|
619
|
+
if (existsSync(join(projectRoot, cfg))) {
|
|
620
|
+
result.testing.e2e.configFile = cfg;
|
|
621
|
+
break;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
for (const [framework, patterns] of Object.entries(DETECTION_PATTERNS.unitFramework)) {
|
|
630
|
+
if (
|
|
631
|
+
hasPackages(pkgJson, patterns.packages) ||
|
|
632
|
+
fileExists(projectRoot, patterns.configFiles)
|
|
633
|
+
) {
|
|
634
|
+
result.testing.unit.framework = framework;
|
|
635
|
+
result._detected.push(`testing.unit.framework: ${framework}`);
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Detect selectors
|
|
641
|
+
result.testing.selectors = detectSelectors(projectRoot);
|
|
642
|
+
|
|
643
|
+
// --- DEPLOYMENT DETECTION ---
|
|
644
|
+
spinner?.text = 'Detecting deployment platforms...';
|
|
645
|
+
|
|
646
|
+
for (const [platform, patterns] of Object.entries(DETECTION_PATTERNS.deployment)) {
|
|
647
|
+
if (fileExists(projectRoot, patterns.configFiles)) {
|
|
648
|
+
// Determine if frontend or backend
|
|
649
|
+
if (['vercel', 'netlify', 'cloudflare'].includes(platform)) {
|
|
650
|
+
result.deployment.frontend.platform = platform;
|
|
651
|
+
result._detected.push(`deployment.frontend.platform: ${platform}`);
|
|
652
|
+
} else if (platform === 'railway') {
|
|
653
|
+
result.deployment.backend.platform = platform;
|
|
654
|
+
result._detected.push(`deployment.backend.platform: ${platform}`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// --- DEV ENVIRONMENT ---
|
|
660
|
+
spinner?.text = 'Detecting dev environment...';
|
|
661
|
+
|
|
662
|
+
// Detect package manager
|
|
663
|
+
if (existsSync(join(projectRoot, 'pnpm-lock.yaml'))) {
|
|
664
|
+
result.devEnvironment.packageManager = 'pnpm';
|
|
665
|
+
} else if (existsSync(join(projectRoot, 'yarn.lock'))) {
|
|
666
|
+
result.devEnvironment.packageManager = 'yarn';
|
|
667
|
+
} else if (existsSync(join(projectRoot, 'bun.lockb'))) {
|
|
668
|
+
result.devEnvironment.packageManager = 'bun';
|
|
669
|
+
} else if (existsSync(join(projectRoot, 'package-lock.json'))) {
|
|
670
|
+
result.devEnvironment.packageManager = 'npm';
|
|
671
|
+
} else if (existsSync(join(projectRoot, 'requirements.txt'))) {
|
|
672
|
+
result.devEnvironment.packageManager = 'pip';
|
|
673
|
+
} else if (existsSync(join(projectRoot, 'poetry.lock'))) {
|
|
674
|
+
result.devEnvironment.packageManager = 'poetry';
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Detect tunnel
|
|
678
|
+
result.devEnvironment.tunnel = detectTunnelConfig(projectRoot);
|
|
679
|
+
|
|
680
|
+
// Detect container
|
|
681
|
+
if (fileExists(projectRoot, ['Dockerfile', 'docker-compose.yml'])) {
|
|
682
|
+
result.devEnvironment.container = 'docker';
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// --- VERSION CONTROL ---
|
|
686
|
+
spinner?.text = 'Detecting version control...';
|
|
687
|
+
|
|
688
|
+
const gitInfo = detectGitInfo(projectRoot);
|
|
689
|
+
if (gitInfo) {
|
|
690
|
+
result.versionControl = {
|
|
691
|
+
...gitInfo,
|
|
692
|
+
defaultBranch: detectDefaultBranch(projectRoot),
|
|
693
|
+
projectBoard: { type: 'none' },
|
|
694
|
+
};
|
|
695
|
+
result._detected.push(`versionControl: ${gitInfo.provider}/${gitInfo.owner}/${gitInfo.repo}`);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// --- BUILD URLs ---
|
|
699
|
+
result.urls.local = {
|
|
700
|
+
frontend: `http://localhost:${result.frontend.port || 5173}`,
|
|
701
|
+
backend: `http://localhost:${result.backend.port || 8000}`,
|
|
702
|
+
api: `http://localhost:${result.backend.port || 8000}/api`,
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
spinner?.succeed('Tech stack detection complete');
|
|
706
|
+
|
|
707
|
+
return result;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Run detection and output results
|
|
712
|
+
*/
|
|
713
|
+
export async function runDetection(options = {}) {
|
|
714
|
+
const projectRoot = options.projectRoot || process.cwd();
|
|
715
|
+
|
|
716
|
+
console.log(chalk.cyan('\n📦 Tech Stack Detection\n'));
|
|
717
|
+
console.log(chalk.dim(`Project: ${projectRoot}\n`));
|
|
718
|
+
|
|
719
|
+
const result = await detectTechStack(projectRoot, options);
|
|
720
|
+
|
|
721
|
+
// Display results
|
|
722
|
+
console.log(chalk.green('\n✓ Detected Technologies:\n'));
|
|
723
|
+
|
|
724
|
+
if (result.frontend.framework) {
|
|
725
|
+
console.log(chalk.white(` Frontend: ${chalk.cyan(result.frontend.framework)}`));
|
|
726
|
+
if (result.frontend.buildTool) {
|
|
727
|
+
console.log(chalk.dim(` Build: ${result.frontend.buildTool}`));
|
|
728
|
+
}
|
|
729
|
+
if (result.frontend.stateManager) {
|
|
730
|
+
console.log(chalk.dim(` State: ${result.frontend.stateManager}`));
|
|
731
|
+
}
|
|
732
|
+
console.log(chalk.dim(` Port: ${result.frontend.port}`));
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (result.backend.framework) {
|
|
736
|
+
console.log(chalk.white(` Backend: ${chalk.cyan(result.backend.framework)} (${result.backend.language})`));
|
|
737
|
+
console.log(chalk.dim(` Port: ${result.backend.port}`));
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (result.database.primary) {
|
|
741
|
+
console.log(chalk.white(` Database: ${chalk.cyan(result.database.primary)}`));
|
|
742
|
+
if (result.database.orm) {
|
|
743
|
+
console.log(chalk.dim(` ORM: ${result.database.orm}`));
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if (result.testing.e2e.framework) {
|
|
748
|
+
console.log(chalk.white(` E2E Tests: ${chalk.cyan(result.testing.e2e.framework)}`));
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (result.testing.unit.framework) {
|
|
752
|
+
console.log(chalk.white(` Unit Tests: ${chalk.cyan(result.testing.unit.framework)}`));
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (result.versionControl.provider) {
|
|
756
|
+
console.log(chalk.white(` Git: ${chalk.cyan(`${result.versionControl.owner}/${result.versionControl.repo}`)}`));
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (result.devEnvironment.packageManager) {
|
|
760
|
+
console.log(chalk.white(` Package Manager: ${chalk.cyan(result.devEnvironment.packageManager)}`));
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
console.log('');
|
|
764
|
+
|
|
765
|
+
return result;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
export default { detectTechStack, runDetection };
|