@nitrostack/cli 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/README.md +131 -0
- package/dist/commands/build.d.ts +6 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +185 -0
- package/dist/commands/dev.d.ts +7 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +365 -0
- package/dist/commands/generate-types.d.ts +8 -0
- package/dist/commands/generate-types.d.ts.map +1 -0
- package/dist/commands/generate-types.js +219 -0
- package/dist/commands/generate.d.ts +12 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +375 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +324 -0
- package/dist/commands/install.d.ts +10 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +80 -0
- package/dist/commands/start.d.ts +6 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +70 -0
- package/dist/commands/upgrade.d.ts +10 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +214 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +94 -0
- package/dist/mcp-dev-wrapper.d.ts +15 -0
- package/dist/mcp-dev-wrapper.d.ts.map +1 -0
- package/dist/mcp-dev-wrapper.js +187 -0
- package/dist/ui/branding.d.ts +31 -0
- package/dist/ui/branding.d.ts.map +1 -0
- package/dist/ui/branding.js +136 -0
- package/package.json +69 -0
- package/templates/typescript-oauth/.env.example +27 -0
- package/templates/typescript-oauth/OAUTH_SETUP.md +592 -0
- package/templates/typescript-oauth/README.md +263 -0
- package/templates/typescript-oauth/package.json +29 -0
- package/templates/typescript-oauth/src/app.module.ts +92 -0
- package/templates/typescript-oauth/src/guards/oauth.guard.ts +126 -0
- package/templates/typescript-oauth/src/health/system.health.ts +55 -0
- package/templates/typescript-oauth/src/index.ts +63 -0
- package/templates/typescript-oauth/src/modules/flights/booking.tools.ts +323 -0
- package/templates/typescript-oauth/src/modules/flights/flights.module.ts +14 -0
- package/templates/typescript-oauth/src/modules/flights/flights.prompts.ts +228 -0
- package/templates/typescript-oauth/src/modules/flights/flights.resources.ts +215 -0
- package/templates/typescript-oauth/src/modules/flights/flights.tools.ts +457 -0
- package/templates/typescript-oauth/src/services/duffel.service.ts +285 -0
- package/templates/typescript-oauth/src/widgets/app/airport-search/page.tsx +270 -0
- package/templates/typescript-oauth/src/widgets/app/flight-details/page.tsx +261 -0
- package/templates/typescript-oauth/src/widgets/app/flight-search-results/page.tsx +378 -0
- package/templates/typescript-oauth/src/widgets/app/globals.css +167 -0
- package/templates/typescript-oauth/src/widgets/app/layout.tsx +18 -0
- package/templates/typescript-oauth/src/widgets/app/order-cancellation/page.tsx +207 -0
- package/templates/typescript-oauth/src/widgets/app/order-summary/page.tsx +245 -0
- package/templates/typescript-oauth/src/widgets/app/payment-confirmation/page.tsx +152 -0
- package/templates/typescript-oauth/src/widgets/app/seat-selection/page.tsx +486 -0
- package/templates/typescript-oauth/src/widgets/next-env.d.ts +5 -0
- package/templates/typescript-oauth/src/widgets/next.config.js +45 -0
- package/templates/typescript-oauth/src/widgets/package-lock.json +4493 -0
- package/templates/typescript-oauth/src/widgets/package.json +24 -0
- package/templates/typescript-oauth/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-oauth/src/widgets/widget-manifest.json +395 -0
- package/templates/typescript-oauth/tsconfig.json +23 -0
- package/templates/typescript-pizzaz/README.md +252 -0
- package/templates/typescript-pizzaz/package.json +34 -0
- package/templates/typescript-pizzaz/src/app.module.ts +28 -0
- package/templates/typescript-pizzaz/src/index.ts +30 -0
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.data.ts +106 -0
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.module.ts +11 -0
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.service.ts +60 -0
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.tools.ts +197 -0
- package/templates/typescript-pizzaz/src/widgets/app/layout.tsx +18 -0
- package/templates/typescript-pizzaz/src/widgets/app/pizza-list/page.tsx +272 -0
- package/templates/typescript-pizzaz/src/widgets/app/pizza-map/page.tsx +216 -0
- package/templates/typescript-pizzaz/src/widgets/app/pizza-shop/page.tsx +374 -0
- package/templates/typescript-pizzaz/src/widgets/components/CompactShopCard.tsx +144 -0
- package/templates/typescript-pizzaz/src/widgets/components/PizzaCard.tsx +191 -0
- package/templates/typescript-pizzaz/src/widgets/next.config.js +45 -0
- package/templates/typescript-pizzaz/src/widgets/package.json +30 -0
- package/templates/typescript-pizzaz/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-pizzaz/src/widgets/widget-manifest.json +253 -0
- package/templates/typescript-pizzaz/tsconfig.json +30 -0
- package/templates/typescript-starter/README.md +320 -0
- package/templates/typescript-starter/package.json +25 -0
- package/templates/typescript-starter/src/app.module.ts +34 -0
- package/templates/typescript-starter/src/health/system.health.ts +55 -0
- package/templates/typescript-starter/src/index.ts +29 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.module.ts +12 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.prompts.ts +73 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.resources.ts +59 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.tools.ts +166 -0
- package/templates/typescript-starter/src/widgets/app/calculator-result/page.tsx +180 -0
- package/templates/typescript-starter/src/widgets/app/layout.tsx +18 -0
- package/templates/typescript-starter/src/widgets/next.config.js +45 -0
- package/templates/typescript-starter/src/widgets/package.json +24 -0
- package/templates/typescript-starter/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-starter/src/widgets/widget-manifest.json +48 -0
- package/templates/typescript-starter/tsconfig.json +23 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { spawn, execSync } from 'child_process';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { dirname } from 'path';
|
|
7
|
+
import chokidar from 'chokidar';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
// ES module equivalent of __dirname
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
13
|
+
// BRANDING & UI COMPONENTS
|
|
14
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
15
|
+
const NITRO_BANNER = `
|
|
16
|
+
${chalk.hex('#FF6B35').bold('╔══════════════════════════════════════════════════════════════════╗')}
|
|
17
|
+
${chalk.hex('#FF6B35').bold('║')} ${chalk.hex('#FF6B35').bold('║')}
|
|
18
|
+
${chalk.hex('#FF6B35').bold('║')} ${chalk.hex('#FF6B35').bold('███╗ ██╗██╗████████╗██████╗ ██████╗ ')} ${chalk.hex('#FF6B35').bold('║')}
|
|
19
|
+
${chalk.hex('#FF6B35').bold('║')} ${chalk.hex('#FF8C42').bold('████╗ ██║██║╚══██╔══╝██╔══██╗██╔═══██╗')} ${chalk.hex('#FF6B35').bold('║')}
|
|
20
|
+
${chalk.hex('#FF6B35').bold('║')} ${chalk.hex('#FFA94D').bold('██╔██╗ ██║██║ ██║ ██████╔╝██║ ██║')} ${chalk.hex('#FF6B35').bold('║')}
|
|
21
|
+
${chalk.hex('#FF6B35').bold('║')} ${chalk.hex('#FFCC80').bold('██║╚██╗██║██║ ██║ ██╔══██╗██║ ██║')} ${chalk.hex('#FF6B35').bold('║')}
|
|
22
|
+
${chalk.hex('#FF6B35').bold('║')} ${chalk.hex('#FFE0B2').bold('██║ ╚████║██║ ██║ ██║ ██║╚██████╔╝')} ${chalk.hex('#FF6B35').bold('║')}
|
|
23
|
+
${chalk.hex('#FF6B35').bold('║')} ${chalk.dim('╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ')} ${chalk.hex('#FF6B35').bold('║')}
|
|
24
|
+
${chalk.hex('#FF6B35').bold('║')} ${chalk.hex('#FF6B35').bold('║')}
|
|
25
|
+
${chalk.hex('#FF6B35').bold('║')} ${chalk.white.bold('S T A C K')} ${chalk.dim('─────────────────────────────────────────')} ${chalk.hex('#FF6B35').bold('║')}
|
|
26
|
+
${chalk.hex('#FF6B35').bold('║')} ${chalk.hex('#FF6B35').bold('║')}
|
|
27
|
+
${chalk.hex('#FF6B35').bold('║')} ${chalk.dim('MCP Development Framework')} ${chalk.hex('#FF6B35').bold('║')}
|
|
28
|
+
${chalk.hex('#FF6B35').bold('║')} ${chalk.hex('#FF6B35').bold('║')}
|
|
29
|
+
${chalk.hex('#FF6B35').bold('╚══════════════════════════════════════════════════════════════════╝')}
|
|
30
|
+
`;
|
|
31
|
+
class DevUI {
|
|
32
|
+
stages = [];
|
|
33
|
+
currentSpinner = null;
|
|
34
|
+
startTime = Date.now();
|
|
35
|
+
constructor() {
|
|
36
|
+
this.stages = [
|
|
37
|
+
{ name: 'Project', status: 'pending' },
|
|
38
|
+
{ name: 'TypeScript', status: 'pending' },
|
|
39
|
+
{ name: 'Widgets', status: 'pending' },
|
|
40
|
+
{ name: 'MCP Server', status: 'pending' },
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
showBanner() {
|
|
44
|
+
console.clear();
|
|
45
|
+
console.log(NITRO_BANNER);
|
|
46
|
+
}
|
|
47
|
+
updateStage(name, status, message) {
|
|
48
|
+
const stage = this.stages.find(s => s.name === name);
|
|
49
|
+
if (stage) {
|
|
50
|
+
stage.status = status;
|
|
51
|
+
stage.message = message;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
startSpinner(text) {
|
|
55
|
+
if (this.currentSpinner) {
|
|
56
|
+
this.currentSpinner.stop();
|
|
57
|
+
}
|
|
58
|
+
this.currentSpinner = ora({
|
|
59
|
+
text,
|
|
60
|
+
color: 'yellow',
|
|
61
|
+
spinner: 'dots12'
|
|
62
|
+
}).start();
|
|
63
|
+
return this.currentSpinner;
|
|
64
|
+
}
|
|
65
|
+
stopSpinner(success = true, text) {
|
|
66
|
+
if (this.currentSpinner) {
|
|
67
|
+
if (success) {
|
|
68
|
+
this.currentSpinner.succeed(text);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
this.currentSpinner.fail(text);
|
|
72
|
+
}
|
|
73
|
+
this.currentSpinner = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
showReadyScreen(config) {
|
|
77
|
+
const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);
|
|
78
|
+
console.log('\n');
|
|
79
|
+
console.log(chalk.hex('#FF6B35').bold('┌──────────────────────────────────────────────────────────────────┐'));
|
|
80
|
+
console.log(chalk.hex('#FF6B35').bold('│') + chalk.green.bold(' ✓ Development Server Ready ') + chalk.hex('#FF6B35').bold('│'));
|
|
81
|
+
console.log(chalk.hex('#FF6B35').bold('├──────────────────────────────────────────────────────────────────┤'));
|
|
82
|
+
console.log(chalk.hex('#FF6B35').bold('│') + ' ' + chalk.hex('#FF6B35').bold('│'));
|
|
83
|
+
console.log(chalk.hex('#FF6B35').bold('│') + ` ${chalk.white.bold('MCP Server')} ${chalk.dim('Running (STDIO transport)')} ` + chalk.hex('#FF6B35').bold('│'));
|
|
84
|
+
if (config.hasWidgets) {
|
|
85
|
+
console.log(chalk.hex('#FF6B35').bold('│') + ` ${chalk.white.bold('Widgets')} ${chalk.cyan(`http://localhost:${config.widgetsPort}`)} ` + chalk.hex('#FF6B35').bold('│'));
|
|
86
|
+
}
|
|
87
|
+
console.log(chalk.hex('#FF6B35').bold('│') + ' ' + chalk.hex('#FF6B35').bold('│'));
|
|
88
|
+
console.log(chalk.hex('#FF6B35').bold('├──────────────────────────────────────────────────────────────────┤'));
|
|
89
|
+
console.log(chalk.hex('#FF6B35').bold('│') + ' ' + chalk.hex('#FF6B35').bold('│'));
|
|
90
|
+
console.log(chalk.hex('#FF6B35').bold('│') + ` ${chalk.yellow.bold('To use NitroStack Studio:')} ` + chalk.hex('#FF6B35').bold('│'));
|
|
91
|
+
console.log(chalk.hex('#FF6B35').bold('│') + ` ${chalk.dim('1. Start NitroStack Studio separately')} ` + chalk.hex('#FF6B35').bold('│'));
|
|
92
|
+
console.log(chalk.hex('#FF6B35').bold('│') + ` ${chalk.dim('2. Enter project path:')} ${chalk.cyan(config.projectPath.substring(0, 30))}${config.projectPath.length > 30 ? '...' : ' '}` + chalk.hex('#FF6B35').bold('│'));
|
|
93
|
+
console.log(chalk.hex('#FF6B35').bold('│') + ' ' + chalk.hex('#FF6B35').bold('│'));
|
|
94
|
+
console.log(chalk.hex('#FF6B35').bold('├──────────────────────────────────────────────────────────────────┤'));
|
|
95
|
+
console.log(chalk.hex('#FF6B35').bold('│') + ` ${chalk.dim(`Started in ${elapsed}s`)} ` + chalk.hex('#FF6B35').bold('│'));
|
|
96
|
+
console.log(chalk.hex('#FF6B35').bold('│') + ` ${chalk.dim('Press')} ${chalk.white('Ctrl+C')} ${chalk.dim('to stop')} ` + chalk.hex('#FF6B35').bold('│'));
|
|
97
|
+
console.log(chalk.hex('#FF6B35').bold('│') + ' ' + chalk.hex('#FF6B35').bold('│'));
|
|
98
|
+
console.log(chalk.hex('#FF6B35').bold('└──────────────────────────────────────────────────────────────────┘'));
|
|
99
|
+
console.log('\n');
|
|
100
|
+
}
|
|
101
|
+
showShutdown() {
|
|
102
|
+
console.log('\n');
|
|
103
|
+
console.log(chalk.hex('#FF6B35').bold('┌──────────────────────────────────────────────────────────────────┐'));
|
|
104
|
+
console.log(chalk.hex('#FF6B35').bold('│') + chalk.yellow.bold(' Shutting down... ') + chalk.hex('#FF6B35').bold('│'));
|
|
105
|
+
console.log(chalk.hex('#FF6B35').bold('└──────────────────────────────────────────────────────────────────┘'));
|
|
106
|
+
}
|
|
107
|
+
showError(message) {
|
|
108
|
+
console.log('\n');
|
|
109
|
+
console.log(chalk.red.bold('┌──────────────────────────────────────────────────────────────────┐'));
|
|
110
|
+
console.log(chalk.red.bold('│') + chalk.red.bold(' ✗ Error ') + chalk.red.bold('│'));
|
|
111
|
+
console.log(chalk.red.bold('├──────────────────────────────────────────────────────────────────┤'));
|
|
112
|
+
console.log(chalk.red.bold('│') + ` ${chalk.white(message.substring(0, 60).padEnd(60))} ` + chalk.red.bold('│'));
|
|
113
|
+
console.log(chalk.red.bold('└──────────────────────────────────────────────────────────────────┘'));
|
|
114
|
+
}
|
|
115
|
+
log(message, type = 'info') {
|
|
116
|
+
const prefix = {
|
|
117
|
+
info: chalk.blue('ℹ'),
|
|
118
|
+
success: chalk.green('✓'),
|
|
119
|
+
warn: chalk.yellow('⚠'),
|
|
120
|
+
error: chalk.red('✗'),
|
|
121
|
+
}[type];
|
|
122
|
+
console.log(`${prefix} ${message}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
126
|
+
// UTILITY FUNCTIONS
|
|
127
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
128
|
+
function isNitrostackFromNpm() {
|
|
129
|
+
try {
|
|
130
|
+
const result = execSync('npm list -g nitrostack --json', {
|
|
131
|
+
encoding: 'utf-8',
|
|
132
|
+
stdio: 'pipe'
|
|
133
|
+
});
|
|
134
|
+
const parsed = JSON.parse(result);
|
|
135
|
+
if (parsed.dependencies && parsed.dependencies.nitrostack) {
|
|
136
|
+
const nitrostackInfo = parsed.dependencies.nitrostack;
|
|
137
|
+
return nitrostackInfo.version && !nitrostackInfo.resolved?.includes('file:');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
function isLocalDevelopment() {
|
|
146
|
+
return __dirname.includes('/src/cli/') || __dirname.includes('\\src\\cli\\');
|
|
147
|
+
}
|
|
148
|
+
function execSilent(command, options = {}) {
|
|
149
|
+
try {
|
|
150
|
+
execSync(command, {
|
|
151
|
+
...options,
|
|
152
|
+
stdio: 'pipe',
|
|
153
|
+
encoding: 'utf-8',
|
|
154
|
+
});
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
export async function devCommand(options) {
|
|
162
|
+
const ui = new DevUI();
|
|
163
|
+
let tscWatchProcess = null;
|
|
164
|
+
let widgetsDevProcess = null;
|
|
165
|
+
let isShuttingDown = false;
|
|
166
|
+
const shutdown = (code = 0) => {
|
|
167
|
+
if (isShuttingDown)
|
|
168
|
+
return;
|
|
169
|
+
isShuttingDown = true;
|
|
170
|
+
ui.showShutdown();
|
|
171
|
+
if (tscWatchProcess) {
|
|
172
|
+
tscWatchProcess.kill();
|
|
173
|
+
tscWatchProcess = null;
|
|
174
|
+
}
|
|
175
|
+
if (widgetsDevProcess) {
|
|
176
|
+
widgetsDevProcess.kill();
|
|
177
|
+
widgetsDevProcess = null;
|
|
178
|
+
}
|
|
179
|
+
setTimeout(() => {
|
|
180
|
+
console.log(chalk.dim('\nGoodbye! 👋\n'));
|
|
181
|
+
process.exit(code);
|
|
182
|
+
}, 500);
|
|
183
|
+
};
|
|
184
|
+
process.on('SIGINT', () => shutdown(0));
|
|
185
|
+
process.on('SIGTERM', () => shutdown(0));
|
|
186
|
+
try {
|
|
187
|
+
// Show banner
|
|
188
|
+
ui.showBanner();
|
|
189
|
+
const projectPath = process.cwd();
|
|
190
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
191
|
+
// STAGE 1: Validate Project
|
|
192
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
193
|
+
let spinner = ui.startSpinner('Validating project...');
|
|
194
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
195
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
196
|
+
ui.stopSpinner(false, 'package.json not found');
|
|
197
|
+
throw new Error('Not a NitroStack project');
|
|
198
|
+
}
|
|
199
|
+
const widgetsPath = path.join(projectPath, 'src', 'widgets');
|
|
200
|
+
const widgetsPackageJsonPath = path.join(widgetsPath, 'package.json');
|
|
201
|
+
const hasWidgets = fs.existsSync(widgetsPackageJsonPath);
|
|
202
|
+
ui.stopSpinner(true, `Project validated ${hasWidgets ? chalk.dim('(with widgets)') : ''}`);
|
|
203
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
204
|
+
// STAGE 2: Setup Widgets (if present)
|
|
205
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
206
|
+
if (hasWidgets) {
|
|
207
|
+
spinner = ui.startSpinner('Checking widget dependencies...');
|
|
208
|
+
const fromNpm = isNitrostackFromNpm();
|
|
209
|
+
const isLocalDev = isLocalDevelopment();
|
|
210
|
+
const widgetsNodeModulesPath = path.join(widgetsPath, 'node_modules');
|
|
211
|
+
const widgetsNitrostackPath = path.join(widgetsNodeModulesPath, '@nitrostack');
|
|
212
|
+
if (!fs.existsSync(widgetsNodeModulesPath) || !fs.existsSync(widgetsNitrostackPath)) {
|
|
213
|
+
// Link local packages if in dev mode
|
|
214
|
+
if (isLocalDev && !fromNpm) {
|
|
215
|
+
spinner.text = 'Linking local @nitrostack/widgets...';
|
|
216
|
+
execSilent('npm link @nitrostack/widgets', { cwd: widgetsPath });
|
|
217
|
+
}
|
|
218
|
+
spinner.text = 'Installing widget dependencies...';
|
|
219
|
+
const installSuccess = execSilent('npm install', { cwd: widgetsPath });
|
|
220
|
+
if (!installSuccess) {
|
|
221
|
+
ui.stopSpinner(false, 'Failed to install widget dependencies');
|
|
222
|
+
throw new Error('Widget dependency installation failed');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
ui.stopSpinner(true, 'Widget dependencies ready');
|
|
226
|
+
// Check if widgets need initial build
|
|
227
|
+
const widgetsOutPath = path.join(widgetsPath, '.next');
|
|
228
|
+
if (!fs.existsSync(widgetsOutPath)) {
|
|
229
|
+
spinner = ui.startSpinner('Building widgets for first time...');
|
|
230
|
+
const buildSuccess = execSilent('npm run build', {
|
|
231
|
+
cwd: widgetsPath,
|
|
232
|
+
env: { ...process.env, NODE_ENV: 'production' }
|
|
233
|
+
});
|
|
234
|
+
if (buildSuccess) {
|
|
235
|
+
ui.stopSpinner(true, 'Widgets built');
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
ui.stopSpinner(false, 'Widget build failed (will try dev server)');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
243
|
+
// STAGE 3: Build TypeScript
|
|
244
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
245
|
+
const distIndexPath = path.join(projectPath, 'dist', 'index.js');
|
|
246
|
+
if (!fs.existsSync(distIndexPath)) {
|
|
247
|
+
spinner = ui.startSpinner('Building MCP server...');
|
|
248
|
+
const buildSuccess = execSilent('npx tsc', { cwd: projectPath });
|
|
249
|
+
if (!buildSuccess) {
|
|
250
|
+
ui.stopSpinner(false, 'TypeScript build failed');
|
|
251
|
+
throw new Error('Build failed. Please fix errors and try again.');
|
|
252
|
+
}
|
|
253
|
+
ui.stopSpinner(true, 'MCP server built');
|
|
254
|
+
}
|
|
255
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
256
|
+
// STAGE 4: Start TypeScript Watch Mode
|
|
257
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
258
|
+
spinner = ui.startSpinner('Starting TypeScript compiler...');
|
|
259
|
+
tscWatchProcess = spawn('npx', ['tsc', '--watch', '--preserveWatchOutput'], {
|
|
260
|
+
cwd: projectPath,
|
|
261
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
262
|
+
shell: true,
|
|
263
|
+
});
|
|
264
|
+
// Only log compilation status, suppress verbose output
|
|
265
|
+
let tscReady = false;
|
|
266
|
+
if (tscWatchProcess.stdout) {
|
|
267
|
+
tscWatchProcess.stdout.on('data', (data) => {
|
|
268
|
+
const output = data.toString();
|
|
269
|
+
if (output.includes('Found 0 errors') || output.includes('Watching for file changes')) {
|
|
270
|
+
if (!tscReady) {
|
|
271
|
+
tscReady = true;
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
console.log(chalk.green('✓') + chalk.dim(' MCP server recompiled'));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
else if (output.includes('error TS')) {
|
|
278
|
+
console.log(chalk.red('✗') + chalk.dim(' TypeScript error - check your code'));
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
ui.stopSpinner(true, 'TypeScript watch mode started');
|
|
283
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
284
|
+
// STAGE 5: Start Widget Dev Server (if present)
|
|
285
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
286
|
+
const widgetsPort = 3001;
|
|
287
|
+
if (hasWidgets) {
|
|
288
|
+
spinner = ui.startSpinner('Starting widget dev server...');
|
|
289
|
+
widgetsDevProcess = spawn('npm', ['run', 'dev', '--', '--port', String(widgetsPort)], {
|
|
290
|
+
cwd: widgetsPath,
|
|
291
|
+
env: { ...process.env, NODE_ENV: 'development' },
|
|
292
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
293
|
+
shell: true,
|
|
294
|
+
});
|
|
295
|
+
// Wait for widgets to be ready
|
|
296
|
+
await new Promise((resolve) => {
|
|
297
|
+
const timeout = setTimeout(resolve, 8000);
|
|
298
|
+
if (widgetsDevProcess?.stdout) {
|
|
299
|
+
widgetsDevProcess.stdout.on('data', (data) => {
|
|
300
|
+
const output = data.toString();
|
|
301
|
+
if (output.includes('Ready') || output.includes('started') || output.includes(String(widgetsPort))) {
|
|
302
|
+
clearTimeout(timeout);
|
|
303
|
+
resolve();
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
ui.stopSpinner(true, `Widget dev server running on :${widgetsPort}`);
|
|
309
|
+
// Set up watcher for new widget pages
|
|
310
|
+
const widgetPagesPath = path.join(widgetsPath, 'app');
|
|
311
|
+
const widgetNextCachePath = path.join(widgetsPath, '.next');
|
|
312
|
+
const watcher = chokidar.watch(widgetPagesPath, {
|
|
313
|
+
ignored: /(^|[\/\\])\../,
|
|
314
|
+
persistent: true,
|
|
315
|
+
ignoreInitial: true,
|
|
316
|
+
awaitWriteFinish: { stabilityThreshold: 1000, pollInterval: 100 }
|
|
317
|
+
});
|
|
318
|
+
let lastRestart = 0;
|
|
319
|
+
const RESTART_COOLDOWN = 3000;
|
|
320
|
+
watcher.on('add', (filepath) => {
|
|
321
|
+
if (filepath.endsWith('page.tsx') || filepath.endsWith('page.jsx')) {
|
|
322
|
+
const now = Date.now();
|
|
323
|
+
if (now - lastRestart < RESTART_COOLDOWN)
|
|
324
|
+
return;
|
|
325
|
+
console.log(chalk.yellow('⚡') + chalk.dim(' New widget detected, restarting...'));
|
|
326
|
+
lastRestart = Date.now();
|
|
327
|
+
if (widgetsDevProcess) {
|
|
328
|
+
widgetsDevProcess.kill();
|
|
329
|
+
}
|
|
330
|
+
if (fs.existsSync(widgetNextCachePath)) {
|
|
331
|
+
fs.rmSync(widgetNextCachePath, { recursive: true, force: true });
|
|
332
|
+
}
|
|
333
|
+
widgetsDevProcess = spawn('npm', ['run', 'dev', '--', '--port', String(widgetsPort)], {
|
|
334
|
+
cwd: widgetsPath,
|
|
335
|
+
env: { ...process.env, NODE_ENV: 'development' },
|
|
336
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
337
|
+
shell: true,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
343
|
+
// READY!
|
|
344
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
345
|
+
ui.showReadyScreen({
|
|
346
|
+
widgetsPort,
|
|
347
|
+
hasWidgets,
|
|
348
|
+
mcpServerPath: distIndexPath,
|
|
349
|
+
projectPath,
|
|
350
|
+
});
|
|
351
|
+
// Set up log forwarding for errors only
|
|
352
|
+
widgetsDevProcess?.stderr?.on('data', (data) => {
|
|
353
|
+
const output = data.toString();
|
|
354
|
+
if (output.includes('error')) {
|
|
355
|
+
console.log(chalk.red('✗') + chalk.dim(' Widgets: ') + output.trim());
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
// Keep process alive
|
|
359
|
+
await new Promise(() => { });
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
ui.showError(error instanceof Error ? error.message : String(error));
|
|
363
|
+
shutdown(1);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates TypeScript types from tool definitions
|
|
3
|
+
* Scans all *.tools.ts files and extracts types from Zod schemas and examples
|
|
4
|
+
*/
|
|
5
|
+
export declare function generateTypes(options?: {
|
|
6
|
+
output?: string;
|
|
7
|
+
}): Promise<void>;
|
|
8
|
+
//# sourceMappingURL=generate-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-types.d.ts","sourceRoot":"","sources":["../../src/commands/generate-types.ts"],"names":[],"mappings":"AAQA;;;GAGG;AACH,wBAAsB,aAAa,CAAC,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;CAAO,iBA4BrE"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname } from 'path';
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
/**
|
|
8
|
+
* Generates TypeScript types from tool definitions
|
|
9
|
+
* Scans all *.tools.ts files and extracts types from Zod schemas and examples
|
|
10
|
+
*/
|
|
11
|
+
export async function generateTypes(options = {}) {
|
|
12
|
+
const projectRoot = process.cwd();
|
|
13
|
+
const outputPath = options.output || path.join(projectRoot, 'src', 'types', 'generated-tools.ts');
|
|
14
|
+
console.log('🔍 Scanning for tool definitions...');
|
|
15
|
+
// Find all *.tools.ts files
|
|
16
|
+
const toolFiles = await findToolFiles(path.join(projectRoot, 'src'));
|
|
17
|
+
if (toolFiles.length === 0) {
|
|
18
|
+
console.log('⚠️ No tool files found');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
console.log(`📝 Found ${toolFiles.length} tool file(s)`);
|
|
22
|
+
// Parse tools and generate types
|
|
23
|
+
const types = await generateTypesFromFiles(toolFiles);
|
|
24
|
+
// Write output file
|
|
25
|
+
const outputDir = path.dirname(outputPath);
|
|
26
|
+
if (!fs.existsSync(outputDir)) {
|
|
27
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
fs.writeFileSync(outputPath, types);
|
|
30
|
+
console.log(`✅ Generated types at: ${outputPath}`);
|
|
31
|
+
}
|
|
32
|
+
async function findToolFiles(dir) {
|
|
33
|
+
const files = [];
|
|
34
|
+
if (!fs.existsSync(dir)) {
|
|
35
|
+
return files;
|
|
36
|
+
}
|
|
37
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
const fullPath = path.join(dir, entry.name);
|
|
40
|
+
if (entry.isDirectory() && !entry.name.includes('node_modules') && !entry.name.includes('dist')) {
|
|
41
|
+
files.push(...await findToolFiles(fullPath));
|
|
42
|
+
}
|
|
43
|
+
else if (entry.isFile() && entry.name.endsWith('.tools.ts')) {
|
|
44
|
+
files.push(fullPath);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return files;
|
|
48
|
+
}
|
|
49
|
+
async function generateTypesFromFiles(files) {
|
|
50
|
+
let output = `/**
|
|
51
|
+
* Auto-generated types from tool definitions
|
|
52
|
+
* DO NOT EDIT THIS FILE MANUALLY
|
|
53
|
+
*
|
|
54
|
+
* Generated by: nitrostack generate-types
|
|
55
|
+
* Generated at: ${new Date().toISOString()}
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
// ==========================================
|
|
59
|
+
// Common Types
|
|
60
|
+
// ==========================================
|
|
61
|
+
|
|
62
|
+
export interface ToolInput<T = unknown> {
|
|
63
|
+
[key: string]: T;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ToolOutput<T = unknown> {
|
|
67
|
+
[key: string]: T;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ==========================================
|
|
71
|
+
// Tool-specific Types
|
|
72
|
+
// ==========================================
|
|
73
|
+
|
|
74
|
+
`;
|
|
75
|
+
const toolTypes = new Map();
|
|
76
|
+
for (const file of files) {
|
|
77
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
78
|
+
// Extract @Tool decorators
|
|
79
|
+
const toolRegex = /@Tool\(\{[\s\S]*?name:\s*['"]([^'"]+)['"]\s*,[\s\S]*?\}\)/g;
|
|
80
|
+
let match;
|
|
81
|
+
while ((match = toolRegex.exec(content)) !== null) {
|
|
82
|
+
const toolName = match[1];
|
|
83
|
+
const decoratorContent = match[0];
|
|
84
|
+
// Extract inputSchema (Zod schema)
|
|
85
|
+
const inputSchemaMatch = decoratorContent.match(/inputSchema:\s*z\.object\(\{([\s\S]*?)\}\)/);
|
|
86
|
+
// Extract example response
|
|
87
|
+
const examplesMatch = decoratorContent.match(/examples:\s*\{[\s\S]*?response:\s*(\{[\s\S]*?\})\s*\}/);
|
|
88
|
+
if (inputSchemaMatch || examplesMatch) {
|
|
89
|
+
const pascalCaseName = toPascalCase(toolName);
|
|
90
|
+
// Generate input type from Zod schema
|
|
91
|
+
let inputType = 'Record<string, unknown>';
|
|
92
|
+
if (inputSchemaMatch) {
|
|
93
|
+
inputType = generateTypeFromZodSchema(inputSchemaMatch[1]);
|
|
94
|
+
}
|
|
95
|
+
// Generate output type from example
|
|
96
|
+
let outputType = 'unknown';
|
|
97
|
+
if (examplesMatch) {
|
|
98
|
+
outputType = generateTypeFromExample(examplesMatch[1]);
|
|
99
|
+
}
|
|
100
|
+
toolTypes.set(toolName, { input: inputType, output: outputType });
|
|
101
|
+
output += `// ${toolName}\n`;
|
|
102
|
+
output += `export type ${pascalCaseName}Input = ${inputType};\n`;
|
|
103
|
+
output += `export type ${pascalCaseName}Output = ${outputType};\n\n`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Add a union type of all tool names
|
|
108
|
+
output += `// ==========================================\n`;
|
|
109
|
+
output += `// Tool Name Union\n`;
|
|
110
|
+
output += `// ==========================================\n\n`;
|
|
111
|
+
output += `export type ToolName = ${Array.from(toolTypes.keys()).map(name => `'${name}'`).join(' | ')};\n\n`;
|
|
112
|
+
// Add a mapped type for inputs and outputs
|
|
113
|
+
output += `// ==========================================\n`;
|
|
114
|
+
output += `// Mapped Types\n`;
|
|
115
|
+
output += `// ==========================================\n\n`;
|
|
116
|
+
output += `export interface ToolInputs {\n`;
|
|
117
|
+
for (const [toolName] of toolTypes) {
|
|
118
|
+
const pascalCase = toPascalCase(toolName);
|
|
119
|
+
output += ` '${toolName}': ${pascalCase}Input;\n`;
|
|
120
|
+
}
|
|
121
|
+
output += `}\n\n`;
|
|
122
|
+
output += `export interface ToolOutputs {\n`;
|
|
123
|
+
for (const [toolName] of toolTypes) {
|
|
124
|
+
const pascalCase = toPascalCase(toolName);
|
|
125
|
+
output += ` '${toolName}': ${pascalCase}Output;\n`;
|
|
126
|
+
}
|
|
127
|
+
output += `}\n`;
|
|
128
|
+
return output;
|
|
129
|
+
}
|
|
130
|
+
function toPascalCase(str) {
|
|
131
|
+
return str
|
|
132
|
+
.split(/[_-]/)
|
|
133
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
134
|
+
.join('');
|
|
135
|
+
}
|
|
136
|
+
function generateTypeFromZodSchema(zodContent) {
|
|
137
|
+
// Simple parser for basic Zod schemas
|
|
138
|
+
// This is a simplified version - in production, you might want to use actual Zod parsing
|
|
139
|
+
const fields = [];
|
|
140
|
+
// Match field definitions like: field_name: z.string().describe('...')
|
|
141
|
+
const fieldRegex = /(\w+):\s*z\.(\w+)\(\)(?:\.optional\(\))?(?:\.describe\([^)]*\))?/g;
|
|
142
|
+
let match;
|
|
143
|
+
while ((match = fieldRegex.exec(zodContent)) !== null) {
|
|
144
|
+
const fieldName = match[1];
|
|
145
|
+
const zodType = match[2];
|
|
146
|
+
const isOptional = zodContent.includes(`${fieldName}:`) && zodContent.includes('.optional()');
|
|
147
|
+
let tsType = 'unknown';
|
|
148
|
+
switch (zodType) {
|
|
149
|
+
case 'string':
|
|
150
|
+
tsType = 'string';
|
|
151
|
+
break;
|
|
152
|
+
case 'number':
|
|
153
|
+
tsType = 'number';
|
|
154
|
+
break;
|
|
155
|
+
case 'boolean':
|
|
156
|
+
tsType = 'boolean';
|
|
157
|
+
break;
|
|
158
|
+
case 'enum':
|
|
159
|
+
// Extract enum values if possible
|
|
160
|
+
const enumMatch = zodContent.match(new RegExp(`${fieldName}:\\s*z\\.enum\\(\\[([^\\]]+)\\]\\)`));
|
|
161
|
+
if (enumMatch) {
|
|
162
|
+
tsType = enumMatch[1].replace(/'/g, '"');
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
tsType = 'string';
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
case 'array':
|
|
169
|
+
tsType = 'unknown[]';
|
|
170
|
+
break;
|
|
171
|
+
case 'object':
|
|
172
|
+
tsType = 'Record<string, unknown>';
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
fields.push(`${fieldName}${isOptional ? '?' : ''}: ${tsType}`);
|
|
176
|
+
}
|
|
177
|
+
return fields.length > 0 ? `{\n ${fields.join(';\n ')};\n}` : 'Record<string, unknown>';
|
|
178
|
+
}
|
|
179
|
+
function generateTypeFromExample(exampleJson) {
|
|
180
|
+
try {
|
|
181
|
+
// Parse the example JSON to infer types
|
|
182
|
+
// This is a simplified version
|
|
183
|
+
const cleaned = exampleJson
|
|
184
|
+
.replace(/\/\/.*/g, '') // Remove comments
|
|
185
|
+
.trim();
|
|
186
|
+
const obj = eval('(' + cleaned + ')');
|
|
187
|
+
return generateTypeFromObject(obj);
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
return 'unknown';
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function generateTypeFromObject(obj, indent = ' ') {
|
|
194
|
+
if (obj === null || obj === undefined) {
|
|
195
|
+
return 'unknown';
|
|
196
|
+
}
|
|
197
|
+
if (Array.isArray(obj)) {
|
|
198
|
+
if (obj.length === 0) {
|
|
199
|
+
return 'unknown[]';
|
|
200
|
+
}
|
|
201
|
+
const itemType = generateTypeFromObject(obj[0], indent + ' ');
|
|
202
|
+
return `Array<${itemType}>`;
|
|
203
|
+
}
|
|
204
|
+
if (typeof obj === 'object') {
|
|
205
|
+
const fields = [];
|
|
206
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
207
|
+
const type = generateTypeFromObject(value, indent + ' ');
|
|
208
|
+
fields.push(`${key}: ${type}`);
|
|
209
|
+
}
|
|
210
|
+
return `{\n${indent}${fields.join(`;\n${indent}`)};\n${indent.slice(2)}}`;
|
|
211
|
+
}
|
|
212
|
+
if (typeof obj === 'string')
|
|
213
|
+
return 'string';
|
|
214
|
+
if (typeof obj === 'number')
|
|
215
|
+
return 'number';
|
|
216
|
+
if (typeof obj === 'boolean')
|
|
217
|
+
return 'boolean';
|
|
218
|
+
return 'unknown';
|
|
219
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface GenerateOptions {
|
|
2
|
+
module?: string;
|
|
3
|
+
output?: string;
|
|
4
|
+
force?: boolean;
|
|
5
|
+
skipRelated?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Generate command
|
|
9
|
+
*/
|
|
10
|
+
export declare function generate(type: string, name: string, options?: GenerateOptions): Promise<void>;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=generate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AA8TA,UAAU,eAAe;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6FvG"}
|