@opensaas/stack-cli 0.3.0 → 0.5.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 +193 -0
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +4 -13
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/migrate.d.ts +9 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +473 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/generator/context.d.ts.map +1 -1
- package/dist/generator/context.js +20 -5
- package/dist/generator/context.js.map +1 -1
- package/dist/generator/index.d.ts +1 -1
- package/dist/generator/index.d.ts.map +1 -1
- package/dist/generator/index.js +1 -1
- package/dist/generator/index.js.map +1 -1
- package/dist/generator/lists.d.ts.map +1 -1
- package/dist/generator/lists.js +33 -1
- package/dist/generator/lists.js.map +1 -1
- 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 +4 -0
- package/dist/generator/prisma.js.map +1 -1
- package/dist/generator/types.d.ts.map +1 -1
- package/dist/generator/types.js +151 -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 +23 -0
- package/dist/mcp/lib/documentation-provider.d.ts.map +1 -1
- package/dist/mcp/lib/documentation-provider.js +471 -0
- package/dist/mcp/lib/documentation-provider.js.map +1 -1
- package/dist/mcp/lib/wizards/migration-wizard.d.ts +80 -0
- package/dist/mcp/lib/wizards/migration-wizard.d.ts.map +1 -0
- package/dist/mcp/lib/wizards/migration-wizard.js +499 -0
- package/dist/mcp/lib/wizards/migration-wizard.js.map +1 -0
- package/dist/mcp/server/index.d.ts.map +1 -1
- package/dist/mcp/server/index.js +103 -0
- package/dist/mcp/server/index.js.map +1 -1
- package/dist/mcp/server/stack-mcp-server.d.ts +85 -0
- package/dist/mcp/server/stack-mcp-server.d.ts.map +1 -1
- package/dist/mcp/server/stack-mcp-server.js +219 -0
- package/dist/mcp/server/stack-mcp-server.js.map +1 -1
- package/dist/migration/generators/migration-generator.d.ts +60 -0
- package/dist/migration/generators/migration-generator.d.ts.map +1 -0
- package/dist/migration/generators/migration-generator.js +510 -0
- package/dist/migration/generators/migration-generator.js.map +1 -0
- package/dist/migration/introspectors/index.d.ts +12 -0
- package/dist/migration/introspectors/index.d.ts.map +1 -0
- package/dist/migration/introspectors/index.js +10 -0
- package/dist/migration/introspectors/index.js.map +1 -0
- package/dist/migration/introspectors/keystone-introspector.d.ts +59 -0
- package/dist/migration/introspectors/keystone-introspector.d.ts.map +1 -0
- package/dist/migration/introspectors/keystone-introspector.js +229 -0
- package/dist/migration/introspectors/keystone-introspector.js.map +1 -0
- package/dist/migration/introspectors/nextjs-introspector.d.ts +59 -0
- package/dist/migration/introspectors/nextjs-introspector.d.ts.map +1 -0
- package/dist/migration/introspectors/nextjs-introspector.js +159 -0
- package/dist/migration/introspectors/nextjs-introspector.js.map +1 -0
- package/dist/migration/introspectors/prisma-introspector.d.ts +45 -0
- package/dist/migration/introspectors/prisma-introspector.d.ts.map +1 -0
- package/dist/migration/introspectors/prisma-introspector.js +190 -0
- package/dist/migration/introspectors/prisma-introspector.js.map +1 -0
- package/dist/migration/types.d.ts +86 -0
- package/dist/migration/types.d.ts.map +1 -0
- package/dist/migration/types.js +5 -0
- package/dist/migration/types.js.map +1 -0
- package/package.json +12 -9
- package/src/commands/__snapshots__/generate.test.ts.snap +92 -21
- package/src/commands/generate.ts +8 -19
- package/src/commands/migrate.ts +534 -0
- package/src/generator/__snapshots__/context.test.ts.snap +60 -15
- package/src/generator/__snapshots__/types.test.ts.snap +689 -95
- package/src/generator/context.test.ts +3 -1
- package/src/generator/context.ts +20 -5
- package/src/generator/index.ts +1 -1
- package/src/generator/lists.ts +39 -1
- package/src/generator/prisma-extensions.ts +159 -0
- package/src/generator/prisma.ts +5 -0
- package/src/generator/types.ts +204 -17
- package/src/index.ts +4 -0
- package/src/mcp/lib/documentation-provider.ts +507 -0
- package/src/mcp/lib/wizards/migration-wizard.ts +584 -0
- package/src/mcp/server/index.ts +121 -0
- package/src/mcp/server/stack-mcp-server.ts +243 -0
- package/src/migration/generators/migration-generator.ts +675 -0
- package/src/migration/introspectors/index.ts +12 -0
- package/src/migration/introspectors/keystone-introspector.ts +296 -0
- package/src/migration/introspectors/nextjs-introspector.ts +209 -0
- package/src/migration/introspectors/prisma-introspector.ts +233 -0
- package/src/migration/types.ts +92 -0
- package/tests/introspectors/keystone-introspector.test.ts +255 -0
- package/tests/introspectors/nextjs-introspector.test.ts +302 -0
- package/tests/introspectors/prisma-introspector.test.ts +268 -0
- package/tests/migration-generator.test.ts +592 -0
- package/tests/migration-wizard.test.ts +442 -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,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prisma Schema Introspector
|
|
3
|
+
*
|
|
4
|
+
* Parses prisma/schema.prisma and extracts structured information
|
|
5
|
+
* about models, fields, relationships, and enums.
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'fs-extra';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
export class PrismaIntrospector {
|
|
10
|
+
/**
|
|
11
|
+
* Introspect a Prisma schema file
|
|
12
|
+
*/
|
|
13
|
+
async introspect(cwd, schemaPath = 'prisma/schema.prisma') {
|
|
14
|
+
const fullPath = path.isAbsolute(schemaPath) ? schemaPath : path.join(cwd, schemaPath);
|
|
15
|
+
if (!(await fs.pathExists(fullPath))) {
|
|
16
|
+
throw new Error(`Schema file not found: ${fullPath}`);
|
|
17
|
+
}
|
|
18
|
+
const schema = await fs.readFile(fullPath, 'utf-8');
|
|
19
|
+
return {
|
|
20
|
+
provider: this.extractProvider(schema),
|
|
21
|
+
models: this.extractModels(schema),
|
|
22
|
+
enums: this.extractEnums(schema),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Extract database provider from datasource block
|
|
27
|
+
*/
|
|
28
|
+
extractProvider(schema) {
|
|
29
|
+
const match = schema.match(/datasource\s+\w+\s*\{[^}]*provider\s*=\s*"(\w+)"/);
|
|
30
|
+
return match ? match[1] : 'unknown';
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Extract all model definitions
|
|
34
|
+
*/
|
|
35
|
+
extractModels(schema) {
|
|
36
|
+
const models = [];
|
|
37
|
+
// Match model blocks
|
|
38
|
+
const modelRegex = /model\s+(\w+)\s*\{([^}]+)\}/g;
|
|
39
|
+
let match;
|
|
40
|
+
while ((match = modelRegex.exec(schema)) !== null) {
|
|
41
|
+
const name = match[1];
|
|
42
|
+
const body = match[2];
|
|
43
|
+
const fields = this.extractFields(body);
|
|
44
|
+
const primaryKey = fields.find((f) => f.isId)?.name || 'id';
|
|
45
|
+
models.push({
|
|
46
|
+
name,
|
|
47
|
+
fields,
|
|
48
|
+
hasRelations: fields.some((f) => f.relation !== undefined),
|
|
49
|
+
primaryKey,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return models;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Extract fields from a model body
|
|
56
|
+
*/
|
|
57
|
+
extractFields(body) {
|
|
58
|
+
const fields = [];
|
|
59
|
+
const lines = body.split('\n');
|
|
60
|
+
for (const line of lines) {
|
|
61
|
+
const trimmed = line.trim();
|
|
62
|
+
// Skip empty lines, comments, and model-level attributes
|
|
63
|
+
if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('@@')) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const field = this.parseFieldLine(trimmed);
|
|
67
|
+
if (field) {
|
|
68
|
+
fields.push(field);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return fields;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Parse a single field line
|
|
75
|
+
*/
|
|
76
|
+
parseFieldLine(line) {
|
|
77
|
+
// Basic field pattern: name Type modifiers attributes
|
|
78
|
+
// Examples:
|
|
79
|
+
// id String @id @default(cuid())
|
|
80
|
+
// title String
|
|
81
|
+
// isActive Boolean? @default(true)
|
|
82
|
+
// posts Post[]
|
|
83
|
+
// author User @relation(fields: [authorId], references: [id])
|
|
84
|
+
// Remove comments
|
|
85
|
+
const withoutComment = line.split('//')[0].trim();
|
|
86
|
+
// Match field name and type
|
|
87
|
+
const fieldMatch = withoutComment.match(/^(\w+)\s+(\w+)(\?)?(\[\])?(.*)$/);
|
|
88
|
+
if (!fieldMatch)
|
|
89
|
+
return null;
|
|
90
|
+
const [, name, rawType, optional, isList, rest] = fieldMatch;
|
|
91
|
+
// Skip if this looks like an index or other non-field line
|
|
92
|
+
if (['@@', 'index', 'unique'].some((kw) => name.startsWith(kw))) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const field = {
|
|
96
|
+
name,
|
|
97
|
+
type: rawType,
|
|
98
|
+
isRequired: !optional,
|
|
99
|
+
isUnique: rest.includes('@unique'),
|
|
100
|
+
isId: rest.includes('@id'),
|
|
101
|
+
isList: !!isList,
|
|
102
|
+
};
|
|
103
|
+
// Extract default value (handle nested parentheses)
|
|
104
|
+
const defaultMatch = rest.match(/@default\(/);
|
|
105
|
+
if (defaultMatch) {
|
|
106
|
+
const startIdx = rest.indexOf('@default(') + '@default('.length;
|
|
107
|
+
let depth = 1;
|
|
108
|
+
let endIdx = startIdx;
|
|
109
|
+
while (depth > 0 && endIdx < rest.length) {
|
|
110
|
+
if (rest[endIdx] === '(')
|
|
111
|
+
depth++;
|
|
112
|
+
else if (rest[endIdx] === ')')
|
|
113
|
+
depth--;
|
|
114
|
+
if (depth > 0)
|
|
115
|
+
endIdx++;
|
|
116
|
+
}
|
|
117
|
+
field.defaultValue = rest.substring(startIdx, endIdx);
|
|
118
|
+
}
|
|
119
|
+
// Extract relation
|
|
120
|
+
const relationMatch = rest.match(/@relation\(([^)]+)\)/);
|
|
121
|
+
if (relationMatch) {
|
|
122
|
+
const relationBody = relationMatch[1];
|
|
123
|
+
// Parse relation parts
|
|
124
|
+
const fieldsMatch = relationBody.match(/fields:\s*\[([^\]]+)\]/);
|
|
125
|
+
const referencesMatch = relationBody.match(/references:\s*\[([^\]]+)\]/);
|
|
126
|
+
const nameMatch = relationBody.match(/name:\s*"([^"]+)"/) || relationBody.match(/"([^"]+)"/);
|
|
127
|
+
field.relation = {
|
|
128
|
+
name: nameMatch ? nameMatch[1] : '',
|
|
129
|
+
model: rawType,
|
|
130
|
+
fields: fieldsMatch ? fieldsMatch[1].split(',').map((f) => f.trim()) : [],
|
|
131
|
+
references: referencesMatch ? referencesMatch[1].split(',').map((r) => r.trim()) : [],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return field;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Extract enum definitions
|
|
138
|
+
*/
|
|
139
|
+
extractEnums(schema) {
|
|
140
|
+
const enums = [];
|
|
141
|
+
// Match enum blocks
|
|
142
|
+
const enumRegex = /enum\s+(\w+)\s*\{([^}]+)\}/g;
|
|
143
|
+
let match;
|
|
144
|
+
while ((match = enumRegex.exec(schema)) !== null) {
|
|
145
|
+
const name = match[1];
|
|
146
|
+
const body = match[2];
|
|
147
|
+
const values = body
|
|
148
|
+
.split('\n')
|
|
149
|
+
.map((line) => line.trim())
|
|
150
|
+
.filter((line) => line && !line.startsWith('//'));
|
|
151
|
+
enums.push({ name, values });
|
|
152
|
+
}
|
|
153
|
+
return enums;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Map Prisma type to OpenSaaS field type
|
|
157
|
+
*/
|
|
158
|
+
mapPrismaTypeToOpenSaas(prismaType) {
|
|
159
|
+
const mappings = {
|
|
160
|
+
String: { type: 'text', import: 'text' },
|
|
161
|
+
Int: { type: 'integer', import: 'integer' },
|
|
162
|
+
Float: { type: 'float', import: 'float' },
|
|
163
|
+
Boolean: { type: 'checkbox', import: 'checkbox' },
|
|
164
|
+
DateTime: { type: 'timestamp', import: 'timestamp' },
|
|
165
|
+
Json: { type: 'json', import: 'json' },
|
|
166
|
+
BigInt: { type: 'text', import: 'text' }, // No native support
|
|
167
|
+
Decimal: { type: 'text', import: 'text' }, // No native support
|
|
168
|
+
Bytes: { type: 'text', import: 'text' }, // No native support
|
|
169
|
+
};
|
|
170
|
+
return mappings[prismaType] || { type: 'text', import: 'text' };
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get warnings for unsupported features
|
|
174
|
+
*/
|
|
175
|
+
getWarnings(schema) {
|
|
176
|
+
const warnings = [];
|
|
177
|
+
// Check for unsupported types
|
|
178
|
+
for (const model of schema.models) {
|
|
179
|
+
for (const field of model.fields) {
|
|
180
|
+
if (['BigInt', 'Decimal', 'Bytes'].includes(field.type)) {
|
|
181
|
+
warnings.push(`Field "${model.name}.${field.name}" uses unsupported type "${field.type}" - will be mapped to text()`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Check for composite IDs
|
|
186
|
+
// This would require checking for @@id in the original schema
|
|
187
|
+
return warnings;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=prisma-introspector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prisma-introspector.js","sourceRoot":"","sources":["../../../src/migration/introspectors/prisma-introspector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration types - Shared types for the migration system
|
|
3
|
+
*/
|
|
4
|
+
export type ProjectType = 'prisma' | 'nextjs' | 'keystone';
|
|
5
|
+
export interface ModelInfo {
|
|
6
|
+
name: string;
|
|
7
|
+
fieldCount: number;
|
|
8
|
+
}
|
|
9
|
+
export interface ProjectAnalysis {
|
|
10
|
+
projectTypes: ProjectType[];
|
|
11
|
+
cwd: string;
|
|
12
|
+
models?: ModelInfo[];
|
|
13
|
+
provider?: string;
|
|
14
|
+
hasAuth?: boolean;
|
|
15
|
+
authLibrary?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface FieldMapping {
|
|
18
|
+
prismaType: string;
|
|
19
|
+
opensaasType: string;
|
|
20
|
+
opensaasImport: string;
|
|
21
|
+
}
|
|
22
|
+
export interface MigrationQuestion {
|
|
23
|
+
id: string;
|
|
24
|
+
text: string;
|
|
25
|
+
type: 'text' | 'select' | 'boolean' | 'multiselect';
|
|
26
|
+
options?: string[];
|
|
27
|
+
defaultValue?: string | boolean | string[];
|
|
28
|
+
required?: boolean;
|
|
29
|
+
dependsOn?: {
|
|
30
|
+
questionId: string;
|
|
31
|
+
value: string | boolean;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export interface MigrationSession {
|
|
35
|
+
id: string;
|
|
36
|
+
projectType: ProjectType;
|
|
37
|
+
analysis: ProjectAnalysis;
|
|
38
|
+
currentQuestionIndex: number;
|
|
39
|
+
answers: Record<string, string | boolean | string[]>;
|
|
40
|
+
generatedConfig?: string;
|
|
41
|
+
isComplete: boolean;
|
|
42
|
+
createdAt: Date;
|
|
43
|
+
updatedAt: Date;
|
|
44
|
+
}
|
|
45
|
+
export interface MigrationOutput {
|
|
46
|
+
configContent: string;
|
|
47
|
+
dependencies: string[];
|
|
48
|
+
files: Array<{
|
|
49
|
+
path: string;
|
|
50
|
+
content: string;
|
|
51
|
+
language: string;
|
|
52
|
+
description: string;
|
|
53
|
+
}>;
|
|
54
|
+
steps: string[];
|
|
55
|
+
warnings: string[];
|
|
56
|
+
}
|
|
57
|
+
export interface IntrospectedModel {
|
|
58
|
+
name: string;
|
|
59
|
+
fields: IntrospectedField[];
|
|
60
|
+
hasRelations: boolean;
|
|
61
|
+
primaryKey: string;
|
|
62
|
+
}
|
|
63
|
+
export interface IntrospectedField {
|
|
64
|
+
name: string;
|
|
65
|
+
type: string;
|
|
66
|
+
isRequired: boolean;
|
|
67
|
+
isUnique: boolean;
|
|
68
|
+
isId: boolean;
|
|
69
|
+
isList: boolean;
|
|
70
|
+
defaultValue?: string;
|
|
71
|
+
relation?: {
|
|
72
|
+
name: string;
|
|
73
|
+
model: string;
|
|
74
|
+
fields: string[];
|
|
75
|
+
references: string[];
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export interface IntrospectedSchema {
|
|
79
|
+
provider: string;
|
|
80
|
+
models: IntrospectedModel[];
|
|
81
|
+
enums: Array<{
|
|
82
|
+
name: string;
|
|
83
|
+
values: string[];
|
|
84
|
+
}>;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/migration/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAA;AAE1D,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,WAAW,EAAE,CAAA;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,SAAS,EAAE,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,aAAa,CAAA;IACnD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAA;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE;QACV,UAAU,EAAE,MAAM,CAAA;QAClB,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;KACxB,CAAA;CACF;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,WAAW,CAAA;IACxB,QAAQ,EAAE,eAAe,CAAA;IACzB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC,CAAA;IACpD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,UAAU,EAAE,OAAO,CAAA;IACnB,SAAS,EAAE,IAAI,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,KAAK,EAAE,KAAK,CAAC;QACX,IAAI,EAAE,MAAM,CAAA;QACZ,OAAO,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,MAAM,CAAA;QAChB,WAAW,EAAE,MAAM,CAAA;KACpB,CAAC,CAAA;IACF,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,iBAAiB,EAAE,CAAA;IAC3B,YAAY,EAAE,OAAO,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,OAAO,CAAA;IACnB,QAAQ,EAAE,OAAO,CAAA;IACjB,IAAI,EAAE,OAAO,CAAA;IACb,MAAM,EAAE,OAAO,CAAA;IACf,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE;QACT,IAAI,EAAE,MAAM,CAAA;QACZ,KAAK,EAAE,MAAM,CAAA;QACb,MAAM,EAAE,MAAM,EAAE,CAAA;QAChB,UAAU,EAAE,MAAM,EAAE,CAAA;KACrB,CAAA;CACF;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,iBAAiB,EAAE,CAAA;IAC3B,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;CACjD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/migration/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opensaas/stack-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "CLI tools for OpenSaas Stack",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -36,22 +36,25 @@
|
|
|
36
36
|
"url": "https://github.com/OpenSaasAU/stack/issues"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.24.3",
|
|
40
40
|
"chalk": "^5.6.2",
|
|
41
|
-
"chokidar": "^
|
|
42
|
-
"commander": "^14.0.
|
|
41
|
+
"chokidar": "^5.0.0",
|
|
42
|
+
"commander": "^14.0.2",
|
|
43
|
+
"fs-extra": "^11.3.2",
|
|
44
|
+
"glob": "^13.0.0",
|
|
43
45
|
"jiti": "^2.6.1",
|
|
44
46
|
"ora": "^9.0.0",
|
|
45
47
|
"prompts": "^2.4.2",
|
|
46
|
-
"zod": "^4.1.
|
|
47
|
-
"@opensaas/stack-core": "0.
|
|
48
|
+
"zod": "^4.1.13",
|
|
49
|
+
"@opensaas/stack-core": "0.5.0"
|
|
48
50
|
},
|
|
49
51
|
"devDependencies": {
|
|
50
|
-
"@types/
|
|
52
|
+
"@types/fs-extra": "^11.0.4",
|
|
53
|
+
"@types/node": "^24.10.1",
|
|
51
54
|
"@types/prompts": "^2.4.9",
|
|
52
|
-
"@vitest/coverage-v8": "^4.0.
|
|
55
|
+
"@vitest/coverage-v8": "^4.0.15",
|
|
53
56
|
"typescript": "^5.9.3",
|
|
54
|
-
"vitest": "^4.0.
|
|
57
|
+
"vitest": "^4.0.15"
|
|
55
58
|
},
|
|
56
59
|
"scripts": {
|
|
57
60
|
"build": "tsc",
|
|
@@ -14,6 +14,7 @@ import { getContext as getOpensaasContext } from '@opensaas/stack-core'
|
|
|
14
14
|
import type { Session as OpensaasSession, OpenSaasConfig } from '@opensaas/stack-core'
|
|
15
15
|
import { PrismaClient } from './prisma-client/client'
|
|
16
16
|
import type { Context } from './types'
|
|
17
|
+
import { prismaExtensions } from './prisma-extensions'
|
|
17
18
|
import configOrPromise from '../opensaas.config'
|
|
18
19
|
|
|
19
20
|
// Resolve config if it's a Promise (when plugins are present)
|
|
@@ -21,15 +22,29 @@ const configPromise = Promise.resolve(configOrPromise)
|
|
|
21
22
|
let resolvedConfig: OpenSaasConfig | null = null
|
|
22
23
|
|
|
23
24
|
// Internal Prisma singleton - managed automatically
|
|
24
|
-
const globalForPrisma = globalThis as unknown as { prisma:
|
|
25
|
-
let prisma:
|
|
25
|
+
const globalForPrisma = globalThis as unknown as { prisma: ReturnType<typeof createExtendedPrisma> | null }
|
|
26
|
+
let prisma: ReturnType<typeof createExtendedPrisma> | null = null
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create Prisma client with result extensions
|
|
30
|
+
*/
|
|
31
|
+
function createExtendedPrisma(basePrisma: PrismaClient) {
|
|
32
|
+
// Check if there are any extensions to apply
|
|
33
|
+
if (Object.keys(prismaExtensions).length === 0) {
|
|
34
|
+
return basePrisma
|
|
35
|
+
}
|
|
36
|
+
// Apply result extensions
|
|
37
|
+
return basePrisma.$extends(prismaExtensions)
|
|
38
|
+
}
|
|
26
39
|
|
|
27
40
|
async function getPrisma() {
|
|
28
41
|
if (!prisma) {
|
|
29
42
|
if (!resolvedConfig) {
|
|
30
43
|
resolvedConfig = await configPromise
|
|
31
44
|
}
|
|
32
|
-
|
|
45
|
+
const basePrisma = resolvedConfig.db.prismaClientConstructor!(PrismaClient)
|
|
46
|
+
const extendedPrisma = createExtendedPrisma(basePrisma)
|
|
47
|
+
prisma = globalForPrisma.prisma ?? extendedPrisma
|
|
33
48
|
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
|
34
49
|
}
|
|
35
50
|
return prisma
|
|
@@ -84,7 +99,7 @@ const storage = {
|
|
|
84
99
|
export async function getContext<TSession extends OpensaasSession = OpensaasSession>(session?: TSession): Promise<Context<TSession>> {
|
|
85
100
|
const config = await getConfig()
|
|
86
101
|
const prismaClient = await getPrisma()
|
|
87
|
-
return getOpensaasContext(config, prismaClient, session ?? null, storage) as Context<TSession>
|
|
102
|
+
return getOpensaasContext(config, prismaClient, session ?? null, storage) as unknown as Context<TSession>
|
|
88
103
|
}
|
|
89
104
|
|
|
90
105
|
/**
|
|
@@ -94,7 +109,7 @@ export async function getContext<TSession extends OpensaasSession = OpensaasSess
|
|
|
94
109
|
export const rawOpensaasContext = (async () => {
|
|
95
110
|
const config = await getConfig()
|
|
96
111
|
const prismaClient = await getPrisma()
|
|
97
|
-
return getOpensaasContext(config, prismaClient, null, storage)
|
|
112
|
+
return getOpensaasContext(config, prismaClient, null, storage) as unknown as Context
|
|
98
113
|
})()
|
|
99
114
|
|
|
100
115
|
/**
|
|
@@ -131,17 +146,35 @@ exports[`Generate Command Integration > Generator Integration > should generate
|
|
|
131
146
|
* DO NOT EDIT - This file is automatically generated
|
|
132
147
|
*/
|
|
133
148
|
|
|
134
|
-
import type { Session as OpensaasSession, StorageUtils, ServerActionProps, AccessControlledDB } from '@opensaas/stack-core'
|
|
149
|
+
import type { Session as OpensaasSession, StorageUtils, ServerActionProps, AccessControlledDB, AccessContext } from '@opensaas/stack-core'
|
|
135
150
|
import type { PrismaClient, Prisma } from './prisma-client/client'
|
|
136
151
|
import type { PluginServices } from './plugin-types'
|
|
137
152
|
|
|
138
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Virtual fields for User - computed fields not in database
|
|
155
|
+
* These are added to query results via resolveOutput hooks
|
|
156
|
+
*/
|
|
157
|
+
export type UserVirtualFields = {
|
|
158
|
+
// No virtual fields defined
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Transformed fields for User - fields with resultExtension transformations
|
|
163
|
+
* These override Prisma's base types with transformed types via result extensions
|
|
164
|
+
*/
|
|
165
|
+
export type UserTransformedFields = {
|
|
166
|
+
// No transformed fields defined
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export type UserOutput = {
|
|
139
170
|
id: string
|
|
140
171
|
name: string
|
|
141
172
|
email: string
|
|
142
173
|
createdAt: Date
|
|
143
174
|
updatedAt: Date
|
|
144
|
-
}
|
|
175
|
+
} & UserVirtualFields
|
|
176
|
+
|
|
177
|
+
export type User = UserOutput
|
|
145
178
|
|
|
146
179
|
export type UserCreateInput = {
|
|
147
180
|
name: string
|
|
@@ -202,15 +235,43 @@ export type UserHooks = {
|
|
|
202
235
|
}) => Promise<void>
|
|
203
236
|
}
|
|
204
237
|
|
|
205
|
-
|
|
206
|
-
|
|
238
|
+
/**
|
|
239
|
+
* Custom DB type that uses Prisma's conditional types with virtual and transformed field support
|
|
240
|
+
* Types change based on select/include - relationships only present when explicitly included
|
|
241
|
+
* Virtual fields and transformed fields are added to the base model type
|
|
242
|
+
*/
|
|
243
|
+
export type CustomDB = Omit<AccessControlledDB<PrismaClient>,
|
|
244
|
+
'user'
|
|
245
|
+
> & {
|
|
246
|
+
user: {
|
|
247
|
+
findUnique: <T extends Prisma.UserFindUniqueArgs>(
|
|
248
|
+
args: Prisma.SelectSubset<T, Prisma.UserFindUniqueArgs>
|
|
249
|
+
) => Promise<(Omit<Prisma.UserGetPayload<T>, keyof UserTransformedFields> & UserTransformedFields & UserVirtualFields) | null>
|
|
250
|
+
findMany: <T extends Prisma.UserFindManyArgs>(
|
|
251
|
+
args?: Prisma.SelectSubset<T, Prisma.UserFindManyArgs>
|
|
252
|
+
) => Promise<Array<Omit<Prisma.UserGetPayload<T>, keyof UserTransformedFields> & UserTransformedFields & UserVirtualFields>>
|
|
253
|
+
create: <T extends Prisma.UserCreateArgs>(
|
|
254
|
+
args: Prisma.SelectSubset<T, Prisma.UserCreateArgs>
|
|
255
|
+
) => Promise<Omit<Prisma.UserGetPayload<T>, keyof UserTransformedFields> & UserTransformedFields & UserVirtualFields>
|
|
256
|
+
update: <T extends Prisma.UserUpdateArgs>(
|
|
257
|
+
args: Prisma.SelectSubset<T, Prisma.UserUpdateArgs>
|
|
258
|
+
) => Promise<(Omit<Prisma.UserGetPayload<T>, keyof UserTransformedFields> & UserTransformedFields & UserVirtualFields) | null>
|
|
259
|
+
delete: <T extends Prisma.UserDeleteArgs>(
|
|
260
|
+
args: Prisma.SelectSubset<T, Prisma.UserDeleteArgs>
|
|
261
|
+
) => Promise<(Omit<Prisma.UserGetPayload<T>, keyof UserTransformedFields> & UserTransformedFields & UserVirtualFields) | null>
|
|
262
|
+
count: (args?: Prisma.UserCountArgs) => Promise<number>
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Context type compatible with AccessContext but with CustomDB for virtual field typing
|
|
268
|
+
* Extends AccessContext and overrides db property to include virtual fields in output types
|
|
269
|
+
*/
|
|
270
|
+
export type Context<TSession extends OpensaasSession = OpensaasSession> = Omit<AccessContext<PrismaClient>, 'db' | 'session'> & {
|
|
271
|
+
db: CustomDB
|
|
207
272
|
session: TSession
|
|
208
|
-
prisma: PrismaClient
|
|
209
|
-
storage: StorageUtils
|
|
210
|
-
plugins: PluginServices
|
|
211
273
|
serverAction: (props: ServerActionProps) => Promise<unknown>
|
|
212
274
|
sudo: () => Context<TSession>
|
|
213
|
-
_isSudo: boolean
|
|
214
275
|
}"
|
|
215
276
|
`;
|
|
216
277
|
|
|
@@ -287,19 +348,29 @@ exports[`Generate Command Integration > Generator Integration > should handle em
|
|
|
287
348
|
* DO NOT EDIT - This file is automatically generated
|
|
288
349
|
*/
|
|
289
350
|
|
|
290
|
-
import type { Session as OpensaasSession, StorageUtils, ServerActionProps, AccessControlledDB } from '@opensaas/stack-core'
|
|
351
|
+
import type { Session as OpensaasSession, StorageUtils, ServerActionProps, AccessControlledDB, AccessContext } from '@opensaas/stack-core'
|
|
291
352
|
import type { PrismaClient, Prisma } from './prisma-client/client'
|
|
292
353
|
import type { PluginServices } from './plugin-types'
|
|
293
354
|
|
|
294
|
-
|
|
295
|
-
|
|
355
|
+
/**
|
|
356
|
+
* Custom DB type that uses Prisma's conditional types with virtual and transformed field support
|
|
357
|
+
* Types change based on select/include - relationships only present when explicitly included
|
|
358
|
+
* Virtual fields and transformed fields are added to the base model type
|
|
359
|
+
*/
|
|
360
|
+
export type CustomDB = Omit<AccessControlledDB<PrismaClient>,
|
|
361
|
+
|
|
362
|
+
> & {
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Context type compatible with AccessContext but with CustomDB for virtual field typing
|
|
367
|
+
* Extends AccessContext and overrides db property to include virtual fields in output types
|
|
368
|
+
*/
|
|
369
|
+
export type Context<TSession extends OpensaasSession = OpensaasSession> = Omit<AccessContext<PrismaClient>, 'db' | 'session'> & {
|
|
370
|
+
db: CustomDB
|
|
296
371
|
session: TSession
|
|
297
|
-
prisma: PrismaClient
|
|
298
|
-
storage: StorageUtils
|
|
299
|
-
plugins: PluginServices
|
|
300
372
|
serverAction: (props: ServerActionProps) => Promise<unknown>
|
|
301
373
|
sudo: () => Context<TSession>
|
|
302
|
-
_isSudo: boolean
|
|
303
374
|
}"
|
|
304
375
|
`;
|
|
305
376
|
|
package/src/commands/generate.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
writeLists,
|
|
12
12
|
writeContext,
|
|
13
13
|
writePluginTypes,
|
|
14
|
-
|
|
14
|
+
writePrismaExtensions,
|
|
15
15
|
} from '../generator/index.js'
|
|
16
16
|
import { OpenSaasConfig } from '@opensaas/stack-core'
|
|
17
17
|
|
|
@@ -58,9 +58,8 @@ export async function generateCommand() {
|
|
|
58
58
|
|
|
59
59
|
try {
|
|
60
60
|
// Import plugin engine (avoid circular dependency)
|
|
61
|
-
const { executeBeforeGenerateHooks } =
|
|
62
|
-
'@opensaas/stack-core/config/plugin-engine'
|
|
63
|
-
)
|
|
61
|
+
const { executeBeforeGenerateHooks } =
|
|
62
|
+
await import('@opensaas/stack-core/config/plugin-engine')
|
|
64
63
|
config = await executeBeforeGenerateHooks(config)
|
|
65
64
|
pluginSpinner.succeed(chalk.green('Plugin beforeGenerate hooks complete'))
|
|
66
65
|
} catch (err) {
|
|
@@ -78,6 +77,7 @@ export async function generateCommand() {
|
|
|
78
77
|
const listsPath = path.join(cwd, '.opensaas', 'lists.ts')
|
|
79
78
|
const contextPath = path.join(cwd, '.opensaas', 'context.ts')
|
|
80
79
|
const pluginTypesPath = path.join(cwd, '.opensaas', 'plugin-types.ts')
|
|
80
|
+
const prismaExtensionsPath = path.join(cwd, '.opensaas', 'prisma-extensions.ts')
|
|
81
81
|
|
|
82
82
|
writePrismaSchema(config, prismaSchemaPath)
|
|
83
83
|
writePrismaConfig(config, prismaConfigPath)
|
|
@@ -85,6 +85,7 @@ export async function generateCommand() {
|
|
|
85
85
|
writeLists(config, listsPath)
|
|
86
86
|
writeContext(config, contextPath)
|
|
87
87
|
writePluginTypes(config, pluginTypesPath)
|
|
88
|
+
writePrismaExtensions(config, prismaExtensionsPath)
|
|
88
89
|
|
|
89
90
|
generatorSpinner.succeed(chalk.green('Schema generation complete'))
|
|
90
91
|
console.log(chalk.green('✅ Prisma schema generated'))
|
|
@@ -93,6 +94,7 @@ export async function generateCommand() {
|
|
|
93
94
|
console.log(chalk.green('✅ Lists namespace generated'))
|
|
94
95
|
console.log(chalk.green('✅ Context factory generated'))
|
|
95
96
|
console.log(chalk.green('✅ Plugin types generated'))
|
|
97
|
+
console.log(chalk.green('✅ Prisma extensions generated'))
|
|
96
98
|
|
|
97
99
|
// Execute afterGenerate hooks if plugins are present
|
|
98
100
|
if (config.plugins && config.plugins.length > 0) {
|
|
@@ -107,9 +109,8 @@ export async function generateCommand() {
|
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
// Execute afterGenerate hooks
|
|
110
|
-
const { executeAfterGenerateHooks } =
|
|
111
|
-
'@opensaas/stack-core/config/plugin-engine'
|
|
112
|
-
)
|
|
112
|
+
const { executeAfterGenerateHooks } =
|
|
113
|
+
await import('@opensaas/stack-core/config/plugin-engine')
|
|
113
114
|
const modifiedFiles = await executeAfterGenerateHooks(config, generatedFiles)
|
|
114
115
|
|
|
115
116
|
// Write back modified files
|
|
@@ -165,18 +166,6 @@ export async function generateCommand() {
|
|
|
165
166
|
process.exit(1)
|
|
166
167
|
}
|
|
167
168
|
|
|
168
|
-
// Patch Prisma types with field transformations
|
|
169
|
-
const patchSpinner = ora('Patching Prisma types...').start()
|
|
170
|
-
try {
|
|
171
|
-
patchPrismaTypes(config, cwd)
|
|
172
|
-
patchSpinner.succeed(chalk.green('Type patching complete'))
|
|
173
|
-
} catch (err) {
|
|
174
|
-
patchSpinner.fail(chalk.red('Failed to patch types'))
|
|
175
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
176
|
-
console.error(chalk.red('\n❌ Error:'), message)
|
|
177
|
-
process.exit(1)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
169
|
console.log(chalk.bold('\n✨ Generation complete!\n'))
|
|
181
170
|
console.log(chalk.gray('Next steps:'))
|
|
182
171
|
console.log(chalk.gray(' 1. Run: npx prisma db push'))
|