baasix 0.1.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 +355 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +2521 -0
- package/package.json +56 -0
- package/src/commands/extension.ts +447 -0
- package/src/commands/generate.ts +485 -0
- package/src/commands/init.ts +1409 -0
- package/src/commands/migrate.ts +573 -0
- package/src/index.ts +39 -0
- package/src/utils/api-client.ts +121 -0
- package/src/utils/get-config.ts +69 -0
- package/src/utils/get-package-info.ts +12 -0
- package/src/utils/package-manager.ts +62 -0
- package/tsconfig.json +19 -0
- package/tsup.config.ts +16 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
cancel,
|
|
6
|
+
confirm,
|
|
7
|
+
intro,
|
|
8
|
+
isCancel,
|
|
9
|
+
log,
|
|
10
|
+
outro,
|
|
11
|
+
select,
|
|
12
|
+
spinner,
|
|
13
|
+
text,
|
|
14
|
+
} from "@clack/prompts";
|
|
15
|
+
import chalk from "chalk";
|
|
16
|
+
import { Command } from "commander";
|
|
17
|
+
import { format as prettierFormat } from "prettier";
|
|
18
|
+
import { getConfig } from "../utils/get-config.js";
|
|
19
|
+
import { fetchSchemas, type SchemaInfo, type FieldDefinition } from "../utils/api-client.js";
|
|
20
|
+
|
|
21
|
+
type GenerateTarget = "types" | "sdk-types" | "schema-json";
|
|
22
|
+
|
|
23
|
+
interface GenerateOptions {
|
|
24
|
+
cwd: string;
|
|
25
|
+
output?: string;
|
|
26
|
+
target?: GenerateTarget;
|
|
27
|
+
url?: string;
|
|
28
|
+
yes?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function generateAction(opts: GenerateOptions) {
|
|
32
|
+
const cwd = path.resolve(opts.cwd);
|
|
33
|
+
|
|
34
|
+
intro(chalk.bgBlue.black(" Baasix Type Generator "));
|
|
35
|
+
|
|
36
|
+
// Load config
|
|
37
|
+
const config = await getConfig(cwd);
|
|
38
|
+
if (!config && !opts.url) {
|
|
39
|
+
log.error("No Baasix configuration found. Create a .env file with BAASIX_URL or use --url flag.");
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const baasixUrl = opts.url || config?.url || "http://localhost:8056";
|
|
44
|
+
|
|
45
|
+
// Select generation target
|
|
46
|
+
let target = opts.target;
|
|
47
|
+
if (!target) {
|
|
48
|
+
const result = await select({
|
|
49
|
+
message: "What do you want to generate?",
|
|
50
|
+
options: [
|
|
51
|
+
{
|
|
52
|
+
value: "types",
|
|
53
|
+
label: "TypeScript Types",
|
|
54
|
+
hint: "Generate types for all collections",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
value: "sdk-types",
|
|
58
|
+
label: "SDK Collection Types",
|
|
59
|
+
hint: "Generate typed SDK helpers for collections",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
value: "schema-json",
|
|
63
|
+
label: "Schema JSON",
|
|
64
|
+
hint: "Export all schemas as JSON",
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (isCancel(result)) {
|
|
70
|
+
cancel("Operation cancelled");
|
|
71
|
+
process.exit(0);
|
|
72
|
+
}
|
|
73
|
+
target = result as GenerateTarget;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Get output path
|
|
77
|
+
let outputPath = opts.output;
|
|
78
|
+
if (!outputPath) {
|
|
79
|
+
const defaultPath = target === "schema-json" ? "schemas.json" : "baasix.d.ts";
|
|
80
|
+
const result = await text({
|
|
81
|
+
message: "Output file path:",
|
|
82
|
+
placeholder: defaultPath,
|
|
83
|
+
defaultValue: defaultPath,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (isCancel(result)) {
|
|
87
|
+
cancel("Operation cancelled");
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
outputPath = result as string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const s = spinner();
|
|
94
|
+
s.start("Fetching schemas from Baasix...");
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
// Fetch schemas from API
|
|
98
|
+
const schemas = await fetchSchemas({
|
|
99
|
+
url: baasixUrl,
|
|
100
|
+
email: config?.email,
|
|
101
|
+
password: config?.password,
|
|
102
|
+
token: config?.token,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (!schemas || schemas.length === 0) {
|
|
106
|
+
s.stop("No schemas found");
|
|
107
|
+
log.warn("No schemas found in your Baasix instance.");
|
|
108
|
+
process.exit(0);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
s.message(`Found ${schemas.length} schemas`);
|
|
112
|
+
|
|
113
|
+
let output: string;
|
|
114
|
+
|
|
115
|
+
if (target === "types") {
|
|
116
|
+
output = generateTypeScriptTypes(schemas);
|
|
117
|
+
} else if (target === "sdk-types") {
|
|
118
|
+
output = generateSDKTypes(schemas);
|
|
119
|
+
} else {
|
|
120
|
+
output = JSON.stringify(schemas, null, 2);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Format with prettier if TypeScript
|
|
124
|
+
if (target !== "schema-json") {
|
|
125
|
+
try {
|
|
126
|
+
output = await prettierFormat(output, {
|
|
127
|
+
parser: "typescript",
|
|
128
|
+
printWidth: 100,
|
|
129
|
+
tabWidth: 2,
|
|
130
|
+
singleQuote: true,
|
|
131
|
+
});
|
|
132
|
+
} catch {
|
|
133
|
+
// Ignore prettier errors
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Check if file exists
|
|
138
|
+
const fullOutputPath = path.resolve(cwd, outputPath);
|
|
139
|
+
if (existsSync(fullOutputPath) && !opts.yes) {
|
|
140
|
+
s.stop("File already exists");
|
|
141
|
+
const overwrite = await confirm({
|
|
142
|
+
message: `File ${outputPath} already exists. Overwrite?`,
|
|
143
|
+
initialValue: true,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (isCancel(overwrite) || !overwrite) {
|
|
147
|
+
cancel("Operation cancelled");
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
150
|
+
s.start("Writing file...");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Ensure directory exists
|
|
154
|
+
const outputDir = path.dirname(fullOutputPath);
|
|
155
|
+
if (!existsSync(outputDir)) {
|
|
156
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
await fs.writeFile(fullOutputPath, output);
|
|
160
|
+
|
|
161
|
+
s.stop("Types generated successfully");
|
|
162
|
+
|
|
163
|
+
outro(chalk.green(`✨ Generated ${outputPath}`));
|
|
164
|
+
|
|
165
|
+
// Print usage info
|
|
166
|
+
if (target === "types" || target === "sdk-types") {
|
|
167
|
+
console.log();
|
|
168
|
+
console.log(chalk.bold("Usage:"));
|
|
169
|
+
console.log(` ${chalk.dim("// Import types in your TypeScript files")}`);
|
|
170
|
+
console.log(` ${chalk.cyan(`import type { Products, Users } from "./${outputPath.replace(/\.d\.ts$/, "")}";`)}`);
|
|
171
|
+
console.log();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
} catch (error) {
|
|
175
|
+
s.stop("Failed to generate types");
|
|
176
|
+
if (error instanceof Error) {
|
|
177
|
+
log.error(error.message);
|
|
178
|
+
} else {
|
|
179
|
+
log.error("Unknown error occurred");
|
|
180
|
+
}
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function fieldTypeToTS(field: FieldDefinition, allSchemas?: SchemaInfo[]): { type: string; jsdoc?: string } {
|
|
186
|
+
// Handle relation fields
|
|
187
|
+
if (field.relType && field.target) {
|
|
188
|
+
const targetType = toPascalCase(field.target);
|
|
189
|
+
|
|
190
|
+
// Check if it's a system collection (baasix_*)
|
|
191
|
+
const isSystemCollection = field.target.startsWith("baasix_");
|
|
192
|
+
|
|
193
|
+
// For HasMany and BelongsToMany, return array type
|
|
194
|
+
if (field.relType === "HasMany" || field.relType === "BelongsToMany") {
|
|
195
|
+
return { type: `${targetType}[] | null` };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// For BelongsTo and HasOne, return single type
|
|
199
|
+
return { type: `${targetType} | null` };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const type = field.type?.toUpperCase(); // Normalize to uppercase for comparison
|
|
203
|
+
|
|
204
|
+
// Handle nullable
|
|
205
|
+
const nullable = field.allowNull !== false;
|
|
206
|
+
const nullSuffix = nullable ? " | null" : "";
|
|
207
|
+
|
|
208
|
+
// Build JSDoc comment for validations
|
|
209
|
+
const jsdocParts: string[] = [];
|
|
210
|
+
|
|
211
|
+
if (field.validate) {
|
|
212
|
+
if (field.validate.min !== undefined) jsdocParts.push(`@min ${field.validate.min}`);
|
|
213
|
+
if (field.validate.max !== undefined) jsdocParts.push(`@max ${field.validate.max}`);
|
|
214
|
+
if (field.validate.len) jsdocParts.push(`@length ${field.validate.len[0]}-${field.validate.len[1]}`);
|
|
215
|
+
if (field.validate.isEmail) jsdocParts.push(`@format email`);
|
|
216
|
+
if (field.validate.isUrl) jsdocParts.push(`@format url`);
|
|
217
|
+
if (field.validate.isIP) jsdocParts.push(`@format ip`);
|
|
218
|
+
if (field.validate.isUUID) jsdocParts.push(`@format uuid`);
|
|
219
|
+
if (field.validate.regex) jsdocParts.push(`@pattern ${field.validate.regex}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Add length info for strings
|
|
223
|
+
if (field.values && typeof field.values === 'object' && !Array.isArray(field.values)) {
|
|
224
|
+
const vals = field.values as Record<string, unknown>;
|
|
225
|
+
if (vals.length) jsdocParts.push(`@maxLength ${vals.length}`);
|
|
226
|
+
if (vals.precision && vals.scale) jsdocParts.push(`@precision ${vals.precision},${vals.scale}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const jsdoc = jsdocParts.length > 0 ? jsdocParts.join(' ') : undefined;
|
|
230
|
+
|
|
231
|
+
switch (type) {
|
|
232
|
+
case "STRING":
|
|
233
|
+
case "TEXT":
|
|
234
|
+
case "UUID":
|
|
235
|
+
case "SUID":
|
|
236
|
+
return { type: `string${nullSuffix}`, jsdoc };
|
|
237
|
+
|
|
238
|
+
case "INTEGER":
|
|
239
|
+
case "BIGINT":
|
|
240
|
+
case "FLOAT":
|
|
241
|
+
case "REAL":
|
|
242
|
+
case "DOUBLE":
|
|
243
|
+
case "DECIMAL":
|
|
244
|
+
return { type: `number${nullSuffix}`, jsdoc };
|
|
245
|
+
|
|
246
|
+
case "BOOLEAN":
|
|
247
|
+
return { type: `boolean${nullSuffix}`, jsdoc };
|
|
248
|
+
|
|
249
|
+
case "DATE":
|
|
250
|
+
case "DATETIME":
|
|
251
|
+
case "TIME":
|
|
252
|
+
return { type: `string${nullSuffix}`, jsdoc }; // ISO date strings
|
|
253
|
+
|
|
254
|
+
case "JSON":
|
|
255
|
+
case "JSONB":
|
|
256
|
+
return { type: `Record<string, unknown>${nullSuffix}`, jsdoc };
|
|
257
|
+
|
|
258
|
+
case "ARRAY": {
|
|
259
|
+
const vals = field.values as Record<string, unknown> | undefined;
|
|
260
|
+
const arrayType = vals?.type as string || "unknown";
|
|
261
|
+
const innerType = arrayType.toUpperCase() === "STRING" ? "string" :
|
|
262
|
+
arrayType.toUpperCase() === "INTEGER" ? "number" :
|
|
263
|
+
arrayType.toUpperCase() === "BOOLEAN" ? "boolean" : "unknown";
|
|
264
|
+
return { type: `${innerType}[]${nullSuffix}`, jsdoc };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
case "ENUM": {
|
|
268
|
+
// Enum values can be directly in field.values as array or in field.values.values
|
|
269
|
+
let enumValues: string[] | undefined;
|
|
270
|
+
|
|
271
|
+
if (Array.isArray(field.values)) {
|
|
272
|
+
enumValues = field.values as string[];
|
|
273
|
+
} else if (field.values && typeof field.values === 'object') {
|
|
274
|
+
const vals = field.values as Record<string, unknown>;
|
|
275
|
+
if (Array.isArray(vals.values)) {
|
|
276
|
+
enumValues = vals.values as string[];
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (enumValues && enumValues.length > 0) {
|
|
281
|
+
const enumType = enumValues.map((v: string) => `"${v}"`).join(" | ");
|
|
282
|
+
return { type: `(${enumType})${nullSuffix}`, jsdoc };
|
|
283
|
+
}
|
|
284
|
+
return { type: `string${nullSuffix}`, jsdoc };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
case "GEOMETRY":
|
|
288
|
+
case "POINT":
|
|
289
|
+
case "LINESTRING":
|
|
290
|
+
case "POLYGON":
|
|
291
|
+
return { type: `GeoJSON.Geometry${nullSuffix}`, jsdoc };
|
|
292
|
+
|
|
293
|
+
default:
|
|
294
|
+
return { type: `unknown${nullSuffix}`, jsdoc };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function toPascalCase(str: string): string {
|
|
299
|
+
return str
|
|
300
|
+
.split(/[-_]/)
|
|
301
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
302
|
+
.join("");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function generateTypeScriptTypes(schemas: SchemaInfo[]): string {
|
|
306
|
+
const lines: string[] = [
|
|
307
|
+
"/**",
|
|
308
|
+
" * Auto-generated TypeScript types for Baasix collections",
|
|
309
|
+
` * Generated at: ${new Date().toISOString()}`,
|
|
310
|
+
" * ",
|
|
311
|
+
" * Do not edit this file manually. Re-run 'baasix generate types' to update.",
|
|
312
|
+
" */",
|
|
313
|
+
"",
|
|
314
|
+
"// GeoJSON types for PostGIS fields",
|
|
315
|
+
"declare namespace GeoJSON {",
|
|
316
|
+
" interface Point { type: 'Point'; coordinates: [number, number]; }",
|
|
317
|
+
" interface LineString { type: 'LineString'; coordinates: [number, number][]; }",
|
|
318
|
+
" interface Polygon { type: 'Polygon'; coordinates: [number, number][][]; }",
|
|
319
|
+
" type Geometry = Point | LineString | Polygon;",
|
|
320
|
+
"}",
|
|
321
|
+
"",
|
|
322
|
+
];
|
|
323
|
+
|
|
324
|
+
// Collect all referenced system collections
|
|
325
|
+
const referencedSystemCollections = new Set<string>();
|
|
326
|
+
for (const schema of schemas) {
|
|
327
|
+
for (const field of Object.values(schema.schema.fields)) {
|
|
328
|
+
const fieldDef = field as FieldDefinition;
|
|
329
|
+
if (fieldDef.relType && fieldDef.target && fieldDef.target.startsWith("baasix_")) {
|
|
330
|
+
referencedSystemCollections.add(fieldDef.target);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Generate types for referenced system collections first
|
|
336
|
+
const systemSchemas = schemas.filter(
|
|
337
|
+
(s) => referencedSystemCollections.has(s.collectionName)
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
for (const schema of systemSchemas) {
|
|
341
|
+
const typeName = toPascalCase(schema.collectionName);
|
|
342
|
+
const fields = schema.schema.fields;
|
|
343
|
+
|
|
344
|
+
lines.push(`/**`);
|
|
345
|
+
lines.push(` * ${schema.schema.name || schema.collectionName} (system collection)`);
|
|
346
|
+
lines.push(` */`);
|
|
347
|
+
lines.push(`export interface ${typeName} {`);
|
|
348
|
+
|
|
349
|
+
for (const [fieldName, field] of Object.entries(fields)) {
|
|
350
|
+
const fieldDef = field as FieldDefinition;
|
|
351
|
+
// Skip relation fields for system collections to avoid circular refs
|
|
352
|
+
if (fieldDef.relType) continue;
|
|
353
|
+
|
|
354
|
+
const { type: tsType, jsdoc } = fieldTypeToTS(fieldDef, schemas);
|
|
355
|
+
const optional = fieldDef.allowNull !== false && !fieldDef.primaryKey ? "?" : "";
|
|
356
|
+
if (jsdoc) {
|
|
357
|
+
lines.push(` /** ${jsdoc} */`);
|
|
358
|
+
}
|
|
359
|
+
lines.push(` ${fieldName}${optional}: ${tsType};`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
lines.push(`}`);
|
|
363
|
+
lines.push("");
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Filter out system collections for user schemas
|
|
367
|
+
const userSchemas = schemas.filter(
|
|
368
|
+
(s) => !s.collectionName.startsWith("baasix_")
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
for (const schema of userSchemas) {
|
|
372
|
+
const typeName = toPascalCase(schema.collectionName);
|
|
373
|
+
const fields = schema.schema.fields;
|
|
374
|
+
|
|
375
|
+
lines.push(`/**`);
|
|
376
|
+
lines.push(` * ${schema.schema.name || schema.collectionName} collection`);
|
|
377
|
+
lines.push(` */`);
|
|
378
|
+
lines.push(`export interface ${typeName} {`);
|
|
379
|
+
|
|
380
|
+
for (const [fieldName, field] of Object.entries(fields)) {
|
|
381
|
+
const fieldDef = field as FieldDefinition;
|
|
382
|
+
const { type: tsType, jsdoc } = fieldTypeToTS(fieldDef, schemas);
|
|
383
|
+
const optional = fieldDef.allowNull !== false && !fieldDef.primaryKey ? "?" : "";
|
|
384
|
+
if (jsdoc) {
|
|
385
|
+
lines.push(` /** ${jsdoc} */`);
|
|
386
|
+
}
|
|
387
|
+
lines.push(` ${fieldName}${optional}: ${tsType};`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Add timestamp fields if enabled
|
|
391
|
+
if (schema.schema.timestamps) {
|
|
392
|
+
lines.push(` createdAt?: string;`);
|
|
393
|
+
lines.push(` updatedAt?: string;`);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Add soft delete field if paranoid
|
|
397
|
+
if (schema.schema.paranoid) {
|
|
398
|
+
lines.push(` deletedAt?: string | null;`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
lines.push(`}`);
|
|
402
|
+
lines.push("");
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Generate a union type of all collection names
|
|
406
|
+
lines.push("/**");
|
|
407
|
+
lines.push(" * All collection names");
|
|
408
|
+
lines.push(" */");
|
|
409
|
+
lines.push("export type CollectionName =");
|
|
410
|
+
for (const schema of userSchemas) {
|
|
411
|
+
lines.push(` | "${schema.collectionName}"`);
|
|
412
|
+
}
|
|
413
|
+
lines.push(";");
|
|
414
|
+
lines.push("");
|
|
415
|
+
|
|
416
|
+
// Generate a type map
|
|
417
|
+
lines.push("/**");
|
|
418
|
+
lines.push(" * Map collection names to their types");
|
|
419
|
+
lines.push(" */");
|
|
420
|
+
lines.push("export interface CollectionTypeMap {");
|
|
421
|
+
for (const schema of userSchemas) {
|
|
422
|
+
const typeName = toPascalCase(schema.collectionName);
|
|
423
|
+
lines.push(` ${schema.collectionName}: ${typeName};`);
|
|
424
|
+
}
|
|
425
|
+
lines.push("}");
|
|
426
|
+
lines.push("");
|
|
427
|
+
|
|
428
|
+
return lines.join("\n");
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function generateSDKTypes(schemas: SchemaInfo[]): string {
|
|
432
|
+
const lines: string[] = [
|
|
433
|
+
"/**",
|
|
434
|
+
" * Auto-generated typed SDK helpers for Baasix collections",
|
|
435
|
+
` * Generated at: ${new Date().toISOString()}`,
|
|
436
|
+
" * ",
|
|
437
|
+
" * Do not edit this file manually. Re-run 'baasix generate sdk-types' to update.",
|
|
438
|
+
" */",
|
|
439
|
+
"",
|
|
440
|
+
'import { createBaasix } from "@tspvivek/baasix-sdk";',
|
|
441
|
+
'import type { QueryParams, Filter, PaginatedResponse } from "@tspvivek/baasix-sdk";',
|
|
442
|
+
"",
|
|
443
|
+
];
|
|
444
|
+
|
|
445
|
+
// Generate types first
|
|
446
|
+
lines.push(generateTypeScriptTypes(schemas));
|
|
447
|
+
|
|
448
|
+
// Generate typed items helper
|
|
449
|
+
lines.push("/**");
|
|
450
|
+
lines.push(" * Create a typed Baasix client with collection-specific methods");
|
|
451
|
+
lines.push(" */");
|
|
452
|
+
lines.push("export function createTypedBaasix(config: Parameters<typeof createBaasix>[0]) {");
|
|
453
|
+
lines.push(" const client = createBaasix(config);");
|
|
454
|
+
lines.push("");
|
|
455
|
+
lines.push(" return {");
|
|
456
|
+
lines.push(" ...client,");
|
|
457
|
+
lines.push(" /**");
|
|
458
|
+
lines.push(" * Type-safe items access");
|
|
459
|
+
lines.push(" */");
|
|
460
|
+
lines.push(" collections: {");
|
|
461
|
+
|
|
462
|
+
const userSchemas = schemas.filter((s) => !s.collectionName.startsWith("baasix_"));
|
|
463
|
+
|
|
464
|
+
for (const schema of userSchemas) {
|
|
465
|
+
const typeName = toPascalCase(schema.collectionName);
|
|
466
|
+
lines.push(` ${schema.collectionName}: client.items<${typeName}>("${schema.collectionName}"),`);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
lines.push(" },");
|
|
470
|
+
lines.push(" };");
|
|
471
|
+
lines.push("}");
|
|
472
|
+
lines.push("");
|
|
473
|
+
|
|
474
|
+
return lines.join("\n");
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export const generate = new Command("generate")
|
|
478
|
+
.alias("gen")
|
|
479
|
+
.description("Generate TypeScript types from Baasix schemas")
|
|
480
|
+
.option("-c, --cwd <path>", "Working directory", process.cwd())
|
|
481
|
+
.option("-o, --output <path>", "Output file path")
|
|
482
|
+
.option("-t, --target <target>", "Generation target (types, sdk-types, schema-json)")
|
|
483
|
+
.option("--url <url>", "Baasix server URL")
|
|
484
|
+
.option("-y, --yes", "Skip confirmation prompts")
|
|
485
|
+
.action(generateAction);
|