@opensaas/stack-cli 0.1.7 → 0.4.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +348 -0
- package/CLAUDE.md +60 -12
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +13 -13
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/mcp.d.ts +6 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +116 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/generator/context.d.ts.map +1 -1
- package/dist/generator/context.js +40 -7
- package/dist/generator/context.js.map +1 -1
- package/dist/generator/index.d.ts +4 -1
- package/dist/generator/index.d.ts.map +1 -1
- package/dist/generator/index.js +4 -1
- package/dist/generator/index.js.map +1 -1
- package/dist/generator/lists.d.ts +31 -0
- package/dist/generator/lists.d.ts.map +1 -0
- package/dist/generator/lists.js +123 -0
- package/dist/generator/lists.js.map +1 -0
- package/dist/generator/plugin-types.d.ts +10 -0
- package/dist/generator/plugin-types.d.ts.map +1 -0
- package/dist/generator/plugin-types.js +122 -0
- package/dist/generator/plugin-types.js.map +1 -0
- package/dist/generator/prisma-config.d.ts +17 -0
- package/dist/generator/prisma-config.d.ts.map +1 -0
- package/dist/generator/prisma-config.js +40 -0
- package/dist/generator/prisma-config.js.map +1 -0
- package/dist/generator/prisma-extensions.d.ts +11 -0
- package/dist/generator/prisma-extensions.d.ts.map +1 -0
- package/dist/generator/prisma-extensions.js +134 -0
- package/dist/generator/prisma-extensions.js.map +1 -0
- package/dist/generator/prisma.d.ts.map +1 -1
- package/dist/generator/prisma.js +5 -2
- package/dist/generator/prisma.js.map +1 -1
- package/dist/generator/types.d.ts.map +1 -1
- package/dist/generator/types.js +201 -17
- package/dist/generator/types.js.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/lib/documentation-provider.d.ts +43 -0
- package/dist/mcp/lib/documentation-provider.d.ts.map +1 -0
- package/dist/mcp/lib/documentation-provider.js +163 -0
- package/dist/mcp/lib/documentation-provider.js.map +1 -0
- package/dist/mcp/lib/features/catalog.d.ts +26 -0
- package/dist/mcp/lib/features/catalog.d.ts.map +1 -0
- package/dist/mcp/lib/features/catalog.js +291 -0
- package/dist/mcp/lib/features/catalog.js.map +1 -0
- package/dist/mcp/lib/generators/feature-generator.d.ts +35 -0
- package/dist/mcp/lib/generators/feature-generator.d.ts.map +1 -0
- package/dist/mcp/lib/generators/feature-generator.js +546 -0
- package/dist/mcp/lib/generators/feature-generator.js.map +1 -0
- package/dist/mcp/lib/types.d.ts +80 -0
- package/dist/mcp/lib/types.d.ts.map +1 -0
- package/dist/mcp/lib/types.js +5 -0
- package/dist/mcp/lib/types.js.map +1 -0
- package/dist/mcp/lib/wizards/wizard-engine.d.ts +71 -0
- package/dist/mcp/lib/wizards/wizard-engine.d.ts.map +1 -0
- package/dist/mcp/lib/wizards/wizard-engine.js +356 -0
- package/dist/mcp/lib/wizards/wizard-engine.js.map +1 -0
- package/dist/mcp/server/index.d.ts +8 -0
- package/dist/mcp/server/index.d.ts.map +1 -0
- package/dist/mcp/server/index.js +202 -0
- package/dist/mcp/server/index.js.map +1 -0
- package/dist/mcp/server/stack-mcp-server.d.ts +92 -0
- package/dist/mcp/server/stack-mcp-server.d.ts.map +1 -0
- package/dist/mcp/server/stack-mcp-server.js +265 -0
- package/dist/mcp/server/stack-mcp-server.js.map +1 -0
- package/package.json +10 -8
- package/src/commands/__snapshots__/generate.test.ts.snap +145 -38
- package/src/commands/dev.test.ts +0 -1
- package/src/commands/generate.test.ts +18 -8
- package/src/commands/generate.ts +20 -19
- package/src/commands/mcp.ts +135 -0
- package/src/generator/__snapshots__/context.test.ts.snap +63 -18
- package/src/generator/__snapshots__/prisma.test.ts.snap +8 -16
- package/src/generator/__snapshots__/types.test.ts.snap +1267 -95
- package/src/generator/context.test.ts +15 -8
- package/src/generator/context.ts +40 -7
- package/src/generator/index.ts +4 -1
- package/src/generator/lists.test.ts +335 -0
- package/src/generator/lists.ts +140 -0
- package/src/generator/plugin-types.ts +147 -0
- package/src/generator/prisma-config.ts +46 -0
- package/src/generator/prisma-extensions.ts +159 -0
- package/src/generator/prisma.test.ts +0 -10
- package/src/generator/prisma.ts +6 -2
- package/src/generator/types.test.ts +0 -12
- package/src/generator/types.ts +257 -17
- package/src/index.ts +4 -0
- package/src/mcp/lib/documentation-provider.ts +203 -0
- package/src/mcp/lib/features/catalog.ts +301 -0
- package/src/mcp/lib/generators/feature-generator.ts +598 -0
- package/src/mcp/lib/types.ts +89 -0
- package/src/mcp/lib/wizards/wizard-engine.ts +427 -0
- package/src/mcp/server/index.ts +240 -0
- package/src/mcp/server/stack-mcp-server.ts +301 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/generator/type-patcher.d.ts +0 -13
- package/dist/generator/type-patcher.d.ts.map +0 -1
- package/dist/generator/type-patcher.js +0 -68
- package/dist/generator/type-patcher.js.map +0 -1
- package/src/generator/type-patcher.ts +0 -93
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature generator - Generates code, config, and documentation for features
|
|
3
|
+
*/
|
|
4
|
+
export class FeatureGenerator {
|
|
5
|
+
feature;
|
|
6
|
+
answers;
|
|
7
|
+
followUpAnswers;
|
|
8
|
+
constructor(feature, answers, followUpAnswers) {
|
|
9
|
+
this.feature = feature;
|
|
10
|
+
this.answers = answers;
|
|
11
|
+
this.followUpAnswers = followUpAnswers;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Generate complete feature implementation
|
|
15
|
+
*/
|
|
16
|
+
generate() {
|
|
17
|
+
const featureType = this.feature.id;
|
|
18
|
+
switch (featureType) {
|
|
19
|
+
case 'authentication':
|
|
20
|
+
return this.generateAuthentication();
|
|
21
|
+
case 'blog':
|
|
22
|
+
return this.generateBlog();
|
|
23
|
+
case 'comments':
|
|
24
|
+
return this.generateComments();
|
|
25
|
+
case 'file-upload':
|
|
26
|
+
return this.generateFileUpload();
|
|
27
|
+
case 'semantic-search':
|
|
28
|
+
return this.generateSemanticSearch();
|
|
29
|
+
default:
|
|
30
|
+
throw new Error(`Unknown feature type: ${featureType}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Generate authentication feature
|
|
35
|
+
*/
|
|
36
|
+
generateAuthentication() {
|
|
37
|
+
const authMethods = this.answers['auth-methods'];
|
|
38
|
+
const hasRoles = this.answers['user-roles'];
|
|
39
|
+
const roles = hasRoles
|
|
40
|
+
? this.followUpAnswers['user-roles_followup']
|
|
41
|
+
?.split(',')
|
|
42
|
+
.map((r) => r.trim()) || ['admin', 'user']
|
|
43
|
+
: null;
|
|
44
|
+
const userFields = this.answers['user-fields'] || [];
|
|
45
|
+
const emailVerification = this.answers['email-verification'];
|
|
46
|
+
const hasOAuth = authMethods.some((m) => ['Google OAuth', 'GitHub OAuth'].includes(m));
|
|
47
|
+
const hasPassword = authMethods.includes('Email & Password');
|
|
48
|
+
const hasMagicLink = authMethods.includes('Magic Links');
|
|
49
|
+
// Build User list fields
|
|
50
|
+
const fields = ['email: text({ validation: { isRequired: true } })'];
|
|
51
|
+
if (hasPassword) {
|
|
52
|
+
fields.push('password: password({ validation: { isRequired: true } })');
|
|
53
|
+
}
|
|
54
|
+
fields.push('name: text()');
|
|
55
|
+
if (hasRoles && roles) {
|
|
56
|
+
fields.push(`role: select({ options: [${roles.map((r) => `'${r}'`).join(', ')}], defaultValue: '${roles[roles.length - 1]}' })`);
|
|
57
|
+
}
|
|
58
|
+
if (userFields.includes('Avatar')) {
|
|
59
|
+
fields.push('avatar: text()');
|
|
60
|
+
}
|
|
61
|
+
if (userFields.includes('Bio')) {
|
|
62
|
+
fields.push('bio: text({ ui: { displayMode: "textarea" } })');
|
|
63
|
+
}
|
|
64
|
+
if (userFields.includes('Phone')) {
|
|
65
|
+
fields.push('phone: text()');
|
|
66
|
+
}
|
|
67
|
+
if (userFields.includes('Location')) {
|
|
68
|
+
fields.push('location: text()');
|
|
69
|
+
}
|
|
70
|
+
if (userFields.includes('Website')) {
|
|
71
|
+
fields.push('website: text()');
|
|
72
|
+
}
|
|
73
|
+
// Config updates
|
|
74
|
+
const configUpdates = `import { config, list } from '@opensaas/stack-core';
|
|
75
|
+
import { text, password, select } from '@opensaas/stack-core/fields';
|
|
76
|
+
import { authPlugin } from '@opensaas/stack-auth';
|
|
77
|
+
|
|
78
|
+
export default config({
|
|
79
|
+
plugins: [
|
|
80
|
+
authPlugin({
|
|
81
|
+
emailAndPassword: { enabled: ${hasPassword} },
|
|
82
|
+
${hasOAuth
|
|
83
|
+
? `oauth: {
|
|
84
|
+
google: { enabled: ${authMethods.includes('Google OAuth')} },
|
|
85
|
+
github: { enabled: ${authMethods.includes('GitHub OAuth')} },
|
|
86
|
+
},`
|
|
87
|
+
: ''}
|
|
88
|
+
${hasMagicLink ? `magicLink: { enabled: true },` : ''}
|
|
89
|
+
${emailVerification ? `emailVerification: { enabled: true },` : ''}
|
|
90
|
+
sessionFields: ['userId', 'email', 'name'${hasRoles ? ", 'role'" : ''}],
|
|
91
|
+
}),
|
|
92
|
+
],
|
|
93
|
+
db: {
|
|
94
|
+
provider: 'postgresql', // or 'sqlite'
|
|
95
|
+
url: process.env.DATABASE_URL,
|
|
96
|
+
},
|
|
97
|
+
lists: {
|
|
98
|
+
User: list({
|
|
99
|
+
fields: {
|
|
100
|
+
${fields.join(',\n ')}
|
|
101
|
+
},
|
|
102
|
+
access: {
|
|
103
|
+
operation: {
|
|
104
|
+
query: () => true,
|
|
105
|
+
create: () => true, // Public sign-up
|
|
106
|
+
update: ({ session, item }) => session?.userId === item.id,
|
|
107
|
+
delete: ({ session }) => session?.role === 'admin',
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
}),
|
|
111
|
+
// Add your other lists here
|
|
112
|
+
},
|
|
113
|
+
});`;
|
|
114
|
+
// Generated files
|
|
115
|
+
const files = [];
|
|
116
|
+
// Sign-in page
|
|
117
|
+
if (hasPassword || hasOAuth) {
|
|
118
|
+
files.push({
|
|
119
|
+
path: 'app/sign-in/page.tsx',
|
|
120
|
+
language: 'tsx',
|
|
121
|
+
description: 'Sign-in page with form and OAuth buttons',
|
|
122
|
+
content: `import { SignInForm } from '@opensaas/stack-auth/ui';
|
|
123
|
+
|
|
124
|
+
export default function SignInPage() {
|
|
125
|
+
return (
|
|
126
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
127
|
+
<div className="w-full max-w-md">
|
|
128
|
+
<h1 className="text-2xl font-bold mb-6">Sign In</h1>
|
|
129
|
+
<SignInForm
|
|
130
|
+
${hasPassword ? 'emailAndPassword' : ''}
|
|
131
|
+
${hasOAuth ? `oauth={[${authMethods.includes('Google OAuth') ? "'google'" : ''}${authMethods.includes('GitHub OAuth') ? ", 'github'" : ''}]}` : ''}
|
|
132
|
+
redirectTo="/dashboard"
|
|
133
|
+
/>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
// Sign-up page
|
|
141
|
+
if (hasPassword) {
|
|
142
|
+
files.push({
|
|
143
|
+
path: 'app/sign-up/page.tsx',
|
|
144
|
+
language: 'tsx',
|
|
145
|
+
description: 'Sign-up page with registration form',
|
|
146
|
+
content: `import { SignUpForm } from '@opensaas/stack-auth/ui';
|
|
147
|
+
|
|
148
|
+
export default function SignUpPage() {
|
|
149
|
+
return (
|
|
150
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
151
|
+
<div className="w-full max-w-md">
|
|
152
|
+
<h1 className="text-2xl font-bold mb-6">Create Account</h1>
|
|
153
|
+
<SignUpForm
|
|
154
|
+
fields={['email', 'password', 'name']}
|
|
155
|
+
redirectTo="/dashboard"
|
|
156
|
+
${emailVerification ? 'requireEmailVerification' : ''}
|
|
157
|
+
/>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}`,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
// Access control helpers
|
|
165
|
+
files.push({
|
|
166
|
+
path: 'lib/access-control.ts',
|
|
167
|
+
language: 'typescript',
|
|
168
|
+
description: 'Reusable access control functions',
|
|
169
|
+
content: `import type { AccessControl } from '@opensaas/stack-core';
|
|
170
|
+
|
|
171
|
+
export const isAuthenticated: AccessControl = ({ session }) => {
|
|
172
|
+
return !!session?.userId;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
${hasRoles
|
|
176
|
+
? `export const isAdmin: AccessControl = ({ session }) => {
|
|
177
|
+
return session?.role === 'admin';
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export const isOwner: AccessControl = ({ session, item }) => {
|
|
181
|
+
return session?.userId === item.id;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export const isAdminOrOwner: AccessControl = ({ session, item }) => {
|
|
185
|
+
return session?.role === 'admin' || session?.userId === item.id;
|
|
186
|
+
};`
|
|
187
|
+
: ''}
|
|
188
|
+
|
|
189
|
+
export const requireAuth: AccessControl = ({ session }) => {
|
|
190
|
+
if (!session?.userId) {
|
|
191
|
+
throw new Error('Authentication required');
|
|
192
|
+
}
|
|
193
|
+
return true;
|
|
194
|
+
};`,
|
|
195
|
+
});
|
|
196
|
+
// Environment variables
|
|
197
|
+
const envVars = {
|
|
198
|
+
DATABASE_URL: 'postgresql://user:password@localhost:5432/mydb',
|
|
199
|
+
BETTER_AUTH_SECRET: '<generate-with-openssl-rand-base64-32>',
|
|
200
|
+
BETTER_AUTH_URL: 'http://localhost:3000',
|
|
201
|
+
};
|
|
202
|
+
if (authMethods.includes('Google OAuth')) {
|
|
203
|
+
envVars.GOOGLE_CLIENT_ID = '<your-google-client-id>';
|
|
204
|
+
envVars.GOOGLE_CLIENT_SECRET = '<your-google-client-secret>';
|
|
205
|
+
}
|
|
206
|
+
if (authMethods.includes('GitHub OAuth')) {
|
|
207
|
+
envVars.GITHUB_CLIENT_ID = '<your-github-client-id>';
|
|
208
|
+
envVars.GITHUB_CLIENT_SECRET = '<your-github-client-secret>';
|
|
209
|
+
}
|
|
210
|
+
// Next steps
|
|
211
|
+
const nextSteps = [
|
|
212
|
+
'Copy the config updates to your `opensaas.config.ts`',
|
|
213
|
+
'Create the files shown above in your project',
|
|
214
|
+
'Add environment variables to your `.env` file',
|
|
215
|
+
hasOAuth ? 'Set up OAuth applications in Google/GitHub developer consoles' : null,
|
|
216
|
+
'Run `pnpm generate` to update Prisma schema',
|
|
217
|
+
'Run `pnpm db:push` to update your database',
|
|
218
|
+
'Start your dev server: `pnpm dev`',
|
|
219
|
+
`Visit http://localhost:3000/${hasPassword ? 'sign-up' : 'sign-in'} to test authentication`,
|
|
220
|
+
].filter(Boolean);
|
|
221
|
+
// Dev guide section
|
|
222
|
+
const devGuideSection = `## Authentication Feature
|
|
223
|
+
|
|
224
|
+
This project uses Better-auth for authentication with the following configuration:
|
|
225
|
+
|
|
226
|
+
${authMethods.map((m) => `- ${m}`).join('\n')}
|
|
227
|
+
${hasRoles ? `\n**User Roles**: ${roles?.join(', ')}` : ''}
|
|
228
|
+
|
|
229
|
+
### Access Control Helpers
|
|
230
|
+
|
|
231
|
+
Use these functions in your list configurations:
|
|
232
|
+
|
|
233
|
+
\`\`\`typescript
|
|
234
|
+
import { isAuthenticated${hasRoles ? ', isAdmin, isOwner' : ''} } from './lib/access-control';
|
|
235
|
+
|
|
236
|
+
// In your list config:
|
|
237
|
+
access: {
|
|
238
|
+
operation: {
|
|
239
|
+
query: () => true,
|
|
240
|
+
create: isAuthenticated,
|
|
241
|
+
update: isOwner,
|
|
242
|
+
delete: ${hasRoles ? 'isAdmin' : 'isOwner'},
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
\`\`\`
|
|
246
|
+
|
|
247
|
+
### Protected Routes
|
|
248
|
+
|
|
249
|
+
To protect a route, check the session in your server components:
|
|
250
|
+
|
|
251
|
+
\`\`\`typescript
|
|
252
|
+
import { auth } from '@/lib/auth';
|
|
253
|
+
|
|
254
|
+
export default async function ProtectedPage() {
|
|
255
|
+
const session = await auth();
|
|
256
|
+
|
|
257
|
+
if (!session) {
|
|
258
|
+
redirect('/sign-in');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return <div>Protected content for {session.user.name}</div>;
|
|
262
|
+
}
|
|
263
|
+
\`\`\`
|
|
264
|
+
|
|
265
|
+
### Getting the Current User
|
|
266
|
+
|
|
267
|
+
In server actions or API routes:
|
|
268
|
+
|
|
269
|
+
\`\`\`typescript
|
|
270
|
+
import { getContext } from '@/.opensaas/context';
|
|
271
|
+
|
|
272
|
+
const context = await getContext();
|
|
273
|
+
const currentUser = await context.db.user.findUnique({
|
|
274
|
+
where: { id: context.session?.userId }
|
|
275
|
+
});
|
|
276
|
+
\`\`\``;
|
|
277
|
+
return {
|
|
278
|
+
configUpdates,
|
|
279
|
+
files,
|
|
280
|
+
instructions: nextSteps,
|
|
281
|
+
devGuideSection,
|
|
282
|
+
envVars,
|
|
283
|
+
nextSteps,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Generate blog feature
|
|
288
|
+
*/
|
|
289
|
+
generateBlog() {
|
|
290
|
+
const contentEditor = this.answers['content-editor'];
|
|
291
|
+
const hasStatus = this.answers['post-status'];
|
|
292
|
+
const taxonomy = this.answers['taxonomy'] || [];
|
|
293
|
+
const postFields = this.answers['post-fields'] || [];
|
|
294
|
+
const useTiptap = contentEditor === 'Rich text editor (Tiptap)';
|
|
295
|
+
const useMarkdown = contentEditor === 'Markdown';
|
|
296
|
+
// Build Post fields
|
|
297
|
+
const fields = [
|
|
298
|
+
'title: text({ validation: { isRequired: true } })',
|
|
299
|
+
'slug: text({ validation: { isRequired: true } })',
|
|
300
|
+
];
|
|
301
|
+
if (useTiptap) {
|
|
302
|
+
fields.push('content: richText({ validation: { isRequired: true } })');
|
|
303
|
+
}
|
|
304
|
+
else if (useMarkdown) {
|
|
305
|
+
fields.push('content: text({ ui: { displayMode: "textarea" }, validation: { isRequired: true } })');
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
fields.push('content: text({ ui: { displayMode: "textarea" }, validation: { isRequired: true } })');
|
|
309
|
+
}
|
|
310
|
+
fields.push('author: relationship({ ref: "User.posts" })');
|
|
311
|
+
if (hasStatus) {
|
|
312
|
+
fields.push("status: select({ options: ['draft', 'published'], defaultValue: 'draft' })");
|
|
313
|
+
fields.push('publishedAt: timestamp()');
|
|
314
|
+
}
|
|
315
|
+
if (postFields.includes('Featured image')) {
|
|
316
|
+
fields.push('featuredImage: text()');
|
|
317
|
+
}
|
|
318
|
+
if (postFields.includes('Excerpt/summary')) {
|
|
319
|
+
fields.push('excerpt: text({ ui: { displayMode: "textarea" } })');
|
|
320
|
+
}
|
|
321
|
+
if (postFields.includes('SEO metadata (title, description)')) {
|
|
322
|
+
fields.push('seoTitle: text()');
|
|
323
|
+
fields.push('seoDescription: text()');
|
|
324
|
+
}
|
|
325
|
+
if (postFields.includes('Reading time estimate')) {
|
|
326
|
+
fields.push('readingTime: integer()');
|
|
327
|
+
}
|
|
328
|
+
const configUpdates = `import { config, list } from '@opensaas/stack-core';
|
|
329
|
+
import { text, select, relationship, timestamp${useTiptap ? '' : ', integer'} } from '@opensaas/stack-core/fields';
|
|
330
|
+
${useTiptap ? "import { richText } from '@opensaas/stack-tiptap/fields';" : ''}
|
|
331
|
+
|
|
332
|
+
export default config({
|
|
333
|
+
lists: {
|
|
334
|
+
Post: list({
|
|
335
|
+
fields: {
|
|
336
|
+
${fields.join(',\n ')},
|
|
337
|
+
},
|
|
338
|
+
access: {
|
|
339
|
+
operation: {
|
|
340
|
+
query: ({ session }) => {
|
|
341
|
+
${hasStatus ? "if (!session) return { status: { equals: 'published' } };" : ''}
|
|
342
|
+
return true;
|
|
343
|
+
},
|
|
344
|
+
create: ({ session }) => !!session?.userId,
|
|
345
|
+
update: ({ session, item }) => session?.userId === item.authorId,
|
|
346
|
+
delete: ({ session, item }) =>
|
|
347
|
+
session?.role === 'admin' || session?.userId === item.authorId,
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
hooks: {
|
|
351
|
+
${hasStatus
|
|
352
|
+
? `resolveInput: async ({ resolvedData, operation }) => {
|
|
353
|
+
// Auto-set publishedAt when publishing
|
|
354
|
+
if (operation === 'update' && resolvedData.status === 'published' && !resolvedData.publishedAt) {
|
|
355
|
+
resolvedData.publishedAt = new Date();
|
|
356
|
+
}
|
|
357
|
+
return resolvedData;
|
|
358
|
+
},`
|
|
359
|
+
: ''}
|
|
360
|
+
},
|
|
361
|
+
}),
|
|
362
|
+
${taxonomy.includes('Categories')
|
|
363
|
+
? `Category: list({
|
|
364
|
+
fields: {
|
|
365
|
+
name: text({ validation: { isRequired: true } }),
|
|
366
|
+
slug: text({ validation: { isRequired: true } }),
|
|
367
|
+
posts: relationship({ ref: 'Post.category', many: true }),
|
|
368
|
+
},
|
|
369
|
+
}),`
|
|
370
|
+
: ''}
|
|
371
|
+
${taxonomy.includes('Tags')
|
|
372
|
+
? `Tag: list({
|
|
373
|
+
fields: {
|
|
374
|
+
name: text({ validation: { isRequired: true } }),
|
|
375
|
+
posts: relationship({ ref: 'Post.tags', many: true }),
|
|
376
|
+
},
|
|
377
|
+
}),`
|
|
378
|
+
: ''}
|
|
379
|
+
},
|
|
380
|
+
});`;
|
|
381
|
+
const files = [];
|
|
382
|
+
// Blog list page
|
|
383
|
+
files.push({
|
|
384
|
+
path: 'app/blog/page.tsx',
|
|
385
|
+
language: 'tsx',
|
|
386
|
+
description: 'Blog listing page',
|
|
387
|
+
content: `import { getContext } from '@/.opensaas/context';
|
|
388
|
+
import Link from 'next/link';
|
|
389
|
+
|
|
390
|
+
export default async function BlogPage() {
|
|
391
|
+
const context = await getContext();
|
|
392
|
+
|
|
393
|
+
const posts = await context.db.post.findMany({
|
|
394
|
+
${hasStatus ? "where: { status: 'published' }," : ''}
|
|
395
|
+
orderBy: { ${hasStatus ? 'publishedAt' : 'createdAt'}: 'desc' },
|
|
396
|
+
include: { author: true },
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
return (
|
|
400
|
+
<div className="container mx-auto py-8">
|
|
401
|
+
<h1 className="text-4xl font-bold mb-8">Blog</h1>
|
|
402
|
+
<div className="grid gap-6">
|
|
403
|
+
{posts.map((post) => (
|
|
404
|
+
<article key={post.id} className="border rounded-lg p-6">
|
|
405
|
+
<Link href={\`/blog/\${post.slug}\`}>
|
|
406
|
+
<h2 className="text-2xl font-bold hover:underline">
|
|
407
|
+
{post.title}
|
|
408
|
+
</h2>
|
|
409
|
+
</Link>
|
|
410
|
+
${postFields.includes('Excerpt/summary') ? '<p className="mt-2 text-gray-600">{post.excerpt}</p>' : ''}
|
|
411
|
+
<div className="mt-4 text-sm text-gray-500">
|
|
412
|
+
By {post.author.name} · ${hasStatus ? '{post.publishedAt?.toLocaleDateString()}' : '{post.createdAt.toLocaleDateString()}'}
|
|
413
|
+
</div>
|
|
414
|
+
</article>
|
|
415
|
+
))}
|
|
416
|
+
</div>
|
|
417
|
+
</div>
|
|
418
|
+
);
|
|
419
|
+
}`,
|
|
420
|
+
});
|
|
421
|
+
// Blog post page
|
|
422
|
+
files.push({
|
|
423
|
+
path: 'app/blog/[slug]/page.tsx',
|
|
424
|
+
language: 'tsx',
|
|
425
|
+
description: 'Individual blog post page',
|
|
426
|
+
content: `import { getContext } from '@/.opensaas/context';
|
|
427
|
+
import { notFound } from 'next/navigation';
|
|
428
|
+
|
|
429
|
+
export default async function BlogPostPage({
|
|
430
|
+
params,
|
|
431
|
+
}: {
|
|
432
|
+
params: { slug: string };
|
|
433
|
+
}) {
|
|
434
|
+
const context = await getContext();
|
|
435
|
+
|
|
436
|
+
const post = await context.db.post.findFirst({
|
|
437
|
+
where: {
|
|
438
|
+
slug: params.slug,
|
|
439
|
+
${hasStatus ? "status: 'published'," : ''}
|
|
440
|
+
},
|
|
441
|
+
include: { author: true },
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
if (!post) {
|
|
445
|
+
notFound();
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return (
|
|
449
|
+
<article className="container mx-auto py-8 max-w-3xl">
|
|
450
|
+
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
|
|
451
|
+
<div className="text-gray-600 mb-8">
|
|
452
|
+
By {post.author.name} · ${hasStatus ? '{post.publishedAt?.toLocaleDateString()}' : '{post.createdAt.toLocaleDateString()}'}
|
|
453
|
+
</div>
|
|
454
|
+
${useTiptap ? '<div className="prose max-w-none" dangerouslySetInnerHTML={{ __html: post.content }} />' : useMarkdown ? '<div className="prose max-w-none">{/* Render markdown here */}{post.content}</div>' : '<div className="prose max-w-none whitespace-pre-wrap">{post.content}</div>'}
|
|
455
|
+
</article>
|
|
456
|
+
);
|
|
457
|
+
}`,
|
|
458
|
+
});
|
|
459
|
+
const nextSteps = [
|
|
460
|
+
'Copy the config updates to your `opensaas.config.ts`',
|
|
461
|
+
'Add the `posts` relationship field to your User list',
|
|
462
|
+
useTiptap ? 'Install Tiptap package: `pnpm add @opensaas/stack-tiptap`' : null,
|
|
463
|
+
'Create the blog pages in your `app/` directory',
|
|
464
|
+
'Run `pnpm generate` to update Prisma schema',
|
|
465
|
+
'Run `pnpm db:push` to update database',
|
|
466
|
+
'Create your first blog post in the admin UI',
|
|
467
|
+
].filter(Boolean);
|
|
468
|
+
const devGuideSection = `## Blog Feature
|
|
469
|
+
|
|
470
|
+
This project includes a blog system with:
|
|
471
|
+
|
|
472
|
+
- ${contentEditor} for writing posts
|
|
473
|
+
${hasStatus ? '- Draft/publish workflow' : ''}
|
|
474
|
+
${taxonomy.length > 0 ? `- ${taxonomy.join(' and ')} for organization` : ''}
|
|
475
|
+
|
|
476
|
+
### Creating a Post
|
|
477
|
+
|
|
478
|
+
${hasStatus
|
|
479
|
+
? `Posts start as drafts and can be published when ready:
|
|
480
|
+
|
|
481
|
+
\`\`\`typescript
|
|
482
|
+
const post = await context.db.post.create({
|
|
483
|
+
data: {
|
|
484
|
+
title: 'My Post',
|
|
485
|
+
slug: 'my-post',
|
|
486
|
+
content: '...',
|
|
487
|
+
authorId: session.userId,
|
|
488
|
+
status: 'draft', // or 'published'
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
\`\`\``
|
|
492
|
+
: ''}
|
|
493
|
+
|
|
494
|
+
### Access Control
|
|
495
|
+
|
|
496
|
+
- Anyone can read ${hasStatus ? 'published posts' : 'posts'}
|
|
497
|
+
- Only authenticated users can create posts
|
|
498
|
+
- Only authors can update their own posts
|
|
499
|
+
- Admins and authors can delete posts`;
|
|
500
|
+
return {
|
|
501
|
+
configUpdates,
|
|
502
|
+
files,
|
|
503
|
+
instructions: nextSteps,
|
|
504
|
+
devGuideSection,
|
|
505
|
+
envVars: useTiptap ? {} : undefined,
|
|
506
|
+
nextSteps,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Generate comments feature (stub - to be implemented)
|
|
511
|
+
*/
|
|
512
|
+
generateComments() {
|
|
513
|
+
return {
|
|
514
|
+
configUpdates: '// Comments feature implementation coming soon',
|
|
515
|
+
files: [],
|
|
516
|
+
instructions: ['Feature implementation in progress'],
|
|
517
|
+
devGuideSection: '## Comments Feature\n\nComing soon...',
|
|
518
|
+
nextSteps: ['Feature implementation in progress'],
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Generate file upload feature (stub - to be implemented)
|
|
523
|
+
*/
|
|
524
|
+
generateFileUpload() {
|
|
525
|
+
return {
|
|
526
|
+
configUpdates: '// File upload feature implementation coming soon',
|
|
527
|
+
files: [],
|
|
528
|
+
instructions: ['Feature implementation in progress'],
|
|
529
|
+
devGuideSection: '## File Upload Feature\n\nComing soon...',
|
|
530
|
+
nextSteps: ['Feature implementation in progress'],
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Generate semantic search feature (stub - to be implemented)
|
|
535
|
+
*/
|
|
536
|
+
generateSemanticSearch() {
|
|
537
|
+
return {
|
|
538
|
+
configUpdates: '// Semantic search feature implementation coming soon',
|
|
539
|
+
files: [],
|
|
540
|
+
instructions: ['Feature implementation in progress'],
|
|
541
|
+
devGuideSection: '## Semantic Search Feature\n\nComing soon...',
|
|
542
|
+
nextSteps: ['Feature implementation in progress'],
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
//# sourceMappingURL=feature-generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-generator.js","sourceRoot":"","sources":["../../../../src/mcp/lib/generators/feature-generator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,OAAO,gBAAgB;IAEjB;IACA;IACA;IAHV,YACU,OAAgB,EAChB,OAAoD,EACpD,eAA4D;QAF5D,YAAO,GAAP,OAAO,CAAS;QAChB,YAAO,GAAP,OAAO,CAA6C;QACpD,oBAAe,GAAf,eAAe,CAA6C;IACnE,CAAC;IAEJ;;OAEG;IACH,QAAQ;QACN,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,CAAA;QAEnC,QAAQ,WAAW,EAAE,CAAC;YACpB,KAAK,gBAAgB;gBACnB,OAAO,IAAI,CAAC,sBAAsB,EAAE,CAAA;YACtC,KAAK,MAAM;gBACT,OAAO,IAAI,CAAC,YAAY,EAAE,CAAA;YAC5B,KAAK,UAAU;gBACb,OAAO,IAAI,CAAC,gBAAgB,EAAE,CAAA;YAChC,KAAK,aAAa;gBAChB,OAAO,IAAI,CAAC,kBAAkB,EAAE,CAAA;YAClC,KAAK,iBAAiB;gBACpB,OAAO,IAAI,CAAC,sBAAsB,EAAE,CAAA;YACtC;gBACE,MAAM,IAAI,KAAK,CAAC,yBAAyB,WAAW,EAAE,CAAC,CAAA;QAC3D,CAAC;IACH,CAAC;IAED;;OAEG;IACK,sBAAsB;QAC5B,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,CAAa,CAAA;QAC5D,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAY,CAAA;QACtD,MAAM,KAAK,GAAG,QAAQ;YACpB,CAAC,CAAE,IAAI,CAAC,eAAe,CAAC,qBAAqB,CAAY;gBACrD,EAAE,KAAK,CAAC,GAAG,CAAC;iBACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC;YAC9C,CAAC,CAAC,IAAI,CAAA;QACR,MAAM,UAAU,GAAI,IAAI,CAAC,OAAO,CAAC,aAAa,CAAc,IAAI,EAAE,CAAA;QAClE,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAY,CAAA;QAEvE,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;QACtF,MAAM,WAAW,GAAG,WAAW,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAA;QAC5D,MAAM,YAAY,GAAG,WAAW,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAA;QAExD,yBAAyB;QACzB,MAAM,MAAM,GAAG,CAAC,mDAAmD,CAAC,CAAA;QAEpE,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAA;QACzE,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;QAE3B,IAAI,QAAQ,IAAI,KAAK,EAAE,CAAC;YACtB,MAAM,CAAC,IAAI,CACT,4BAA4B,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,qBAAqB,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CACpH,CAAA;QACH,CAAC;QAED,IAAI,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAClC,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;QAC/B,CAAC;QACD,IAAI,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAA;QAC/D,CAAC;QACD,IAAI,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YACjC,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;QAC9B,CAAC;QACD,IAAI,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YACpC,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;QACjC,CAAC;QACD,IAAI,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YACnC,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;QAChC,CAAC;QAED,iBAAiB;QACjB,MAAM,aAAa,GAAG;;;;;;;qCAOW,WAAW;QAExC,QAAQ;YACN,CAAC,CAAC;6BACiB,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC;6BACpC,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC;SACxD;YACC,CAAC,CAAC,EACN;QACE,YAAY,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,EAAE;QACnD,iBAAiB,CAAC,CAAC,CAAC,uCAAuC,CAAC,CAAC,CAAC,EAAE;iDACvB,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;UAUjE,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC;;;;;;;;;;;;;IAahC,CAAA;QAEA,kBAAkB;QAClB,MAAM,KAAK,GAAoB,EAAE,CAAA;QAEjC,eAAe;QACf,IAAI,WAAW,IAAI,QAAQ,EAAE,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,sBAAsB;gBAC5B,QAAQ,EAAE,KAAK;gBACf,WAAW,EAAE,0CAA0C;gBACvD,OAAO,EAAE;;;;;;;;YAQL,WAAW,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE;YACrC,QAAQ,CAAC,CAAC,CAAC,WAAW,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE;;;;;;EAM1J;aACK,CAAC,CAAA;QACJ,CAAC;QAED,eAAe;QACf,IAAI,WAAW,EAAE,CAAC;YAChB,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,sBAAsB;gBAC5B,QAAQ,EAAE,KAAK;gBACf,WAAW,EAAE,qCAAqC;gBAClD,OAAO,EAAE;;;;;;;;;;YAUL,iBAAiB,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,EAAE;;;;;EAK7D;aACK,CAAC,CAAA;QACJ,CAAC;QAED,yBAAyB;QACzB,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,uBAAuB;YAC7B,QAAQ,EAAE,YAAY;YACtB,WAAW,EAAE,mCAAmC;YAChD,OAAO,EAAE;;;;;;EAOb,QAAQ;gBACN,CAAC,CAAC;;;;;;;;;;GAUH;gBACC,CAAC,CAAC,EACN;;;;;;;GAOG;SACE,CAAC,CAAA;QAEF,wBAAwB;QACxB,MAAM,OAAO,GAA2B;YACtC,YAAY,EAAE,gDAAgD;YAC9D,kBAAkB,EAAE,wCAAwC;YAC5D,eAAe,EAAE,uBAAuB;SACzC,CAAA;QAED,IAAI,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YACzC,OAAO,CAAC,gBAAgB,GAAG,yBAAyB,CAAA;YACpD,OAAO,CAAC,oBAAoB,GAAG,6BAA6B,CAAA;QAC9D,CAAC;QAED,IAAI,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YACzC,OAAO,CAAC,gBAAgB,GAAG,yBAAyB,CAAA;YACpD,OAAO,CAAC,oBAAoB,GAAG,6BAA6B,CAAA;QAC9D,CAAC;QAED,aAAa;QACb,MAAM,SAAS,GAAG;YAChB,sDAAsD;YACtD,8CAA8C;YAC9C,+CAA+C;YAC/C,QAAQ,CAAC,CAAC,CAAC,+DAA+D,CAAC,CAAC,CAAC,IAAI;YACjF,6CAA6C;YAC7C,4CAA4C;YAC5C,mCAAmC;YACnC,+BAA+B,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,yBAAyB;SAC5F,CAAC,MAAM,CAAC,OAAO,CAAa,CAAA;QAE7B,oBAAoB;QACpB,MAAM,eAAe,GAAG;;;;EAI1B,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;EAC3C,QAAQ,CAAC,CAAC,CAAC,qBAAqB,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;;;;;;;0BAOhC,QAAQ,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE;;;;;;;;cAQhD,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkCvC,CAAA;QAEH,OAAO;YACL,aAAa;YACb,KAAK;YACL,YAAY,EAAE,SAAS;YACvB,eAAe;YACf,OAAO;YACP,SAAS;SACV,CAAA;IACH,CAAC;IAED;;OAEG;IACK,YAAY;QAClB,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAW,CAAA;QAC9D,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAY,CAAA;QACxD,MAAM,QAAQ,GAAI,IAAI,CAAC,OAAO,CAAC,UAAU,CAAc,IAAI,EAAE,CAAA;QAC7D,MAAM,UAAU,GAAI,IAAI,CAAC,OAAO,CAAC,aAAa,CAAc,IAAI,EAAE,CAAA;QAElE,MAAM,SAAS,GAAG,aAAa,KAAK,2BAA2B,CAAA;QAC/D,MAAM,WAAW,GAAG,aAAa,KAAK,UAAU,CAAA;QAEhD,oBAAoB;QACpB,MAAM,MAAM,GAAG;YACb,mDAAmD;YACnD,kDAAkD;SACnD,CAAA;QAED,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAA;QACxE,CAAC;aAAM,IAAI,WAAW,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CACT,sFAAsF,CACvF,CAAA;QACH,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CACT,sFAAsF,CACvF,CAAA;QACH,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAA;QAE1D,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,CAAC,IAAI,CAAC,4EAA4E,CAAC,CAAA;YACzF,MAAM,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAA;QACzC,CAAC;QAED,IAAI,UAAU,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;YAC1C,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAA;QACtC,CAAC;QACD,IAAI,UAAU,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;YAC3C,MAAM,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAA;QACnE,CAAC;QACD,IAAI,UAAU,CAAC,QAAQ,CAAC,mCAAmC,CAAC,EAAE,CAAC;YAC7D,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;YAC/B,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAA;QACvC,CAAC;QACD,IAAI,UAAU,CAAC,QAAQ,CAAC,uBAAuB,CAAC,EAAE,CAAC;YACjD,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAA;QACvC,CAAC;QAED,MAAM,aAAa,GAAG;gDACsB,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW;EAC1E,SAAS,CAAC,CAAC,CAAC,2DAA2D,CAAC,CAAC,CAAC,EAAE;;;;;;UAMpE,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC;;;;;cAKtB,SAAS,CAAC,CAAC,CAAC,2DAA2D,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;UAWhF,SAAS;YACP,CAAC,CAAC;;;;;;WAMH;YACC,CAAC,CAAC,EACN;;;MAIF,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC;YAC7B,CAAC,CAAC;;;;;;QAMF;YACA,CAAC,CAAC,EACN;MAEE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;YACvB,CAAC,CAAC;;;;;QAKF;YACA,CAAC,CAAC,EACN;;IAEA,CAAA;QAEA,MAAM,KAAK,GAAoB,EAAE,CAAA;QAEjC,iBAAiB;QACjB,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,mBAAmB;YACzB,QAAQ,EAAE,KAAK;YACf,WAAW,EAAE,mBAAmB;YAChC,OAAO,EAAE;;;;;;;MAOT,SAAS,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC,CAAC,EAAE;iBACvC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,WAAW;;;;;;;;;;;;;;;cAe1C,UAAU,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,sDAAsD,CAAC,CAAC,CAAC,EAAE;;wCAE1E,SAAS,CAAC,CAAC,CAAC,0CAA0C,CAAC,CAAC,CAAC,uCAAuC;;;;;;;EAOtI;SACG,CAAC,CAAA;QAEF,iBAAiB;QACjB,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,0BAA0B;YAChC,QAAQ,EAAE,KAAK;YACf,WAAW,EAAE,2BAA2B;YACxC,OAAO,EAAE;;;;;;;;;;;;;QAaP,SAAS,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;kCAab,SAAS,CAAC,CAAC,CAAC,0CAA0C,CAAC,CAAC,CAAC,uCAAuC;;QAE1H,SAAS,CAAC,CAAC,CAAC,yFAAyF,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,oFAAoF,CAAC,CAAC,CAAC,4EAA4E;;;EAG/R;SACG,CAAC,CAAA;QAEF,MAAM,SAAS,GAAG;YAChB,sDAAsD;YACtD,sDAAsD;YACtD,SAAS,CAAC,CAAC,CAAC,2DAA2D,CAAC,CAAC,CAAC,IAAI;YAC9E,gDAAgD;YAChD,6CAA6C;YAC7C,uCAAuC;YACvC,6CAA6C;SAC9C,CAAC,MAAM,CAAC,OAAO,CAAa,CAAA;QAE7B,MAAM,eAAe,GAAG;;;;IAIxB,aAAa;EACf,SAAS,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,EAAE;EAC3C,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE;;;;EAKzE,SAAS;YACP,CAAC,CAAC;;;;;;;;;;;;OAYC;YACH,CAAC,CAAC,EACN;;;;oBAIoB,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO;;;sCAGrB,CAAA;QAElC,OAAO;YACL,aAAa;YACb,KAAK;YACL,YAAY,EAAE,SAAS;YACvB,eAAe;YACf,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS;YACnC,SAAS;SACV,CAAA;IACH,CAAC;IAED;;OAEG;IACK,gBAAgB;QACtB,OAAO;YACL,aAAa,EAAE,gDAAgD;YAC/D,KAAK,EAAE,EAAE;YACT,YAAY,EAAE,CAAC,oCAAoC,CAAC;YACpD,eAAe,EAAE,uCAAuC;YACxD,SAAS,EAAE,CAAC,oCAAoC,CAAC;SAClD,CAAA;IACH,CAAC;IAED;;OAEG;IACK,kBAAkB;QACxB,OAAO;YACL,aAAa,EAAE,mDAAmD;YAClE,KAAK,EAAE,EAAE;YACT,YAAY,EAAE,CAAC,oCAAoC,CAAC;YACpD,eAAe,EAAE,0CAA0C;YAC3D,SAAS,EAAE,CAAC,oCAAoC,CAAC;SAClD,CAAA;IACH,CAAC;IAED;;OAEG;IACK,sBAAsB;QAC5B,OAAO;YACL,aAAa,EAAE,uDAAuD;YACtE,KAAK,EAAE,EAAE;YACT,YAAY,EAAE,CAAC,oCAAoC,CAAC;YACpD,eAAe,EAAE,8CAA8C;YAC/D,SAAS,EAAE,CAAC,oCAAoC,CAAC;SAClD,CAAA;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature catalog types for OpenSaaS Stack MCP server
|
|
3
|
+
*/
|
|
4
|
+
export type QuestionType = 'text' | 'textarea' | 'select' | 'multiselect' | 'boolean';
|
|
5
|
+
export interface FeatureQuestion {
|
|
6
|
+
id: string;
|
|
7
|
+
text: string;
|
|
8
|
+
type: QuestionType;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
options?: string[];
|
|
11
|
+
defaultValue?: string | boolean | string[];
|
|
12
|
+
dependsOn?: {
|
|
13
|
+
questionId: string;
|
|
14
|
+
value: string | boolean;
|
|
15
|
+
};
|
|
16
|
+
followUp?: {
|
|
17
|
+
if: string | boolean;
|
|
18
|
+
ask: string;
|
|
19
|
+
type: QuestionType;
|
|
20
|
+
options?: string[];
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export interface Feature {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
description: string;
|
|
27
|
+
includes: string[];
|
|
28
|
+
dependsOn?: string[];
|
|
29
|
+
questions: FeatureQuestion[];
|
|
30
|
+
category: 'authentication' | 'content' | 'storage' | 'search' | 'custom';
|
|
31
|
+
}
|
|
32
|
+
export interface WizardSession {
|
|
33
|
+
id: string;
|
|
34
|
+
featureId: string;
|
|
35
|
+
feature: Feature;
|
|
36
|
+
currentQuestionIndex: number;
|
|
37
|
+
answers: Record<string, string | boolean | string[]>;
|
|
38
|
+
followUpAnswers: Record<string, string | boolean | string[]>;
|
|
39
|
+
isComplete: boolean;
|
|
40
|
+
createdAt: Date;
|
|
41
|
+
updatedAt: Date;
|
|
42
|
+
}
|
|
43
|
+
export interface SessionStorage {
|
|
44
|
+
[sessionId: string]: WizardSession;
|
|
45
|
+
}
|
|
46
|
+
export interface GeneratedFile {
|
|
47
|
+
path: string;
|
|
48
|
+
content: string;
|
|
49
|
+
language: string;
|
|
50
|
+
description: string;
|
|
51
|
+
}
|
|
52
|
+
export interface FeatureImplementation {
|
|
53
|
+
configUpdates: string;
|
|
54
|
+
files: GeneratedFile[];
|
|
55
|
+
instructions: string[];
|
|
56
|
+
devGuideSection: string;
|
|
57
|
+
envVars?: Record<string, string>;
|
|
58
|
+
nextSteps: string[];
|
|
59
|
+
}
|
|
60
|
+
export interface DocumentationLookup {
|
|
61
|
+
topic: string;
|
|
62
|
+
content: string;
|
|
63
|
+
url: string;
|
|
64
|
+
codeExamples: string[];
|
|
65
|
+
relatedTopics: string[];
|
|
66
|
+
}
|
|
67
|
+
export interface ValidationError {
|
|
68
|
+
message: string;
|
|
69
|
+
location: string;
|
|
70
|
+
suggestion: string;
|
|
71
|
+
before?: string;
|
|
72
|
+
after?: string;
|
|
73
|
+
}
|
|
74
|
+
export interface ValidationResult {
|
|
75
|
+
valid: boolean;
|
|
76
|
+
errors: ValidationError[];
|
|
77
|
+
warnings: string[];
|
|
78
|
+
suggestions: string[];
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/mcp/lib/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/mcp/lib/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|