@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,375 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { createHeader, createSuccessBox, createErrorBox, log, spacer, nextSteps } from '../ui/branding.js';
|
|
5
|
+
/**
|
|
6
|
+
* Generator templates
|
|
7
|
+
*/
|
|
8
|
+
const TEMPLATES = {
|
|
9
|
+
middleware: (name) => `import { Middleware, MiddlewareInterface, ExecutionContext } from 'nitrostack';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ${name}
|
|
13
|
+
*
|
|
14
|
+
* TODO: Add description
|
|
15
|
+
*/
|
|
16
|
+
@Middleware()
|
|
17
|
+
export class ${name} implements MiddlewareInterface {
|
|
18
|
+
async use(context: ExecutionContext, next: () => Promise<any>) {
|
|
19
|
+
// Before handler
|
|
20
|
+
console.log(\`[\${context.toolName}] Started\`);
|
|
21
|
+
|
|
22
|
+
const result = await next();
|
|
23
|
+
|
|
24
|
+
// After handler
|
|
25
|
+
console.log(\`[\${context.toolName}] Completed\`);
|
|
26
|
+
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
`,
|
|
31
|
+
interceptor: (name) => `import { Interceptor, InterceptorInterface, ExecutionContext } from 'nitrostack';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* ${name}
|
|
35
|
+
*
|
|
36
|
+
* TODO: Add description
|
|
37
|
+
*/
|
|
38
|
+
@Interceptor()
|
|
39
|
+
export class ${name} implements InterceptorInterface {
|
|
40
|
+
async intercept(context: ExecutionContext, next: () => Promise<any>) {
|
|
41
|
+
const result = await next();
|
|
42
|
+
|
|
43
|
+
// Transform response
|
|
44
|
+
return {
|
|
45
|
+
success: true,
|
|
46
|
+
data: result,
|
|
47
|
+
timestamp: new Date().toISOString(),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
`,
|
|
52
|
+
pipe: (name) => `import { Pipe, PipeInterface, ArgumentMetadata } from 'nitrostack';
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* ${name}
|
|
56
|
+
*
|
|
57
|
+
* TODO: Add description
|
|
58
|
+
*/
|
|
59
|
+
@Pipe()
|
|
60
|
+
export class ${name} implements PipeInterface {
|
|
61
|
+
transform(value: unknown, metadata: ArgumentMetadata) {
|
|
62
|
+
// Validate
|
|
63
|
+
if (!value) {
|
|
64
|
+
throw new Error('Value is required');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Transform
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
`,
|
|
72
|
+
filter: (name) => `import { ExceptionFilter, ExceptionFilterInterface, ExecutionContext } from 'nitrostack';
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* ${name}
|
|
76
|
+
*
|
|
77
|
+
* TODO: Add description
|
|
78
|
+
*/
|
|
79
|
+
@ExceptionFilter()
|
|
80
|
+
export class ${name} implements ExceptionFilterInterface {
|
|
81
|
+
catch(exception: unknown, context: ExecutionContext) {
|
|
82
|
+
const errorMessage = exception instanceof Error ? exception.message : String(exception);
|
|
83
|
+
context.logger.error('Exception caught', {
|
|
84
|
+
tool: context.toolName,
|
|
85
|
+
error: errorMessage,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
error: true,
|
|
90
|
+
message: errorMessage,
|
|
91
|
+
timestamp: new Date().toISOString(),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
`,
|
|
96
|
+
service: (name) => `import { Injectable } from 'nitrostack';
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* ${name}
|
|
100
|
+
*
|
|
101
|
+
* TODO: Add description
|
|
102
|
+
*/
|
|
103
|
+
@Injectable()
|
|
104
|
+
export class ${name} {
|
|
105
|
+
// Add your service methods here
|
|
106
|
+
|
|
107
|
+
async findOne(id: string): Promise<Record<string, unknown>> {
|
|
108
|
+
// TODO: Implement
|
|
109
|
+
return { id };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async findAll(): Promise<Record<string, unknown>[]> {
|
|
113
|
+
// TODO: Implement
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async create(data: Record<string, unknown>): Promise<Record<string, unknown>> {
|
|
118
|
+
// TODO: Implement
|
|
119
|
+
return { id: '1', ...data };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async update(id: string, data: Record<string, unknown>): Promise<Record<string, unknown>> {
|
|
123
|
+
// TODO: Implement
|
|
124
|
+
return { id, ...data };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async delete(id: string): Promise<void> {
|
|
128
|
+
// TODO: Implement
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
`,
|
|
132
|
+
guard: (name) => `import { Guard, ExecutionContext } from 'nitrostack';
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* ${name}
|
|
136
|
+
*
|
|
137
|
+
* TODO: Add description
|
|
138
|
+
*/
|
|
139
|
+
export class ${name} implements Guard {
|
|
140
|
+
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
141
|
+
// TODO: Implement your guard logic
|
|
142
|
+
|
|
143
|
+
// Example: Check if user has required role
|
|
144
|
+
const userRole = context.auth?.role;
|
|
145
|
+
|
|
146
|
+
if (!userRole) {
|
|
147
|
+
throw new Error('Authentication required');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Check role
|
|
151
|
+
if (userRole !== 'admin') {
|
|
152
|
+
throw new Error('Insufficient permissions');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
`,
|
|
159
|
+
health: (name) => `import { HealthCheck, HealthCheckInterface, HealthCheckResult } from 'nitrostack';
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* ${name}
|
|
163
|
+
*
|
|
164
|
+
* TODO: Add description
|
|
165
|
+
*/
|
|
166
|
+
@HealthCheck({
|
|
167
|
+
name: '${name.toLowerCase().replace(/healthcheck$/, '').replace(/health$/, '')}',
|
|
168
|
+
description: 'TODO: Add description',
|
|
169
|
+
interval: 30 // Check every 30 seconds
|
|
170
|
+
})
|
|
171
|
+
export class ${name} implements HealthCheckInterface {
|
|
172
|
+
async check(): Promise<HealthCheckResult> {
|
|
173
|
+
try {
|
|
174
|
+
// TODO: Implement health check logic
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
status: 'up',
|
|
178
|
+
message: 'Service is healthy',
|
|
179
|
+
};
|
|
180
|
+
} catch (error: unknown) {
|
|
181
|
+
return {
|
|
182
|
+
status: 'down',
|
|
183
|
+
message: 'Service is down',
|
|
184
|
+
details: error instanceof Error ? error.message : String(error),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
`,
|
|
190
|
+
module: (name) => `import { Module } from 'nitrostack';
|
|
191
|
+
import { ${name}Tools } from './${name.toLowerCase()}.tools.js';
|
|
192
|
+
import { ${name}Resources } from './${name.toLowerCase()}.resources.js';
|
|
193
|
+
import { ${name}Prompts } from './${name.toLowerCase()}.prompts.js';
|
|
194
|
+
|
|
195
|
+
@Module({
|
|
196
|
+
name: '${name.toLowerCase()}',
|
|
197
|
+
description: 'TODO: Add description',
|
|
198
|
+
controllers: [${name}Tools, ${name}Resources, ${name}Prompts],
|
|
199
|
+
})
|
|
200
|
+
export class ${name}Module {}
|
|
201
|
+
`,
|
|
202
|
+
tools: (name) => `import { ToolDecorator as Tool, z, ExecutionContext, Injectable } from 'nitrostack';
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* ${name} Tools
|
|
206
|
+
*
|
|
207
|
+
* TODO: Add description
|
|
208
|
+
*/
|
|
209
|
+
@Injectable()
|
|
210
|
+
export class ${name}Tools {
|
|
211
|
+
@Tool({
|
|
212
|
+
name: '${name.toLowerCase()}_example',
|
|
213
|
+
description: 'TODO: Add description',
|
|
214
|
+
inputSchema: z.object({
|
|
215
|
+
id: z.string().describe('ID parameter'),
|
|
216
|
+
}),
|
|
217
|
+
})
|
|
218
|
+
async exampleTool(input: { id: string }, context: ExecutionContext) {
|
|
219
|
+
// TODO: Implement tool logic
|
|
220
|
+
return { id: input.id, result: 'success' };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
`,
|
|
224
|
+
resources: (name) => `import { ResourceDecorator as Resource, ExecutionContext } from 'nitrostack';
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* ${name} Resources
|
|
228
|
+
*
|
|
229
|
+
* TODO: Add description
|
|
230
|
+
*/
|
|
231
|
+
export class ${name}Resources {
|
|
232
|
+
@Resource({
|
|
233
|
+
uri: '${name.toLowerCase()}://example',
|
|
234
|
+
name: 'Example Resource',
|
|
235
|
+
description: 'TODO: Add description',
|
|
236
|
+
mimeType: 'application/json',
|
|
237
|
+
})
|
|
238
|
+
async exampleResource(context: ExecutionContext) {
|
|
239
|
+
// TODO: Implement resource logic
|
|
240
|
+
return {
|
|
241
|
+
type: 'text' as const,
|
|
242
|
+
text: JSON.stringify({ example: 'data' }, null, 2),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
`,
|
|
247
|
+
prompts: (name) => `import { PromptDecorator as Prompt, ExecutionContext } from 'nitrostack';
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* ${name} Prompts
|
|
251
|
+
*
|
|
252
|
+
* TODO: Add description
|
|
253
|
+
*/
|
|
254
|
+
export class ${name}Prompts {
|
|
255
|
+
@Prompt({
|
|
256
|
+
name: '${name.toLowerCase()}-help',
|
|
257
|
+
description: 'TODO: Add description',
|
|
258
|
+
})
|
|
259
|
+
async helpPrompt(args: Record<string, unknown>, context: ExecutionContext) {
|
|
260
|
+
return [
|
|
261
|
+
{
|
|
262
|
+
role: 'user' as const,
|
|
263
|
+
content: {
|
|
264
|
+
type: 'text' as const,
|
|
265
|
+
text: 'TODO: Add prompt content',
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
];
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
`,
|
|
272
|
+
};
|
|
273
|
+
/**
|
|
274
|
+
* Get file path for generated file
|
|
275
|
+
*/
|
|
276
|
+
function getFilePath(type, name, module) {
|
|
277
|
+
const basePath = process.cwd();
|
|
278
|
+
if (module) {
|
|
279
|
+
return path.join(basePath, 'src', 'modules', module, `${module}.${type}.ts`);
|
|
280
|
+
}
|
|
281
|
+
const dirMap = {
|
|
282
|
+
middleware: 'middleware',
|
|
283
|
+
interceptor: 'interceptors',
|
|
284
|
+
pipe: 'pipes',
|
|
285
|
+
filter: 'filters',
|
|
286
|
+
service: 'services',
|
|
287
|
+
guard: 'guards',
|
|
288
|
+
health: 'health',
|
|
289
|
+
};
|
|
290
|
+
const dir = dirMap[type] || type;
|
|
291
|
+
const fileName = name.toLowerCase().replace(/\s+/g, '-') + '.ts';
|
|
292
|
+
return path.join(basePath, 'src', dir, fileName);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Generate command
|
|
296
|
+
*/
|
|
297
|
+
export async function generate(type, name, options = {}) {
|
|
298
|
+
// Special case for types generation
|
|
299
|
+
if (type === 'types') {
|
|
300
|
+
const { generateTypes } = await import('./generate-types.js');
|
|
301
|
+
await generateTypes(options);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
console.log(createHeader('Generate', `Creating ${type}: ${name}`));
|
|
305
|
+
// Validate type
|
|
306
|
+
const validTypes = Object.keys(TEMPLATES);
|
|
307
|
+
if (!validTypes.includes(type)) {
|
|
308
|
+
console.log(createErrorBox('Invalid Type', `Valid types: ${validTypes.join(', ')}, types`));
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
// Get template
|
|
312
|
+
const template = TEMPLATES[type](name);
|
|
313
|
+
const generatedFiles = [];
|
|
314
|
+
// Determine file path
|
|
315
|
+
let filePath;
|
|
316
|
+
if (type === 'module' || type === 'tools' || type === 'resources' || type === 'prompts') {
|
|
317
|
+
const moduleName = options.module || name.toLowerCase();
|
|
318
|
+
filePath = getFilePath(type, name, moduleName);
|
|
319
|
+
// Create module directory if it doesn't exist
|
|
320
|
+
const moduleDir = path.dirname(filePath);
|
|
321
|
+
if (!fs.existsSync(moduleDir)) {
|
|
322
|
+
fs.mkdirSync(moduleDir, { recursive: true });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
filePath = getFilePath(type, name);
|
|
327
|
+
// Create directory if it doesn't exist
|
|
328
|
+
const dir = path.dirname(filePath);
|
|
329
|
+
if (!fs.existsSync(dir)) {
|
|
330
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// Check if file already exists
|
|
334
|
+
if (fs.existsSync(filePath) && !options.force) {
|
|
335
|
+
console.log(createErrorBox('File Exists', 'Use --force to overwrite'));
|
|
336
|
+
spacer();
|
|
337
|
+
log(`Path: ${filePath}`, 'dim');
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
// Write file
|
|
341
|
+
fs.writeFileSync(filePath, template, 'utf-8');
|
|
342
|
+
generatedFiles.push(path.relative(process.cwd(), filePath));
|
|
343
|
+
log(`Created ${type}: ${chalk.cyan(path.relative(process.cwd(), filePath))}`, 'success');
|
|
344
|
+
// If generating a module, create all related files
|
|
345
|
+
if (type === 'module' && !options.skipRelated) {
|
|
346
|
+
const moduleName = name;
|
|
347
|
+
const moduleDir = path.dirname(filePath);
|
|
348
|
+
const toolsPath = path.join(moduleDir, `${name.toLowerCase()}.tools.ts`);
|
|
349
|
+
const resourcesPath = path.join(moduleDir, `${name.toLowerCase()}.resources.ts`);
|
|
350
|
+
const promptsPath = path.join(moduleDir, `${name.toLowerCase()}.prompts.ts`);
|
|
351
|
+
if (!fs.existsSync(toolsPath)) {
|
|
352
|
+
fs.writeFileSync(toolsPath, TEMPLATES.tools(moduleName), 'utf-8');
|
|
353
|
+
generatedFiles.push(path.relative(process.cwd(), toolsPath));
|
|
354
|
+
log(`Created tools: ${chalk.cyan(path.relative(process.cwd(), toolsPath))}`, 'success');
|
|
355
|
+
}
|
|
356
|
+
if (!fs.existsSync(resourcesPath)) {
|
|
357
|
+
fs.writeFileSync(resourcesPath, TEMPLATES.resources(moduleName), 'utf-8');
|
|
358
|
+
generatedFiles.push(path.relative(process.cwd(), resourcesPath));
|
|
359
|
+
log(`Created resources: ${chalk.cyan(path.relative(process.cwd(), resourcesPath))}`, 'success');
|
|
360
|
+
}
|
|
361
|
+
if (!fs.existsSync(promptsPath)) {
|
|
362
|
+
fs.writeFileSync(promptsPath, TEMPLATES.prompts(moduleName), 'utf-8');
|
|
363
|
+
generatedFiles.push(path.relative(process.cwd(), promptsPath));
|
|
364
|
+
log(`Created prompts: ${chalk.cyan(path.relative(process.cwd(), promptsPath))}`, 'success');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// Summary
|
|
368
|
+
spacer();
|
|
369
|
+
console.log(createSuccessBox('Generation Complete', generatedFiles));
|
|
370
|
+
nextSteps([
|
|
371
|
+
'Open the generated file and implement the TODOs',
|
|
372
|
+
'Register the component in your module',
|
|
373
|
+
'Build and test your changes',
|
|
374
|
+
]);
|
|
375
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAiDA,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,WAAW,iBA+MtF"}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname } from 'path';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import { NITRO_BANNER_FULL, createSuccessBox, NitroSpinner, log, spacer, nextSteps, brand } from '../ui/branding.js';
|
|
9
|
+
// ES module equivalent of __dirname
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
/**
|
|
13
|
+
* Check if nitrostack is installed globally from npm registry
|
|
14
|
+
*/
|
|
15
|
+
function isNitrostackFromNpm() {
|
|
16
|
+
try {
|
|
17
|
+
const result = execSync('npm list -g nitrostack --json', {
|
|
18
|
+
encoding: 'utf-8',
|
|
19
|
+
stdio: 'pipe'
|
|
20
|
+
});
|
|
21
|
+
const parsed = JSON.parse(result);
|
|
22
|
+
if (parsed.dependencies && parsed.dependencies.nitrostack) {
|
|
23
|
+
const nitrostackInfo = parsed.dependencies.nitrostack;
|
|
24
|
+
return nitrostackInfo.version && !nitrostackInfo.resolved?.includes('file:');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check if we're running nitrostack from a local development environment
|
|
34
|
+
*/
|
|
35
|
+
function isLocalDevelopment() {
|
|
36
|
+
return __dirname.includes('/src/cli/') || __dirname.includes('\\src\\cli\\');
|
|
37
|
+
}
|
|
38
|
+
export async function initCommand(projectName, options) {
|
|
39
|
+
let spinner = null;
|
|
40
|
+
try {
|
|
41
|
+
// Show banner
|
|
42
|
+
console.log(NITRO_BANNER_FULL);
|
|
43
|
+
console.log(chalk.dim(' Create a new MCP server project\n'));
|
|
44
|
+
// Prompt for project name if not provided
|
|
45
|
+
if (!projectName) {
|
|
46
|
+
const answers = await inquirer.prompt([
|
|
47
|
+
{
|
|
48
|
+
type: 'input',
|
|
49
|
+
name: 'projectName',
|
|
50
|
+
message: chalk.white('Project name:'),
|
|
51
|
+
default: 'my-mcp-server',
|
|
52
|
+
validate: (input) => {
|
|
53
|
+
if (input.trim().length === 0) {
|
|
54
|
+
return 'Project name cannot be empty';
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
]);
|
|
60
|
+
projectName = answers.projectName;
|
|
61
|
+
}
|
|
62
|
+
const finalProjectName = projectName;
|
|
63
|
+
const targetDir = path.join(process.cwd(), finalProjectName);
|
|
64
|
+
// Check if directory exists
|
|
65
|
+
if (fs.existsSync(targetDir)) {
|
|
66
|
+
const { overwrite } = await inquirer.prompt([
|
|
67
|
+
{
|
|
68
|
+
type: 'confirm',
|
|
69
|
+
name: 'overwrite',
|
|
70
|
+
message: chalk.yellow(`Directory ${finalProjectName} already exists. Overwrite?`),
|
|
71
|
+
default: false,
|
|
72
|
+
},
|
|
73
|
+
]);
|
|
74
|
+
if (!overwrite) {
|
|
75
|
+
log('Cancelled', 'warning');
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
fs.removeSync(targetDir);
|
|
79
|
+
}
|
|
80
|
+
// Prompt for template and details
|
|
81
|
+
const answers = await inquirer.prompt([
|
|
82
|
+
{
|
|
83
|
+
type: 'list',
|
|
84
|
+
name: 'template',
|
|
85
|
+
message: chalk.white('Choose a template:'),
|
|
86
|
+
choices: [
|
|
87
|
+
{
|
|
88
|
+
name: `${brand.accent('Starter')} ${chalk.dim('Simple calculator for learning basics')}`,
|
|
89
|
+
value: 'typescript-starter',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: `${brand.accent('Advanced')} ${chalk.dim('Pizza shop finder with maps & widgets')}`,
|
|
93
|
+
value: 'typescript-pizzaz',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: `${brand.accent('OAuth')} ${chalk.dim('Flight booking with OAuth 2.1 auth')}`,
|
|
97
|
+
value: 'typescript-oauth',
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
default: 'typescript-starter',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
type: 'input',
|
|
104
|
+
name: 'description',
|
|
105
|
+
message: chalk.white('Description:'),
|
|
106
|
+
default: 'My awesome MCP server',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
type: 'input',
|
|
110
|
+
name: 'author',
|
|
111
|
+
message: chalk.white('Author:'),
|
|
112
|
+
default: '',
|
|
113
|
+
},
|
|
114
|
+
]);
|
|
115
|
+
spacer();
|
|
116
|
+
spinner = new NitroSpinner('Creating project structure...').start();
|
|
117
|
+
// Create project directory
|
|
118
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
119
|
+
// Get template path - templates are in the CLI package root
|
|
120
|
+
const templateDir = path.join(__dirname, '../../templates', answers.template);
|
|
121
|
+
// Copy template files
|
|
122
|
+
if (fs.existsSync(templateDir)) {
|
|
123
|
+
fs.copySync(templateDir, targetDir);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
await createProjectFromScratch(targetDir, finalProjectName, answers);
|
|
127
|
+
}
|
|
128
|
+
// Update package.json
|
|
129
|
+
const packageJsonPath = path.join(targetDir, 'package.json');
|
|
130
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
131
|
+
const packageJson = fs.readJSONSync(packageJsonPath);
|
|
132
|
+
packageJson.name = finalProjectName;
|
|
133
|
+
packageJson.description = answers.description;
|
|
134
|
+
packageJson.author = answers.author;
|
|
135
|
+
fs.writeJSONSync(packageJsonPath, packageJson, { spaces: 2 });
|
|
136
|
+
}
|
|
137
|
+
spinner.succeed('Project created');
|
|
138
|
+
// Install dependencies
|
|
139
|
+
if (!options.skipInstall) {
|
|
140
|
+
spinner = new NitroSpinner('Installing dependencies...').start();
|
|
141
|
+
try {
|
|
142
|
+
execSync('npm install', {
|
|
143
|
+
cwd: targetDir,
|
|
144
|
+
stdio: 'pipe',
|
|
145
|
+
});
|
|
146
|
+
spinner.succeed('Dependencies installed');
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
spinner.warn('Dependencies installation failed - run npm install manually');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Handle widgets
|
|
153
|
+
if (!options.skipInstall && ['typescript-starter', 'typescript-pizzaz', 'typescript-oauth'].includes(answers.template)) {
|
|
154
|
+
const fromNpm = isNitrostackFromNpm();
|
|
155
|
+
const isLocalDev = isLocalDevelopment();
|
|
156
|
+
if (isLocalDev && !fromNpm) {
|
|
157
|
+
// Local development - link nitrostack
|
|
158
|
+
spinner = new NitroSpinner('Linking local nitrostack...').start();
|
|
159
|
+
try {
|
|
160
|
+
execSync('npm link nitrostack', { cwd: targetDir, stdio: 'pipe' });
|
|
161
|
+
spinner.succeed('Local nitrostack linked');
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
spinner.warn('Failed to link nitrostack');
|
|
165
|
+
}
|
|
166
|
+
spinner = new NitroSpinner('Installing widget dependencies...').start();
|
|
167
|
+
try {
|
|
168
|
+
const widgetsDir = path.join(targetDir, 'src', 'widgets');
|
|
169
|
+
execSync('npm link nitrostack', { cwd: widgetsDir, stdio: 'pipe' });
|
|
170
|
+
execSync('npm install', { cwd: widgetsDir, stdio: 'pipe' });
|
|
171
|
+
spinner.succeed('Widget dependencies installed');
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
spinner.warn('Widget dependencies will be installed on first run');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
// Published package
|
|
179
|
+
spinner = new NitroSpinner('Installing widget dependencies...').start();
|
|
180
|
+
try {
|
|
181
|
+
const widgetsDir = path.join(targetDir, 'src', 'widgets');
|
|
182
|
+
execSync('npm install', { cwd: widgetsDir, stdio: 'pipe' });
|
|
183
|
+
execSync('npm install nitrostack@latest', { cwd: widgetsDir, stdio: 'pipe' });
|
|
184
|
+
spinner.succeed('Widget dependencies installed');
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
spinner.warn('Widget dependencies will be installed on first run');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Success summary
|
|
192
|
+
spacer();
|
|
193
|
+
console.log(createSuccessBox('Project Ready', [
|
|
194
|
+
`Name: ${finalProjectName}`,
|
|
195
|
+
`Template: ${answers.template}`,
|
|
196
|
+
`Path: ${targetDir}`,
|
|
197
|
+
]));
|
|
198
|
+
// Next steps
|
|
199
|
+
const steps = [
|
|
200
|
+
`cd ${finalProjectName}`,
|
|
201
|
+
];
|
|
202
|
+
if (options.skipInstall) {
|
|
203
|
+
steps.push('npm install');
|
|
204
|
+
}
|
|
205
|
+
if (answers.template === 'typescript-oauth') {
|
|
206
|
+
steps.push('cp .env.example .env # Configure OAuth');
|
|
207
|
+
}
|
|
208
|
+
steps.push('npm run dev');
|
|
209
|
+
nextSteps(steps);
|
|
210
|
+
// Template-specific tips
|
|
211
|
+
if (answers.template === 'typescript-oauth') {
|
|
212
|
+
console.log(chalk.dim(' OAuth Setup: See OAUTH_SETUP.md for provider guides\n'));
|
|
213
|
+
}
|
|
214
|
+
else if (answers.template === 'typescript-pizzaz') {
|
|
215
|
+
console.log(chalk.dim(' Mapbox (optional): Get free key from mapbox.com\n'));
|
|
216
|
+
}
|
|
217
|
+
console.log(chalk.dim(' Happy coding! 🎉\n'));
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
if (spinner) {
|
|
221
|
+
spinner.fail('Failed to create project');
|
|
222
|
+
}
|
|
223
|
+
spacer();
|
|
224
|
+
log(error instanceof Error ? error.message : String(error), 'error');
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async function createProjectFromScratch(targetDir, projectName, answers) {
|
|
229
|
+
const packageJson = {
|
|
230
|
+
name: projectName,
|
|
231
|
+
version: '1.0.0',
|
|
232
|
+
description: answers.description,
|
|
233
|
+
author: answers.author,
|
|
234
|
+
main: 'dist/index.js',
|
|
235
|
+
scripts: {
|
|
236
|
+
dev: 'tsx watch src/index.ts',
|
|
237
|
+
build: 'tsc',
|
|
238
|
+
start: 'node dist/index.js',
|
|
239
|
+
},
|
|
240
|
+
dependencies: {
|
|
241
|
+
nitrostack: '^1.0.0',
|
|
242
|
+
dotenv: '^16.3.1',
|
|
243
|
+
},
|
|
244
|
+
devDependencies: {
|
|
245
|
+
'@types/node': '^20.10.5',
|
|
246
|
+
typescript: '^5.3.3',
|
|
247
|
+
tsx: '^4.7.0',
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
fs.writeJSONSync(path.join(targetDir, 'package.json'), packageJson, { spaces: 2 });
|
|
251
|
+
const tsconfig = {
|
|
252
|
+
compilerOptions: {
|
|
253
|
+
target: 'ES2022',
|
|
254
|
+
module: 'commonjs',
|
|
255
|
+
outDir: './dist',
|
|
256
|
+
rootDir: './src',
|
|
257
|
+
strict: true,
|
|
258
|
+
esModuleInterop: true,
|
|
259
|
+
skipLibCheck: true,
|
|
260
|
+
forceConsistentCasingInFileNames: true,
|
|
261
|
+
},
|
|
262
|
+
include: ['src/**/*'],
|
|
263
|
+
exclude: ['node_modules'],
|
|
264
|
+
};
|
|
265
|
+
fs.writeJSONSync(path.join(targetDir, 'tsconfig.json'), tsconfig, { spaces: 2 });
|
|
266
|
+
fs.mkdirSync(path.join(targetDir, 'src'), { recursive: true });
|
|
267
|
+
const indexTs = `import { createServer, createTool, z } from 'nitrostack';
|
|
268
|
+
|
|
269
|
+
const server = createServer({
|
|
270
|
+
name: '${projectName}',
|
|
271
|
+
version: '1.0.0',
|
|
272
|
+
description: '${answers.description}',
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
server.tool(
|
|
276
|
+
createTool({
|
|
277
|
+
name: 'hello',
|
|
278
|
+
description: 'Say hello to someone',
|
|
279
|
+
inputSchema: z.object({
|
|
280
|
+
name: z.string().describe('The name to greet'),
|
|
281
|
+
}),
|
|
282
|
+
handler: async (input, context) => {
|
|
283
|
+
context.logger.info(\`Greeting \${input.name}\`);
|
|
284
|
+
return \`Hello, \${input.name}! 👋\`;
|
|
285
|
+
},
|
|
286
|
+
})
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
server.start().catch((error) => {
|
|
290
|
+
console.error('Failed to start server:', error);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
});
|
|
293
|
+
`;
|
|
294
|
+
fs.writeFileSync(path.join(targetDir, 'src', 'index.ts'), indexTs);
|
|
295
|
+
fs.writeFileSync(path.join(targetDir, '.env'), '# Environment variables\n');
|
|
296
|
+
const gitignore = `node_modules/
|
|
297
|
+
dist/
|
|
298
|
+
.env
|
|
299
|
+
.env.local
|
|
300
|
+
*.log
|
|
301
|
+
`;
|
|
302
|
+
fs.writeFileSync(path.join(targetDir, '.gitignore'), gitignore);
|
|
303
|
+
const readme = `# ${projectName}
|
|
304
|
+
|
|
305
|
+
${answers.description}
|
|
306
|
+
|
|
307
|
+
## Getting Started
|
|
308
|
+
|
|
309
|
+
\`\`\`bash
|
|
310
|
+
npm install
|
|
311
|
+
npm run dev
|
|
312
|
+
\`\`\`
|
|
313
|
+
|
|
314
|
+
## Building for Production
|
|
315
|
+
|
|
316
|
+
\`\`\`bash
|
|
317
|
+
npm run build
|
|
318
|
+
npm start
|
|
319
|
+
\`\`\`
|
|
320
|
+
|
|
321
|
+
Built with [NitroStack](https://nitrostack.dev) âš¡
|
|
322
|
+
`;
|
|
323
|
+
fs.writeFileSync(path.join(targetDir, 'README.md'), readme);
|
|
324
|
+
}
|