create-sdd-project 0.2.1 → 0.2.2
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/lib/init-generator.js +19 -12
- package/lib/prompts.js +9 -9
- package/lib/scanner.js +45 -14
- package/package.json +1 -1
package/lib/init-generator.js
CHANGED
|
@@ -314,7 +314,6 @@ function adaptBackendStandards(template, scan) {
|
|
|
314
314
|
);
|
|
315
315
|
|
|
316
316
|
// Update Technology Stack
|
|
317
|
-
const framework = scan.backend.framework || 'Unknown';
|
|
318
317
|
const orm = scan.backend.orm;
|
|
319
318
|
const db = scan.backend.db;
|
|
320
319
|
const lang = scan.language === 'typescript' ? 'TypeScript' : 'JavaScript';
|
|
@@ -325,8 +324,10 @@ function adaptBackendStandards(template, scan) {
|
|
|
325
324
|
|
|
326
325
|
let stackLines = [
|
|
327
326
|
`- **Runtime**: Node.js with ${lang}`,
|
|
328
|
-
`- **Framework**: ${framework}`,
|
|
329
327
|
];
|
|
328
|
+
if (scan.backend.framework) {
|
|
329
|
+
stackLines.push(`- **Framework**: ${scan.backend.framework}`);
|
|
330
|
+
}
|
|
330
331
|
if (orm) {
|
|
331
332
|
stackLines.push(`- **ORM**: ${orm}${db ? ` (${db})` : ''}`);
|
|
332
333
|
} else if (db) {
|
|
@@ -352,21 +353,23 @@ function adaptBackendStandards(template, scan) {
|
|
|
352
353
|
const patternLabel = patternLabels[scan.srcStructure.pattern] || 'Custom';
|
|
353
354
|
|
|
354
355
|
const archStructure = buildArchitectureTree(scan);
|
|
356
|
+
// Robust: match any "## Architecture — <anything>" heading up to "## Naming"
|
|
355
357
|
content = content.replace(
|
|
356
|
-
/## Architecture —
|
|
358
|
+
/## Architecture — [^\n]+\n\n```\n[\s\S]*?```\n\n(?:### Layer Rules\n\n[\s\S]*?)?(?=\n## Naming)/,
|
|
357
359
|
`## Architecture — ${patternLabel}\n\n\`\`\`\n${archStructure}\`\`\`\n\n<!-- TODO: Add layer rules that match your project's architecture. -->\n\n`
|
|
358
360
|
);
|
|
359
361
|
|
|
360
362
|
// Update Database Patterns section if not Prisma
|
|
363
|
+
// Robust: match from "## Database Patterns" to next "## " heading
|
|
361
364
|
if (!scan.backend.orm) {
|
|
362
365
|
content = content.replace(
|
|
363
|
-
/## Database Patterns\n\n
|
|
364
|
-
`## Database Patterns\n\n<!-- TODO: Add database access patterns for your project. -->\n
|
|
366
|
+
/## Database Patterns\n\n[\s\S]*?(?=\n## )/,
|
|
367
|
+
`## Database Patterns\n\n<!-- TODO: Add database access patterns for your project. -->\n`
|
|
365
368
|
);
|
|
366
369
|
} else if (scan.backend.orm !== 'Prisma') {
|
|
367
370
|
content = content.replace(
|
|
368
|
-
/## Database Patterns\n\n
|
|
369
|
-
`## Database Patterns\n\n<!-- TODO: Add ${scan.backend.orm} best practices and patterns for your project. -->\n
|
|
371
|
+
/## Database Patterns\n\n[\s\S]*?(?=\n## )/,
|
|
372
|
+
`## Database Patterns\n\n<!-- TODO: Add ${scan.backend.orm} best practices and patterns for your project. -->\n`
|
|
370
373
|
);
|
|
371
374
|
}
|
|
372
375
|
|
|
@@ -444,7 +447,7 @@ function buildArchitectureTree(scan) {
|
|
|
444
447
|
}
|
|
445
448
|
|
|
446
449
|
function capitalizeFramework(name) {
|
|
447
|
-
const map = { jest: 'Jest', vitest: 'Vitest', mocha: 'Mocha' };
|
|
450
|
+
const map = { jest: 'Jest', vitest: 'Vitest', mocha: 'Mocha', playwright: 'Playwright', cypress: 'Cypress' };
|
|
448
451
|
return map[name] || name;
|
|
449
452
|
}
|
|
450
453
|
|
|
@@ -467,15 +470,17 @@ function adaptAgentsMd(template, config, scan) {
|
|
|
467
470
|
const tree = rootDirs.map((d) => `├── ${d.replace(/\/$/, '/')} `).join('\n');
|
|
468
471
|
const treeBlock = `\`\`\`\nproject/\n${tree}\n└── docs/ ← Documentation\n\`\`\``;
|
|
469
472
|
|
|
473
|
+
// Robust: flexible whitespace between CONFIG comment and code block
|
|
470
474
|
content = content.replace(
|
|
471
|
-
/<!-- CONFIG: Adjust directories
|
|
475
|
+
/<!-- CONFIG: Adjust directories[^>]*-->\n+```\nproject\/\n[\s\S]*?```/,
|
|
472
476
|
treeBlock
|
|
473
477
|
);
|
|
474
478
|
|
|
475
479
|
// If not monorepo, simplify the install instructions
|
|
480
|
+
// Robust: match any number of table rows (not hardcoded count)
|
|
476
481
|
if (!scan.isMonorepo) {
|
|
477
482
|
content = content.replace(
|
|
478
|
-
/\*\*Critical\*\*: NEVER install dependencies in the root directory\.\n\n
|
|
483
|
+
/\*\*Critical\*\*: NEVER install dependencies in the root directory\.\n\n(\|.*\n)+/,
|
|
479
484
|
''
|
|
480
485
|
);
|
|
481
486
|
}
|
|
@@ -528,9 +533,11 @@ function configureKeyFacts(template, config, scan) {
|
|
|
528
533
|
|
|
529
534
|
// Technology Stack from scan
|
|
530
535
|
if (scan.backend.detected) {
|
|
531
|
-
const fw = scan.backend.framework || 'Unknown';
|
|
532
536
|
const runtime = scan.language === 'typescript' ? 'Node.js (TypeScript)' : 'Node.js';
|
|
533
|
-
|
|
537
|
+
const backendLabel = scan.backend.framework
|
|
538
|
+
? `${scan.backend.framework}, ${runtime}`
|
|
539
|
+
: runtime;
|
|
540
|
+
content = content.replace('[Framework, runtime, version]', backendLabel);
|
|
534
541
|
|
|
535
542
|
if (scan.backend.db) {
|
|
536
543
|
content = content.replace('[Type, host, port]', scan.backend.db);
|
package/lib/prompts.js
CHANGED
|
@@ -47,29 +47,29 @@ function askMultiline(rl, question) {
|
|
|
47
47
|
console.log(' (Enter text below. Empty line to finish, or press Enter to skip)');
|
|
48
48
|
const lines = [];
|
|
49
49
|
let firstLine = true;
|
|
50
|
+
let done = false;
|
|
50
51
|
|
|
51
|
-
const
|
|
52
|
+
const handler = (line) => {
|
|
53
|
+
if (done) return;
|
|
52
54
|
if (firstLine && line.trim() === '') {
|
|
53
|
-
|
|
55
|
+
done = true;
|
|
56
|
+
rl.removeListener('line', handler);
|
|
54
57
|
resolve('');
|
|
55
58
|
return;
|
|
56
59
|
}
|
|
57
60
|
firstLine = false;
|
|
58
61
|
if (line.trim() === '') {
|
|
59
|
-
|
|
62
|
+
done = true;
|
|
63
|
+
rl.removeListener('line', handler);
|
|
60
64
|
resolve(lines.join('\n'));
|
|
61
65
|
} else {
|
|
62
66
|
lines.push(line);
|
|
67
|
+
process.stdout.write(' > ');
|
|
63
68
|
}
|
|
64
69
|
};
|
|
65
70
|
|
|
66
71
|
process.stdout.write(' > ');
|
|
67
|
-
rl.on('line',
|
|
68
|
-
if (lines.length > 0 || line.trim() !== '') {
|
|
69
|
-
process.stdout.write(' > ');
|
|
70
|
-
}
|
|
71
|
-
onLine(line);
|
|
72
|
-
});
|
|
72
|
+
rl.on('line', handler);
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
75
|
|
package/lib/scanner.js
CHANGED
|
@@ -67,11 +67,12 @@ function detectBackend(dir, pkg) {
|
|
|
67
67
|
|
|
68
68
|
// Framework detection
|
|
69
69
|
const frameworks = [
|
|
70
|
-
{
|
|
71
|
-
{
|
|
72
|
-
{
|
|
73
|
-
{
|
|
74
|
-
{
|
|
70
|
+
{ dep: 'express', label: 'Express' },
|
|
71
|
+
{ dep: 'fastify', label: 'Fastify' },
|
|
72
|
+
{ dep: 'koa', label: 'Koa' },
|
|
73
|
+
{ dep: '@nestjs/core', label: 'NestJS' },
|
|
74
|
+
{ dep: '@hapi/hapi', label: 'Hapi' },
|
|
75
|
+
{ dep: '@adonisjs/core', label: 'AdonisJS' },
|
|
75
76
|
];
|
|
76
77
|
|
|
77
78
|
for (const fw of frameworks) {
|
|
@@ -84,11 +85,14 @@ function detectBackend(dir, pkg) {
|
|
|
84
85
|
|
|
85
86
|
// ORM detection
|
|
86
87
|
const orms = [
|
|
87
|
-
{
|
|
88
|
-
{
|
|
89
|
-
{
|
|
90
|
-
{
|
|
91
|
-
{
|
|
88
|
+
{ dep: '@prisma/client', label: 'Prisma' },
|
|
89
|
+
{ dep: 'mongoose', label: 'Mongoose' },
|
|
90
|
+
{ dep: 'typeorm', label: 'TypeORM' },
|
|
91
|
+
{ dep: 'sequelize', label: 'Sequelize' },
|
|
92
|
+
{ dep: 'drizzle-orm', label: 'Drizzle' },
|
|
93
|
+
{ dep: 'knex', label: 'Knex' },
|
|
94
|
+
{ dep: '@mikro-orm/core', label: 'MikroORM' },
|
|
95
|
+
{ dep: 'objection', label: 'Objection.js' },
|
|
92
96
|
];
|
|
93
97
|
|
|
94
98
|
for (const orm of orms) {
|
|
@@ -129,9 +133,13 @@ function detectFrontend(dir, pkg) {
|
|
|
129
133
|
const deps = getAllDeps(pkg);
|
|
130
134
|
const result = { detected: false, framework: null, styling: null, components: null, state: null };
|
|
131
135
|
|
|
132
|
-
// Framework detection
|
|
136
|
+
// Framework detection (order matters — more specific first)
|
|
133
137
|
const frameworks = [
|
|
134
138
|
{ dep: 'next', label: 'Next.js' },
|
|
139
|
+
{ dep: 'nuxt', label: 'Nuxt' },
|
|
140
|
+
{ dep: '@remix-run/react', label: 'Remix' },
|
|
141
|
+
{ dep: 'astro', label: 'Astro' },
|
|
142
|
+
{ dep: 'solid-js', label: 'SolidJS' },
|
|
135
143
|
{ dep: 'react', label: 'React' },
|
|
136
144
|
{ dep: 'vue', label: 'Vue' },
|
|
137
145
|
{ dep: '@angular/core', label: 'Angular' },
|
|
@@ -155,6 +163,8 @@ function detectFrontend(dir, pkg) {
|
|
|
155
163
|
// Component libraries
|
|
156
164
|
if (deps['@radix-ui/react-dialog'] || deps['@radix-ui/react-select'] || hasRadixDep(deps)) {
|
|
157
165
|
result.components = 'Radix UI';
|
|
166
|
+
} else if (deps['@headlessui/react']) {
|
|
167
|
+
result.components = 'Headless UI';
|
|
158
168
|
} else if (deps['@mui/material']) {
|
|
159
169
|
result.components = 'Material UI';
|
|
160
170
|
} else if (deps['@chakra-ui/react']) {
|
|
@@ -166,6 +176,8 @@ function detectFrontend(dir, pkg) {
|
|
|
166
176
|
// State management
|
|
167
177
|
if (deps['zustand']) result.state = 'Zustand';
|
|
168
178
|
else if (deps['@reduxjs/toolkit'] || deps['redux']) result.state = 'Redux';
|
|
179
|
+
else if (deps['jotai']) result.state = 'Jotai';
|
|
180
|
+
else if (deps['@tanstack/react-query']) result.state = 'TanStack Query';
|
|
169
181
|
else if (deps['recoil']) result.state = 'Recoil';
|
|
170
182
|
else if (deps['pinia']) result.state = 'Pinia';
|
|
171
183
|
else if (deps['mobx']) result.state = 'MobX';
|
|
@@ -280,13 +292,14 @@ function detectTests(dir, pkg) {
|
|
|
280
292
|
const deps = getAllDeps(pkg);
|
|
281
293
|
const result = {
|
|
282
294
|
framework: 'none',
|
|
295
|
+
e2eFramework: null,
|
|
283
296
|
hasConfig: false,
|
|
284
297
|
testFiles: 0,
|
|
285
298
|
testDirs: [],
|
|
286
299
|
estimatedCoverage: 'none',
|
|
287
300
|
};
|
|
288
301
|
|
|
289
|
-
//
|
|
302
|
+
// Unit test framework detection
|
|
290
303
|
if (deps['jest'] || deps['@jest/core'] || deps['ts-jest'] || deps['@types/jest']) {
|
|
291
304
|
result.framework = 'jest';
|
|
292
305
|
} else if (deps['vitest']) {
|
|
@@ -295,11 +308,21 @@ function detectTests(dir, pkg) {
|
|
|
295
308
|
result.framework = 'mocha';
|
|
296
309
|
}
|
|
297
310
|
|
|
311
|
+
// E2E test framework detection (supplement, doesn't override unit framework)
|
|
312
|
+
result.e2eFramework = null;
|
|
313
|
+
if (deps['@playwright/test'] || deps['playwright']) {
|
|
314
|
+
result.e2eFramework = 'playwright';
|
|
315
|
+
} else if (deps['cypress']) {
|
|
316
|
+
result.e2eFramework = 'cypress';
|
|
317
|
+
}
|
|
318
|
+
|
|
298
319
|
// Config files
|
|
299
320
|
const configFiles = [
|
|
300
321
|
'jest.config.js', 'jest.config.ts', 'jest.config.mjs', 'jest.config.cjs',
|
|
301
322
|
'vitest.config.js', 'vitest.config.ts', 'vitest.config.mjs',
|
|
302
323
|
'.mocharc.yml', '.mocharc.json', '.mocharc.js',
|
|
324
|
+
'playwright.config.ts', 'playwright.config.js',
|
|
325
|
+
'cypress.config.ts', 'cypress.config.js',
|
|
303
326
|
];
|
|
304
327
|
result.hasConfig = configFiles.some((f) => fs.existsSync(path.join(dir, f)));
|
|
305
328
|
|
|
@@ -417,7 +440,10 @@ function detectDatabaseFromPrisma(dir) {
|
|
|
417
440
|
if (fs.existsSync(schemaPath)) {
|
|
418
441
|
try {
|
|
419
442
|
const content = fs.readFileSync(schemaPath, 'utf8');
|
|
420
|
-
|
|
443
|
+
// Match provider only within a datasource block (skip generator blocks)
|
|
444
|
+
const dsBlock = content.match(/datasource\s+\w+\s*\{[^}]*\}/);
|
|
445
|
+
const providerSource = dsBlock ? dsBlock[0] : content;
|
|
446
|
+
const match = providerSource.match(/provider\s*=\s*"(\w+)"/);
|
|
421
447
|
if (match) {
|
|
422
448
|
const provider = match[1];
|
|
423
449
|
const dbMap = {
|
|
@@ -444,6 +470,7 @@ function detectDatabaseFromEnv(dir) {
|
|
|
444
470
|
if (fs.existsSync(envPath)) {
|
|
445
471
|
try {
|
|
446
472
|
const content = fs.readFileSync(envPath, 'utf8');
|
|
473
|
+
// Check DATABASE_URL
|
|
447
474
|
const match = content.match(/DATABASE_URL\s*=\s*(\S+)/);
|
|
448
475
|
if (match) {
|
|
449
476
|
const url = match[1].replace(/["']/g, '');
|
|
@@ -452,6 +479,10 @@ function detectDatabaseFromEnv(dir) {
|
|
|
452
479
|
if (url.startsWith('mysql://')) return 'MySQL';
|
|
453
480
|
if (url.includes('sqlite')) return 'SQLite';
|
|
454
481
|
}
|
|
482
|
+
// Check MONGODB_URI / MONGO_URI
|
|
483
|
+
if (/^MONGO(?:DB)?_URI\s*=/m.test(content)) return 'MongoDB';
|
|
484
|
+
// Check REDIS_URL
|
|
485
|
+
if (/^REDIS_URL\s*=/m.test(content)) return 'Redis';
|
|
455
486
|
} catch { /* ignore */ }
|
|
456
487
|
}
|
|
457
488
|
}
|
|
@@ -466,7 +497,7 @@ function detectPort(dir, pkg) {
|
|
|
466
497
|
if (fs.existsSync(envPath)) {
|
|
467
498
|
try {
|
|
468
499
|
const content = fs.readFileSync(envPath, 'utf8');
|
|
469
|
-
const match = content.match(/^PORT\s*=\s*(\d+)/m);
|
|
500
|
+
const match = content.match(/^PORT\s*=\s*["']?(\d+)/m);
|
|
470
501
|
if (match) return parseInt(match[1], 10);
|
|
471
502
|
} catch { /* ignore */ }
|
|
472
503
|
}
|