ultra-dex 3.1.0 ā 3.3.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/README.md +79 -74
- package/assets/code-patterns/clerk-middleware.ts +138 -0
- package/assets/code-patterns/prisma-schema.prisma +224 -0
- package/assets/code-patterns/rls-policies.sql +246 -0
- package/assets/code-patterns/server-actions.ts +191 -0
- package/assets/code-patterns/trpc-router.ts +258 -0
- package/assets/cursor-rules/13-ai-integration.mdc +155 -0
- package/assets/cursor-rules/14-server-components.mdc +81 -0
- package/assets/cursor-rules/15-server-actions.mdc +102 -0
- package/assets/cursor-rules/16-edge-middleware.mdc +105 -0
- package/assets/cursor-rules/17-streaming-ssr.mdc +138 -0
- package/bin/ultra-dex.js +50 -1
- package/lib/commands/agents.js +16 -13
- package/lib/commands/banner.js +43 -21
- package/lib/commands/build.js +26 -17
- package/lib/commands/cloud.js +780 -0
- package/lib/commands/doctor.js +98 -79
- package/lib/commands/exec.js +434 -0
- package/lib/commands/generate.js +19 -16
- package/lib/commands/github.js +475 -0
- package/lib/commands/init.js +52 -56
- package/lib/commands/scaffold.js +151 -0
- package/lib/commands/search.js +477 -0
- package/lib/commands/serve.js +15 -13
- package/lib/commands/state.js +43 -70
- package/lib/commands/swarm.js +31 -9
- package/lib/config/theme.js +47 -0
- package/lib/mcp/client.js +502 -0
- package/lib/providers/agent-sdk.js +630 -0
- package/lib/providers/anthropic-agents.js +580 -0
- package/lib/templates/code/clerk-middleware.ts +138 -0
- package/lib/templates/code/prisma-schema.prisma +224 -0
- package/lib/templates/code/rls-policies.sql +246 -0
- package/lib/templates/code/server-actions.ts +191 -0
- package/lib/templates/code/trpc-router.ts +258 -0
- package/lib/themes/doomsday.js +229 -0
- package/lib/ui/index.js +5 -0
- package/lib/ui/interface.js +241 -0
- package/lib/ui/spinners.js +116 -0
- package/lib/ui/theme.js +183 -0
- package/lib/utils/agents.js +32 -0
- package/lib/utils/browser.js +373 -0
- package/lib/utils/help.js +64 -0
- package/lib/utils/messages.js +35 -0
- package/lib/utils/progress.js +24 -0
- package/lib/utils/prompts.js +47 -0
- package/lib/utils/spinners.js +46 -0
- package/lib/utils/status.js +31 -0
- package/lib/utils/tables.js +41 -0
- package/lib/utils/theme-state.js +9 -0
- package/lib/utils/version-display.js +32 -0
- package/package.json +19 -4
package/lib/commands/init.js
CHANGED
|
@@ -8,9 +8,10 @@ import { QUICK_START_TEMPLATE } from '../templates/quick-start.js';
|
|
|
8
8
|
import { CONTEXT_TEMPLATE } from '../templates/context.js';
|
|
9
9
|
import { validateProjectName, validateSafePath } from '../utils/validation.js';
|
|
10
10
|
import { ASSETS_ROOT, ROOT_FALLBACK, LIVE_TEMPLATES_ROOT } from '../config/paths.js';
|
|
11
|
-
import { githubBlobUrl
|
|
11
|
+
import { githubBlobUrl } from '../config/urls.js';
|
|
12
12
|
import { copyWithFallback, listWithFallback, readWithFallback } from '../utils/fallback.js';
|
|
13
13
|
import { copyDirectory, pathExists } from '../utils/files.js';
|
|
14
|
+
import { getRandomMessage } from '../utils/messages.js';
|
|
14
15
|
|
|
15
16
|
const LIVE_STACKS = {
|
|
16
17
|
'next15-prisma-clerk': 'Next.js 15 + Prisma + Clerk',
|
|
@@ -21,7 +22,7 @@ const LIVE_STACKS = {
|
|
|
21
22
|
export function registerInitCommand(program) {
|
|
22
23
|
program
|
|
23
24
|
.command('init')
|
|
24
|
-
.description('Initialize a new Ultra-Dex
|
|
25
|
+
.description('Initialize a new Ultra-Dex Project')
|
|
25
26
|
.option('-n, --name <name>', 'Project name')
|
|
26
27
|
.option('-d, --dir <directory>', 'Output directory', '.')
|
|
27
28
|
.option('--preview', 'Preview files without creating them')
|
|
@@ -29,16 +30,17 @@ export function registerInitCommand(program) {
|
|
|
29
30
|
.option('--stack <preset>', 'Preset: next15-prisma-clerk, remix-supabase, sveltekit-drizzle')
|
|
30
31
|
.action(async (options) => {
|
|
31
32
|
console.log(chalk.cyan(program.banner));
|
|
32
|
-
console.log(chalk.bold('\
|
|
33
|
+
console.log(chalk.hex('#8b5cf6').bold('\nā” INITIALIZING PROJECT PROTOCOL...\n'));
|
|
34
|
+
console.log(chalk.italic(chalk.gray(`"${getRandomMessage('start')}"`)));
|
|
35
|
+
console.log('');
|
|
33
36
|
|
|
34
37
|
if (options.preview) {
|
|
35
|
-
console.log('\nš Files that would be created:\n');
|
|
38
|
+
console.log('\nš Project Preview (Files that would be created):\n');
|
|
36
39
|
console.log(' QUICK-START.md');
|
|
37
40
|
console.log(' CONTEXT.md');
|
|
38
41
|
console.log(' IMPLEMENTATION-PLAN.md');
|
|
39
42
|
console.log(' docs/CHECKLIST.md');
|
|
40
43
|
console.log(' docs/AI-PROMPTS.md');
|
|
41
|
-
console.log('\nRun without --preview to create files.');
|
|
42
44
|
return;
|
|
43
45
|
}
|
|
44
46
|
|
|
@@ -60,7 +62,7 @@ export function registerInitCommand(program) {
|
|
|
60
62
|
if (await pathExists(outputDir, 'dir')) {
|
|
61
63
|
const existing = await fs.readdir(outputDir);
|
|
62
64
|
if (existing.length > 0) {
|
|
63
|
-
console.log(chalk.red('Target directory is not empty.'));
|
|
65
|
+
console.log(chalk.red('Target directory is not empty. Execution halted to prevent data loss.'));
|
|
64
66
|
process.exit(1);
|
|
65
67
|
}
|
|
66
68
|
}
|
|
@@ -77,14 +79,14 @@ export function registerInitCommand(program) {
|
|
|
77
79
|
const spinner = ora(`Generating ${LIVE_STACKS[preset]} scaffold...`).start();
|
|
78
80
|
try {
|
|
79
81
|
await copyDirectory(sourcePath, outputDir);
|
|
80
|
-
spinner.succeed(chalk.green('
|
|
82
|
+
spinner.succeed(chalk.green('Project scaffold generated successfully!'));
|
|
81
83
|
console.log(chalk.gray(`\nPreset: ${preset}`));
|
|
82
84
|
console.log(chalk.gray(`Next steps:`));
|
|
83
85
|
console.log(chalk.cyan(` 1. cd ${outputDir}`));
|
|
84
86
|
console.log(chalk.cyan(' 2. npm install'));
|
|
85
87
|
console.log(chalk.cyan(' 3. npm run dev\n'));
|
|
86
88
|
} catch (error) {
|
|
87
|
-
spinner.fail(chalk.red('Failed to
|
|
89
|
+
spinner.fail(chalk.red('Failed to generate project scaffold'));
|
|
88
90
|
console.error(`[init] ${error?.message ?? error}`);
|
|
89
91
|
process.exit(1);
|
|
90
92
|
}
|
|
@@ -95,103 +97,103 @@ export function registerInitCommand(program) {
|
|
|
95
97
|
{
|
|
96
98
|
type: 'input',
|
|
97
99
|
name: 'projectName',
|
|
98
|
-
message: 'What
|
|
100
|
+
message: 'What is the name of this project?',
|
|
99
101
|
default: options.name || 'my-saas',
|
|
100
102
|
validate: validateProjectName,
|
|
101
103
|
},
|
|
102
104
|
{
|
|
103
105
|
type: 'input',
|
|
104
106
|
name: 'ideaWhat',
|
|
105
|
-
message: 'What are you building?
|
|
106
|
-
validate: (input) => input.length > 0 || 'Please describe your
|
|
107
|
+
message: 'Define the core purpose (What are you building?):',
|
|
108
|
+
validate: (input) => input.length > 0 || 'Please describe your project',
|
|
107
109
|
},
|
|
108
110
|
{
|
|
109
111
|
type: 'input',
|
|
110
112
|
name: 'ideaFor',
|
|
111
|
-
message: 'Who
|
|
112
|
-
validate: (input) => input.length > 0 || 'Please specify your target
|
|
113
|
+
message: 'Target audience (Who are the users)?',
|
|
114
|
+
validate: (input) => input.length > 0 || 'Please specify your target audience',
|
|
113
115
|
},
|
|
114
116
|
{
|
|
115
117
|
type: 'input',
|
|
116
118
|
name: 'problem1',
|
|
117
|
-
message: '
|
|
119
|
+
message: 'Primary problem to solve:',
|
|
118
120
|
default: '',
|
|
119
121
|
},
|
|
120
122
|
{
|
|
121
123
|
type: 'input',
|
|
122
124
|
name: 'problem2',
|
|
123
|
-
message: '
|
|
125
|
+
message: 'Secondary problem to solve:',
|
|
124
126
|
default: '',
|
|
125
127
|
},
|
|
126
128
|
{
|
|
127
129
|
type: 'input',
|
|
128
130
|
name: 'problem3',
|
|
129
|
-
message: '
|
|
131
|
+
message: 'Tertiary problem to solve:',
|
|
130
132
|
default: '',
|
|
131
133
|
},
|
|
132
134
|
{
|
|
133
135
|
type: 'input',
|
|
134
136
|
name: 'feature1',
|
|
135
|
-
message: '
|
|
137
|
+
message: 'Core feature:',
|
|
136
138
|
default: '',
|
|
137
139
|
},
|
|
138
140
|
{
|
|
139
141
|
type: 'list',
|
|
140
142
|
name: 'frontend',
|
|
141
|
-
message: 'Frontend
|
|
143
|
+
message: 'Frontend Technology Stack:',
|
|
142
144
|
choices: ['Next.js', 'Remix', 'SvelteKit', 'Nuxt', 'Other'],
|
|
143
145
|
},
|
|
144
146
|
{
|
|
145
147
|
type: 'list',
|
|
146
148
|
name: 'database',
|
|
147
|
-
message: 'Database:',
|
|
149
|
+
message: 'Database Infrastructure:',
|
|
148
150
|
choices: ['PostgreSQL', 'Supabase', 'MongoDB', 'PlanetScale', 'Other'],
|
|
149
151
|
},
|
|
150
152
|
{
|
|
151
153
|
type: 'list',
|
|
152
154
|
name: 'auth',
|
|
153
|
-
message: 'Authentication:',
|
|
155
|
+
message: 'Authentication Provider:',
|
|
154
156
|
choices: ['NextAuth', 'Clerk', 'Auth0', 'Supabase Auth', 'Other'],
|
|
155
157
|
},
|
|
156
158
|
{
|
|
157
159
|
type: 'list',
|
|
158
160
|
name: 'payments',
|
|
159
|
-
message: '
|
|
161
|
+
message: 'Payment Processor:',
|
|
160
162
|
choices: ['Stripe', 'Lemonsqueezy', 'Paddle', 'None (free)', 'Other'],
|
|
161
163
|
},
|
|
162
164
|
{
|
|
163
165
|
type: 'list',
|
|
164
166
|
name: 'hosting',
|
|
165
|
-
message: '
|
|
167
|
+
message: 'Deployment Platform:',
|
|
166
168
|
choices: ['Vercel', 'Railway', 'Fly.io', 'AWS', 'Other'],
|
|
167
169
|
},
|
|
168
170
|
{
|
|
169
171
|
type: 'confirm',
|
|
170
172
|
name: 'includeCursorRules',
|
|
171
|
-
message: '
|
|
173
|
+
message: 'Install IDE intelligence protocols? (Cursor/Copilot Rules)',
|
|
172
174
|
default: true,
|
|
173
175
|
},
|
|
174
176
|
{
|
|
175
177
|
type: 'confirm',
|
|
176
178
|
name: 'includeFullTemplate',
|
|
177
|
-
message: '
|
|
179
|
+
message: 'Include full 34-section project template?',
|
|
178
180
|
default: false,
|
|
179
181
|
},
|
|
180
182
|
{
|
|
181
183
|
type: 'confirm',
|
|
182
184
|
name: 'includeDocs',
|
|
183
|
-
message: '
|
|
185
|
+
message: 'Include project documentation standards?',
|
|
184
186
|
default: true,
|
|
185
187
|
},
|
|
186
188
|
{
|
|
187
189
|
type: 'confirm',
|
|
188
190
|
name: 'includeAgents',
|
|
189
|
-
message: '
|
|
191
|
+
message: 'Configure AI agent orchestration?',
|
|
190
192
|
default: true,
|
|
191
193
|
},
|
|
192
194
|
]);
|
|
193
195
|
|
|
194
|
-
const spinner = ora('
|
|
196
|
+
const spinner = ora(getRandomMessage('loading')).start();
|
|
195
197
|
|
|
196
198
|
try {
|
|
197
199
|
const outputDir = path.resolve(options.dir, answers.projectName);
|
|
@@ -239,15 +241,8 @@ ${answers.ideaWhat} for ${answers.ideaFor}.
|
|
|
239
241
|
## Next Steps
|
|
240
242
|
|
|
241
243
|
1. Open QUICK-START.md and complete the remaining sections
|
|
242
|
-
2.
|
|
243
|
-
3.
|
|
244
|
-
4. Start building!
|
|
245
|
-
|
|
246
|
-
## Resources
|
|
247
|
-
|
|
248
|
-
- [Full Template](${githubBlobUrl('@%20Ultra%20DeX/Saas%20plan/04-Imp-Template.md')})
|
|
249
|
-
- [TaskFlow Example](${githubBlobUrl('@%20Ultra%20DeX/Saas%20plan/Examples/TaskFlow-Complete.md')})
|
|
250
|
-
- [Methodology](${githubBlobUrl('@%20Ultra%20DeX/Saas%20plan/03-METHODOLOGY.md')})
|
|
244
|
+
2. Customize the implementation plan based on your requirements
|
|
245
|
+
3. Start the agent orchestration to begin development
|
|
251
246
|
`;
|
|
252
247
|
|
|
253
248
|
await fs.writeFile(path.join(outputDir, 'IMPLEMENTATION-PLAN.md'), planContent);
|
|
@@ -274,11 +269,11 @@ ${answers.ideaWhat} for ${answers.ideaFor}.
|
|
|
274
269
|
await fs.mkdir(dotGithub, { recursive: true });
|
|
275
270
|
await fs.writeFile(path.join(dotGithub, 'copilot-instructions.md'), coreContent);
|
|
276
271
|
} catch {
|
|
277
|
-
// Core rule not available
|
|
272
|
+
// Core rule not available
|
|
278
273
|
}
|
|
279
274
|
} catch {
|
|
280
|
-
console.log(chalk.red('\n
|
|
281
|
-
console.log(chalk.cyan('
|
|
275
|
+
console.log(chalk.red('\n ā IDE intelligence protocols not found.'));
|
|
276
|
+
console.log(chalk.cyan(' Run: ultra-dex fetch --rules'));
|
|
282
277
|
}
|
|
283
278
|
}
|
|
284
279
|
|
|
@@ -288,8 +283,8 @@ ${answers.ideaWhat} for ${answers.ideaFor}.
|
|
|
288
283
|
try {
|
|
289
284
|
await copyWithFallback(templatePath, fallbackTemplatePath, path.join(outputDir, 'docs', 'MASTER-PLAN.md'));
|
|
290
285
|
} catch {
|
|
291
|
-
console.log(chalk.red('\n
|
|
292
|
-
console.log(chalk.cyan('
|
|
286
|
+
console.log(chalk.red('\n ā Project template not found.'));
|
|
287
|
+
console.log(chalk.cyan(' Run: ultra-dex fetch --docs'));
|
|
293
288
|
}
|
|
294
289
|
}
|
|
295
290
|
|
|
@@ -302,8 +297,8 @@ ${answers.ideaWhat} for ${answers.ideaFor}.
|
|
|
302
297
|
await copyWithFallback(verificationPath, fallbackVerificationPath, path.join(outputDir, 'docs', 'CHECKLIST.md'));
|
|
303
298
|
await copyWithFallback(agentPath, fallbackAgentPath, path.join(outputDir, 'docs', 'AI-PROMPTS.md'));
|
|
304
299
|
} catch {
|
|
305
|
-
console.log(chalk.red('\n
|
|
306
|
-
console.log(chalk.cyan('
|
|
300
|
+
console.log(chalk.red('\n ā Documentation standards not found.'));
|
|
301
|
+
console.log(chalk.cyan(' Run: ultra-dex fetch --docs'));
|
|
307
302
|
}
|
|
308
303
|
}
|
|
309
304
|
|
|
@@ -345,43 +340,44 @@ ${answers.ideaWhat} for ${answers.ideaFor}.
|
|
|
345
340
|
path.join(agentsDir, 'README.md')
|
|
346
341
|
);
|
|
347
342
|
} catch {
|
|
348
|
-
console.log(chalk.red('\n
|
|
349
|
-
console.log(chalk.cyan('
|
|
343
|
+
console.log(chalk.red('\n ā Agent orchestration assets not found.'));
|
|
344
|
+
console.log(chalk.cyan(' Run: ultra-dex fetch --agents'));
|
|
350
345
|
}
|
|
351
346
|
}
|
|
352
347
|
|
|
353
|
-
spinner.succeed(chalk.green('Project
|
|
348
|
+
spinner.succeed(chalk.green('Project initialized successfully!'));
|
|
354
349
|
|
|
355
|
-
console.log('\n' + chalk.bold('
|
|
350
|
+
console.log('\n' + chalk.bold('Artifacts created:'));
|
|
356
351
|
console.log(chalk.gray(` ${outputDir}/`));
|
|
357
352
|
console.log(chalk.gray(' āāā QUICK-START.md'));
|
|
358
353
|
console.log(chalk.gray(' āāā CONTEXT.md'));
|
|
359
354
|
console.log(chalk.gray(' āāā IMPLEMENTATION-PLAN.md'));
|
|
360
355
|
if (answers.includeFullTemplate) {
|
|
361
|
-
console.log(chalk.gray(' āāā docs/MASTER-PLAN.md
|
|
356
|
+
console.log(chalk.gray(' āāā docs/MASTER-PLAN.md'));
|
|
362
357
|
}
|
|
363
358
|
if (answers.includeDocs) {
|
|
364
359
|
console.log(chalk.gray(' āāā docs/CHECKLIST.md'));
|
|
365
360
|
console.log(chalk.gray(' āāā docs/AI-PROMPTS.md'));
|
|
366
361
|
}
|
|
367
362
|
if (answers.includeCursorRules) {
|
|
368
|
-
console.log(chalk.gray(' āāā .cursor/rules/
|
|
363
|
+
console.log(chalk.gray(' āāā .cursor/rules/'));
|
|
369
364
|
}
|
|
370
365
|
if (answers.includeAgents) {
|
|
371
|
-
console.log(chalk.gray(' āāā .agents/
|
|
366
|
+
console.log(chalk.gray(' āāā .agents/'));
|
|
372
367
|
}
|
|
373
368
|
|
|
374
369
|
console.log('\n' + chalk.bold('Next steps:'));
|
|
375
370
|
console.log(chalk.cyan(` 1. cd ${answers.projectName}`));
|
|
376
|
-
console.log(chalk.cyan(' 2. Open QUICK-START.md
|
|
377
|
-
console.log(chalk.cyan(' 3.
|
|
371
|
+
console.log(chalk.cyan(' 2. Open QUICK-START.md'));
|
|
372
|
+
console.log(chalk.cyan(' 3. ultra-dex swarm "Analyze requirements"'));
|
|
378
373
|
|
|
379
|
-
console.log('\n' + chalk.
|
|
380
|
-
console.log(
|
|
374
|
+
console.log('\n' + chalk.hex('#8b5cf6').bold(' ā SYSTEM READY.'));
|
|
375
|
+
console.log('');
|
|
376
|
+
|
|
381
377
|
} catch (error) {
|
|
382
|
-
spinner.fail(chalk.red('Failed to
|
|
378
|
+
spinner.fail(chalk.red('Failed to initialize project'));
|
|
383
379
|
console.error(`[init] ${error?.message ?? error}`);
|
|
384
380
|
process.exit(1);
|
|
385
381
|
}
|
|
386
382
|
});
|
|
387
|
-
}
|
|
383
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
const TEMPLATES = {
|
|
12
|
+
'next15-prisma-clerk': {
|
|
13
|
+
name: 'Next.js 15 + Prisma + Clerk',
|
|
14
|
+
description: 'Full-stack SaaS with App Router, Prisma ORM, and Clerk auth',
|
|
15
|
+
stack: ['Next.js 15', 'Prisma', 'Clerk', 'PostgreSQL', 'Tailwind CSS'],
|
|
16
|
+
},
|
|
17
|
+
'remix-supabase': {
|
|
18
|
+
name: 'Remix + Supabase',
|
|
19
|
+
description: 'Full-stack app with Remix and Supabase backend',
|
|
20
|
+
stack: ['Remix', 'Supabase', 'PostgreSQL', 'Tailwind CSS'],
|
|
21
|
+
},
|
|
22
|
+
'sveltekit-drizzle': {
|
|
23
|
+
name: 'SvelteKit + Drizzle',
|
|
24
|
+
description: 'SvelteKit app with Drizzle ORM',
|
|
25
|
+
stack: ['SvelteKit', 'Drizzle', 'PostgreSQL', 'Tailwind CSS'],
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
async function copyDirectory(src, dest) {
|
|
30
|
+
await fs.mkdir(dest, { recursive: true });
|
|
31
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
32
|
+
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
const srcPath = path.join(src, entry.name);
|
|
35
|
+
const destPath = path.join(dest, entry.name);
|
|
36
|
+
|
|
37
|
+
if (entry.isDirectory()) {
|
|
38
|
+
await copyDirectory(srcPath, destPath);
|
|
39
|
+
} else {
|
|
40
|
+
await fs.copyFile(srcPath, destPath);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function scaffoldCommand(templateName, options) {
|
|
46
|
+
console.log(chalk.cyan('\nšļø Ultra-Dex Scaffold\n'));
|
|
47
|
+
|
|
48
|
+
// If no template specified, show selection
|
|
49
|
+
if (!templateName) {
|
|
50
|
+
const { selected } = await inquirer.prompt([
|
|
51
|
+
{
|
|
52
|
+
type: 'list',
|
|
53
|
+
name: 'selected',
|
|
54
|
+
message: 'Select a template:',
|
|
55
|
+
choices: Object.entries(TEMPLATES).map(([key, val]) => ({
|
|
56
|
+
name: `${val.name} - ${val.description}`,
|
|
57
|
+
value: key,
|
|
58
|
+
})),
|
|
59
|
+
},
|
|
60
|
+
]);
|
|
61
|
+
templateName = selected;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const template = TEMPLATES[templateName];
|
|
65
|
+
if (!template) {
|
|
66
|
+
console.log(chalk.red(`\nā Template "${templateName}" not found.\n`));
|
|
67
|
+
console.log(chalk.gray('Available templates:'));
|
|
68
|
+
Object.entries(TEMPLATES).forEach(([key, val]) => {
|
|
69
|
+
console.log(chalk.cyan(` - ${key}`) + chalk.gray(` (${val.name})`));
|
|
70
|
+
});
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const outputDir = options.output || templateName;
|
|
75
|
+
const spinner = ora(`Scaffolding ${template.name}...`).start();
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
// Find template directory
|
|
79
|
+
const assetsDir = path.resolve(__dirname, '../../assets/live-templates', templateName);
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
await fs.access(assetsDir);
|
|
83
|
+
} catch {
|
|
84
|
+
spinner.fail('Template files not found in assets');
|
|
85
|
+
console.log(chalk.yellow('\nš” Templates are bundled with the npm package.'));
|
|
86
|
+
console.log(chalk.gray(' Make sure you have the full package installed.\n'));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Copy template
|
|
91
|
+
await copyDirectory(assetsDir, outputDir);
|
|
92
|
+
|
|
93
|
+
spinner.succeed(`Scaffolded ${template.name}`);
|
|
94
|
+
|
|
95
|
+
console.log(chalk.bold('\nš Created files:\n'));
|
|
96
|
+
|
|
97
|
+
async function listFiles(dir, prefix = '') {
|
|
98
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
99
|
+
for (const entry of entries) {
|
|
100
|
+
if (entry.isDirectory()) {
|
|
101
|
+
console.log(chalk.gray(` ${prefix}${entry.name}/`));
|
|
102
|
+
await listFiles(path.join(dir, entry.name), prefix + ' ');
|
|
103
|
+
} else {
|
|
104
|
+
console.log(chalk.green(` ${prefix}${entry.name}`));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
await listFiles(outputDir);
|
|
109
|
+
|
|
110
|
+
console.log(chalk.bold('\nš Next steps:\n'));
|
|
111
|
+
console.log(chalk.cyan(` cd ${outputDir}`));
|
|
112
|
+
console.log(chalk.cyan(' npm install'));
|
|
113
|
+
console.log(chalk.cyan(' cp .env.example .env.local'));
|
|
114
|
+
console.log(chalk.cyan(' npm run dev'));
|
|
115
|
+
|
|
116
|
+
console.log(chalk.bold('\nš Stack:\n'));
|
|
117
|
+
template.stack.forEach(tech => {
|
|
118
|
+
console.log(chalk.gray(` ⢠${tech}`));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
console.log(chalk.bold('\nš” Tips:\n'));
|
|
122
|
+
console.log(chalk.gray(' ⢠Run "ultra-dex init" to add Ultra-Dex planning docs'));
|
|
123
|
+
console.log(chalk.gray(' ⢠Run "ultra-dex generate" to create implementation plan'));
|
|
124
|
+
console.log(chalk.gray(' ⢠Run "ultra-dex agents" to see available AI agents\n'));
|
|
125
|
+
|
|
126
|
+
} catch (error) {
|
|
127
|
+
spinner.fail('Failed to scaffold');
|
|
128
|
+
console.error(chalk.red(error.message));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function registerScaffoldCommand(program) {
|
|
134
|
+
program
|
|
135
|
+
.command('scaffold [template]')
|
|
136
|
+
.description('Generate a production-ready project from a template')
|
|
137
|
+
.option('-o, --output <dir>', 'Output directory')
|
|
138
|
+
.option('--list', 'List available templates')
|
|
139
|
+
.action(async (template, options) => {
|
|
140
|
+
if (options.list) {
|
|
141
|
+
console.log(chalk.cyan('\nš¦ Available Templates\n'));
|
|
142
|
+
Object.entries(TEMPLATES).forEach(([key, val]) => {
|
|
143
|
+
console.log(chalk.bold(` ${key}`));
|
|
144
|
+
console.log(chalk.gray(` ${val.description}`));
|
|
145
|
+
console.log(chalk.gray(` Stack: ${val.stack.join(', ')}\n`));
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
await scaffoldCommand(template, options);
|
|
150
|
+
});
|
|
151
|
+
}
|