schemock 0.0.4-alpha.2 → 0.0.4-alpha.3
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 +247 -0
- package/dist/cli/index.d.mts +127 -1
- package/dist/cli/index.d.ts +127 -1
- package/dist/cli/index.js +625 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +621 -3
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli.js +733 -21
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,243 @@ var __export = (target, all) => {
|
|
|
10
10
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
// src/cli/generators/claude-md.ts
|
|
14
|
+
var claude_md_exports = {};
|
|
15
|
+
__export(claude_md_exports, {
|
|
16
|
+
generateClaudeMd: () => generateClaudeMd,
|
|
17
|
+
generateCursorRules: () => generateCursorRules,
|
|
18
|
+
generateSchemockSection: () => generateSchemockSection,
|
|
19
|
+
mergeClaudeMdContent: () => mergeClaudeMdContent,
|
|
20
|
+
validateExistingContent: () => validateExistingContent
|
|
21
|
+
});
|
|
22
|
+
function generateSchemockSection(config) {
|
|
23
|
+
const lines = [];
|
|
24
|
+
lines.push(SECTION_START_MARKER);
|
|
25
|
+
lines.push("");
|
|
26
|
+
lines.push("## Schemock - AI Instructions");
|
|
27
|
+
lines.push("");
|
|
28
|
+
lines.push("This project uses [Schemock](https://github.com/prajyot-tote/schemock) for schema-first code generation.");
|
|
29
|
+
lines.push("");
|
|
30
|
+
lines.push("### Generated Files - DO NOT MODIFY");
|
|
31
|
+
lines.push("");
|
|
32
|
+
lines.push("The following directories contain auto-generated code. **NEVER edit these files directly.**");
|
|
33
|
+
lines.push("Changes will be overwritten on next `npx schemock generate`.");
|
|
34
|
+
lines.push("");
|
|
35
|
+
const outputDirs = getOutputDirectories(config);
|
|
36
|
+
for (const dir of outputDirs) {
|
|
37
|
+
lines.push(`- \`${dir}/**/*\``);
|
|
38
|
+
}
|
|
39
|
+
lines.push("");
|
|
40
|
+
lines.push("### How to Make Changes");
|
|
41
|
+
lines.push("");
|
|
42
|
+
lines.push("To modify generated types, hooks, or clients:");
|
|
43
|
+
lines.push("");
|
|
44
|
+
lines.push(`1. **Edit schema files** in \`${config.schemas.replace("/**/*.ts", "/")}\``);
|
|
45
|
+
lines.push("2. **Run generation**: `npx schemock generate`");
|
|
46
|
+
lines.push("3. **Import from generated directory**");
|
|
47
|
+
lines.push("");
|
|
48
|
+
lines.push("### Schema DSL Quick Reference");
|
|
49
|
+
lines.push("");
|
|
50
|
+
lines.push("```typescript");
|
|
51
|
+
lines.push("import { defineData, field, hasMany, belongsTo } from 'schemock/schema';");
|
|
52
|
+
lines.push("");
|
|
53
|
+
lines.push("export const userSchema = defineData('user', {");
|
|
54
|
+
lines.push(" id: field.uuid(),");
|
|
55
|
+
lines.push(" email: field.email().unique(),");
|
|
56
|
+
lines.push(" name: field.string(),");
|
|
57
|
+
lines.push(" role: field.enum(['admin', 'user']).default('user'),");
|
|
58
|
+
lines.push(" avatar: field.url().nullable(),");
|
|
59
|
+
lines.push(" createdAt: field.timestamp().default(new Date()),");
|
|
60
|
+
lines.push("});");
|
|
61
|
+
lines.push("");
|
|
62
|
+
lines.push("export const postSchema = defineData('post', {");
|
|
63
|
+
lines.push(" id: field.uuid(),");
|
|
64
|
+
lines.push(" title: field.string(),");
|
|
65
|
+
lines.push(" content: field.text(),");
|
|
66
|
+
lines.push(" authorId: field.ref('user'),");
|
|
67
|
+
lines.push("}, {");
|
|
68
|
+
lines.push(" relations: {");
|
|
69
|
+
lines.push(" author: belongsTo('user', 'authorId'),");
|
|
70
|
+
lines.push(" },");
|
|
71
|
+
lines.push("});");
|
|
72
|
+
lines.push("```");
|
|
73
|
+
lines.push("");
|
|
74
|
+
lines.push("### Available Field Types");
|
|
75
|
+
lines.push("");
|
|
76
|
+
lines.push("| Type | Description | Example |");
|
|
77
|
+
lines.push("|------|-------------|---------|");
|
|
78
|
+
lines.push("| `field.uuid()` | UUID primary key | `id: field.uuid()` |");
|
|
79
|
+
lines.push("| `field.string()` | Text string | `name: field.string()` |");
|
|
80
|
+
lines.push("| `field.text()` | Long text | `content: field.text()` |");
|
|
81
|
+
lines.push("| `field.email()` | Email address | `email: field.email()` |");
|
|
82
|
+
lines.push("| `field.url()` | URL string | `avatar: field.url()` |");
|
|
83
|
+
lines.push("| `field.int()` | Integer number | `age: field.int()` |");
|
|
84
|
+
lines.push("| `field.float()` | Decimal number | `price: field.float()` |");
|
|
85
|
+
lines.push("| `field.boolean()` | True/false | `active: field.boolean()` |");
|
|
86
|
+
lines.push("| `field.enum([...])` | Enum values | `status: field.enum(['draft', 'published'])` |");
|
|
87
|
+
lines.push("| `field.timestamp()` | Date/time | `createdAt: field.timestamp()` |");
|
|
88
|
+
lines.push("| `field.date()` | Date only | `birthDate: field.date()` |");
|
|
89
|
+
lines.push("| `field.ref('entity')` | Foreign key | `authorId: field.ref('user')` |");
|
|
90
|
+
lines.push("| `field.json()` | JSON object | `metadata: field.json()` |");
|
|
91
|
+
lines.push("");
|
|
92
|
+
lines.push("### Field Modifiers");
|
|
93
|
+
lines.push("");
|
|
94
|
+
lines.push("- `.nullable()` - Field can be null");
|
|
95
|
+
lines.push("- `.default(value)` - Default value");
|
|
96
|
+
lines.push("- `.unique()` - Must be unique");
|
|
97
|
+
lines.push("- `.index()` - Create database index");
|
|
98
|
+
lines.push("");
|
|
99
|
+
lines.push("### Relations");
|
|
100
|
+
lines.push("");
|
|
101
|
+
lines.push("```typescript");
|
|
102
|
+
lines.push("import { hasMany, belongsTo, hasOne, manyToMany } from 'schemock/schema';");
|
|
103
|
+
lines.push("");
|
|
104
|
+
lines.push("// One-to-many: User has many Posts");
|
|
105
|
+
lines.push("hasMany('post', 'authorId')");
|
|
106
|
+
lines.push("");
|
|
107
|
+
lines.push("// Many-to-one: Post belongs to User");
|
|
108
|
+
lines.push("belongsTo('user', 'authorId')");
|
|
109
|
+
lines.push("");
|
|
110
|
+
lines.push("// One-to-one: User has one Profile");
|
|
111
|
+
lines.push("hasOne('profile', 'userId')");
|
|
112
|
+
lines.push("");
|
|
113
|
+
lines.push("// Many-to-many: Post has many Tags");
|
|
114
|
+
lines.push("manyToMany('tag', 'post_tags')");
|
|
115
|
+
lines.push("```");
|
|
116
|
+
lines.push("");
|
|
117
|
+
lines.push("### Common Tasks");
|
|
118
|
+
lines.push("");
|
|
119
|
+
lines.push("| Task | What to do |");
|
|
120
|
+
lines.push("|------|------------|");
|
|
121
|
+
lines.push("| Add new entity | Create new schema file in `src/schemas/`, run `npx schemock generate` |");
|
|
122
|
+
lines.push("| Add field | Edit schema file, run `npx schemock generate` |");
|
|
123
|
+
lines.push("| Add relation | Add to schema `relations` object, run `npx schemock generate` |");
|
|
124
|
+
lines.push("| Change field type | Edit schema file, run `npx schemock generate` |");
|
|
125
|
+
lines.push("| Fix generated code bug | Report issue, don't edit generated files |");
|
|
126
|
+
lines.push("");
|
|
127
|
+
lines.push("### CLI Commands");
|
|
128
|
+
lines.push("");
|
|
129
|
+
lines.push("```bash");
|
|
130
|
+
lines.push("# Generate all code from schemas");
|
|
131
|
+
lines.push("npx schemock generate");
|
|
132
|
+
lines.push("");
|
|
133
|
+
lines.push("# Generate for specific adapter");
|
|
134
|
+
lines.push("npx schemock generate --adapter supabase");
|
|
135
|
+
lines.push("");
|
|
136
|
+
lines.push("# Generate SQL migrations");
|
|
137
|
+
lines.push("npx schemock generate:sql");
|
|
138
|
+
lines.push("");
|
|
139
|
+
lines.push("# Dry run (show what would be generated)");
|
|
140
|
+
lines.push("npx schemock generate --dry-run");
|
|
141
|
+
lines.push("```");
|
|
142
|
+
lines.push("");
|
|
143
|
+
lines.push(SECTION_END_MARKER);
|
|
144
|
+
return lines.join("\n");
|
|
145
|
+
}
|
|
146
|
+
function getOutputDirectories(config) {
|
|
147
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
148
|
+
if (config.output) {
|
|
149
|
+
dirs.add(normalizeOutputPath(config.output));
|
|
150
|
+
}
|
|
151
|
+
if (config.targets && config.targets.length > 0) {
|
|
152
|
+
for (const target of config.targets) {
|
|
153
|
+
dirs.add(normalizeOutputPath(target.output));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (dirs.size === 0) {
|
|
157
|
+
dirs.add("./src/generated");
|
|
158
|
+
}
|
|
159
|
+
return Array.from(dirs).sort();
|
|
160
|
+
}
|
|
161
|
+
function normalizeOutputPath(path) {
|
|
162
|
+
if (!path.startsWith("./") && !path.startsWith("/")) {
|
|
163
|
+
return `./${path}`;
|
|
164
|
+
}
|
|
165
|
+
return path;
|
|
166
|
+
}
|
|
167
|
+
function mergeClaudeMdContent(existingContent, schemockSection) {
|
|
168
|
+
const startIndex = existingContent.indexOf(SECTION_START_MARKER);
|
|
169
|
+
const endIndex = existingContent.indexOf(SECTION_END_MARKER);
|
|
170
|
+
if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
|
|
171
|
+
const before = existingContent.substring(0, startIndex);
|
|
172
|
+
const after = existingContent.substring(endIndex + SECTION_END_MARKER.length);
|
|
173
|
+
const oldSection = existingContent.substring(startIndex, endIndex + SECTION_END_MARKER.length);
|
|
174
|
+
if (oldSection === schemockSection) {
|
|
175
|
+
return { content: existingContent, wasUpdated: false };
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
content: before + schemockSection + after,
|
|
179
|
+
wasUpdated: true
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const separator = existingContent.trim() ? "\n\n" : "";
|
|
183
|
+
return {
|
|
184
|
+
content: existingContent.trim() + separator + schemockSection + "\n",
|
|
185
|
+
wasUpdated: true
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function validateExistingContent(content) {
|
|
189
|
+
const warnings = [];
|
|
190
|
+
const startCount = (content.match(/SCHEMOCK:START/g) || []).length;
|
|
191
|
+
const endCount = (content.match(/SCHEMOCK:END/g) || []).length;
|
|
192
|
+
if (startCount !== endCount) {
|
|
193
|
+
warnings.push(
|
|
194
|
+
`Found mismatched Schemock markers (${startCount} START, ${endCount} END). Section will be appended instead of replaced.`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
if (startCount > 1 || endCount > 1) {
|
|
198
|
+
warnings.push(
|
|
199
|
+
"Found multiple Schemock sections. Only the first will be replaced."
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
isValid: warnings.length === 0,
|
|
204
|
+
warnings
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function generateClaudeMd(config, existingContent = "") {
|
|
208
|
+
const warnings = [];
|
|
209
|
+
if (existingContent) {
|
|
210
|
+
const validation = validateExistingContent(existingContent);
|
|
211
|
+
warnings.push(...validation.warnings);
|
|
212
|
+
}
|
|
213
|
+
const schemockSection = generateSchemockSection(config);
|
|
214
|
+
const { content, wasUpdated } = mergeClaudeMdContent(existingContent, schemockSection);
|
|
215
|
+
return {
|
|
216
|
+
created: !existingContent,
|
|
217
|
+
modified: wasUpdated,
|
|
218
|
+
content,
|
|
219
|
+
path: "CLAUDE.md",
|
|
220
|
+
warnings
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function generateCursorRules(config) {
|
|
224
|
+
const outputDirs = getOutputDirectories(config);
|
|
225
|
+
const lines = [];
|
|
226
|
+
lines.push("# Schemock Rules for Cursor");
|
|
227
|
+
lines.push("");
|
|
228
|
+
lines.push("## Generated Files - DO NOT MODIFY");
|
|
229
|
+
lines.push("");
|
|
230
|
+
lines.push("Never edit files in these directories:");
|
|
231
|
+
for (const dir of outputDirs) {
|
|
232
|
+
lines.push(`- ${dir}/**/*`);
|
|
233
|
+
}
|
|
234
|
+
lines.push("");
|
|
235
|
+
lines.push("To make changes, edit schema files and run: npx schemock generate");
|
|
236
|
+
lines.push("");
|
|
237
|
+
lines.push(`Schema files location: ${config.schemas}`);
|
|
238
|
+
lines.push("");
|
|
239
|
+
return lines.join("\n");
|
|
240
|
+
}
|
|
241
|
+
var SECTION_START_MARKER, SECTION_END_MARKER;
|
|
242
|
+
var init_claude_md = __esm({
|
|
243
|
+
"src/cli/generators/claude-md.ts"() {
|
|
244
|
+
"use strict";
|
|
245
|
+
SECTION_START_MARKER = "<!-- SCHEMOCK:START - AI instructions for Schemock. Do not remove this marker -->";
|
|
246
|
+
SECTION_END_MARKER = "<!-- SCHEMOCK:END -->";
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
13
250
|
// src/cli/config.ts
|
|
14
251
|
function validateConfig(config, filePath) {
|
|
15
252
|
const result = SchemockConfigSchema.safeParse(config);
|
|
@@ -4435,6 +4672,313 @@ var init_hooks = __esm({
|
|
|
4435
4672
|
}
|
|
4436
4673
|
});
|
|
4437
4674
|
|
|
4675
|
+
// src/cli/generators/form-schemas.ts
|
|
4676
|
+
function generateFormSchemas(schemas) {
|
|
4677
|
+
const code = new CodeBuilder();
|
|
4678
|
+
code.line();
|
|
4679
|
+
code.comment("=".repeat(70));
|
|
4680
|
+
code.comment("FORM SCHEMAS - Zod validation, defaults, and column metadata");
|
|
4681
|
+
code.comment("=".repeat(70));
|
|
4682
|
+
code.line();
|
|
4683
|
+
code.line("import { z } from 'zod';");
|
|
4684
|
+
code.line();
|
|
4685
|
+
for (const schema of schemas) {
|
|
4686
|
+
if (schema.isJunctionTable) continue;
|
|
4687
|
+
generateEntityFormSchemas(code, schema);
|
|
4688
|
+
}
|
|
4689
|
+
generateColumnTypes(code);
|
|
4690
|
+
return code.toString();
|
|
4691
|
+
}
|
|
4692
|
+
function generateEntityFormSchemas(code, schema) {
|
|
4693
|
+
const { pascalName, fields } = schema;
|
|
4694
|
+
const formFields = fields.filter((f) => f.name !== "id" && !f.readOnly && !f.isComputed);
|
|
4695
|
+
code.multiDocComment([
|
|
4696
|
+
`Zod validation schema for ${pascalName} forms`,
|
|
4697
|
+
"",
|
|
4698
|
+
"Use with react-hook-form:",
|
|
4699
|
+
"```typescript",
|
|
4700
|
+
"import { useForm } from 'react-hook-form';",
|
|
4701
|
+
"import { zodResolver } from '@hookform/resolvers/zod';",
|
|
4702
|
+
"",
|
|
4703
|
+
"const form = useForm({",
|
|
4704
|
+
` resolver: zodResolver(${pascalName}FormSchema),`,
|
|
4705
|
+
` defaultValues: ${pascalName}FormDefaults,`,
|
|
4706
|
+
"});",
|
|
4707
|
+
"```"
|
|
4708
|
+
]);
|
|
4709
|
+
code.block(`export const ${pascalName}FormSchema = z.object({`, () => {
|
|
4710
|
+
for (const field of formFields) {
|
|
4711
|
+
const zodType = fieldToZodType(field);
|
|
4712
|
+
code.line(`${field.name}: ${zodType},`);
|
|
4713
|
+
}
|
|
4714
|
+
}, "});");
|
|
4715
|
+
code.line();
|
|
4716
|
+
code.docComment(`Default values for ${pascalName} form initialization`);
|
|
4717
|
+
code.block(`export const ${pascalName}FormDefaults: z.input<typeof ${pascalName}FormSchema> = {`, () => {
|
|
4718
|
+
for (const field of formFields) {
|
|
4719
|
+
const defaultValue = getFieldDefault(field);
|
|
4720
|
+
code.line(`${field.name}: ${defaultValue},`);
|
|
4721
|
+
}
|
|
4722
|
+
}, "};");
|
|
4723
|
+
code.line();
|
|
4724
|
+
code.docComment(`Inferred type from ${pascalName}FormSchema`);
|
|
4725
|
+
code.line(`export type ${pascalName}FormData = z.infer<typeof ${pascalName}FormSchema>;`);
|
|
4726
|
+
code.line();
|
|
4727
|
+
const tableFields = fields.filter((f) => !f.isComputed || f.name === "id");
|
|
4728
|
+
code.docComment(`Table column definitions for ${pascalName}`);
|
|
4729
|
+
code.block(`export const ${pascalName}TableColumns: ColumnDef[] = [`, () => {
|
|
4730
|
+
for (const field of tableFields) {
|
|
4731
|
+
const columnDef = fieldToColumnDef(field);
|
|
4732
|
+
code.line(`${columnDef},`);
|
|
4733
|
+
}
|
|
4734
|
+
}, "];");
|
|
4735
|
+
code.line();
|
|
4736
|
+
const columnKeys = tableFields.map((f) => `'${f.name}'`).join(" | ");
|
|
4737
|
+
code.docComment(`Valid column keys for ${pascalName} table`);
|
|
4738
|
+
code.line(`export type ${pascalName}ColumnKey = ${columnKeys};`);
|
|
4739
|
+
code.line();
|
|
4740
|
+
}
|
|
4741
|
+
function fieldToZodType(field) {
|
|
4742
|
+
let zodType;
|
|
4743
|
+
if (field.isEnum && field.enumValues && field.enumValues.length > 0) {
|
|
4744
|
+
const enumLiterals = field.enumValues.map((v) => `'${v}'`).join(", ");
|
|
4745
|
+
zodType = `z.enum([${enumLiterals}])`;
|
|
4746
|
+
} else if (field.isArray && field.itemType) {
|
|
4747
|
+
const itemZod = fieldToZodType(field.itemType);
|
|
4748
|
+
zodType = `z.array(${itemZod})`;
|
|
4749
|
+
} else if (field.isObject && field.shape) {
|
|
4750
|
+
const shapeFields = Object.entries(field.shape).map(([key, f]) => `${key}: ${fieldToZodType(f)}`).join(", ");
|
|
4751
|
+
zodType = `z.object({ ${shapeFields} })`;
|
|
4752
|
+
} else if (field.isRef) {
|
|
4753
|
+
zodType = "z.string().uuid()";
|
|
4754
|
+
} else {
|
|
4755
|
+
zodType = baseTypeToZod(field.type, field);
|
|
4756
|
+
}
|
|
4757
|
+
zodType = applyConstraints(zodType, field);
|
|
4758
|
+
if (field.nullable) {
|
|
4759
|
+
zodType = `${zodType}.nullable()`;
|
|
4760
|
+
}
|
|
4761
|
+
if (field.hasDefault) {
|
|
4762
|
+
zodType = `${zodType}.optional()`;
|
|
4763
|
+
}
|
|
4764
|
+
return zodType;
|
|
4765
|
+
}
|
|
4766
|
+
function baseTypeToZod(type, field) {
|
|
4767
|
+
const lowerType = type.toLowerCase();
|
|
4768
|
+
if (["string", "text", "varchar", "char", "uuid", "citext"].includes(lowerType) || lowerType.startsWith("varchar")) {
|
|
4769
|
+
if (lowerType === "uuid") {
|
|
4770
|
+
return "z.string().uuid()";
|
|
4771
|
+
}
|
|
4772
|
+
return "z.string()";
|
|
4773
|
+
}
|
|
4774
|
+
if (lowerType === "email" || field.type.includes("email")) {
|
|
4775
|
+
return "z.string().email()";
|
|
4776
|
+
}
|
|
4777
|
+
if (lowerType === "url" || field.type.includes("url")) {
|
|
4778
|
+
return "z.string().url()";
|
|
4779
|
+
}
|
|
4780
|
+
if (["int", "integer", "smallint", "serial", "smallserial"].includes(lowerType)) {
|
|
4781
|
+
return "z.number().int()";
|
|
4782
|
+
}
|
|
4783
|
+
if (["bigint", "bigserial"].includes(lowerType)) {
|
|
4784
|
+
return "z.bigint()";
|
|
4785
|
+
}
|
|
4786
|
+
if (["float", "double", "real", "double precision"].includes(lowerType)) {
|
|
4787
|
+
return "z.number()";
|
|
4788
|
+
}
|
|
4789
|
+
if (["decimal", "numeric", "money"].includes(lowerType)) {
|
|
4790
|
+
return 'z.string().regex(/^-?\\d+(\\.\\d+)?$/, "Must be a valid decimal number")';
|
|
4791
|
+
}
|
|
4792
|
+
if (["boolean", "bool"].includes(lowerType)) {
|
|
4793
|
+
return "z.boolean()";
|
|
4794
|
+
}
|
|
4795
|
+
if (["date", "datetime", "timestamp", "timestamptz"].includes(lowerType)) {
|
|
4796
|
+
return "z.coerce.date()";
|
|
4797
|
+
}
|
|
4798
|
+
if (["time", "timetz", "interval"].includes(lowerType)) {
|
|
4799
|
+
return "z.string()";
|
|
4800
|
+
}
|
|
4801
|
+
if (["json", "jsonb"].includes(lowerType)) {
|
|
4802
|
+
return "z.unknown()";
|
|
4803
|
+
}
|
|
4804
|
+
switch (field.tsType) {
|
|
4805
|
+
case "string":
|
|
4806
|
+
return "z.string()";
|
|
4807
|
+
case "number":
|
|
4808
|
+
return "z.number()";
|
|
4809
|
+
case "boolean":
|
|
4810
|
+
return "z.boolean()";
|
|
4811
|
+
case "Date":
|
|
4812
|
+
return "z.coerce.date()";
|
|
4813
|
+
case "bigint":
|
|
4814
|
+
return "z.bigint()";
|
|
4815
|
+
default:
|
|
4816
|
+
return "z.unknown()";
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4819
|
+
function applyConstraints(zodType, field) {
|
|
4820
|
+
const constraints = [];
|
|
4821
|
+
if (field.tsType === "string" || field.type === "string" || field.type === "text") {
|
|
4822
|
+
if (field.min !== void 0) {
|
|
4823
|
+
constraints.push(`.min(${field.min})`);
|
|
4824
|
+
}
|
|
4825
|
+
if (field.max !== void 0) {
|
|
4826
|
+
constraints.push(`.max(${field.max})`);
|
|
4827
|
+
}
|
|
4828
|
+
if (field.pattern) {
|
|
4829
|
+
const escapedPattern = field.pattern.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
4830
|
+
constraints.push(`.regex(/${escapedPattern}/)`);
|
|
4831
|
+
}
|
|
4832
|
+
}
|
|
4833
|
+
if (field.tsType === "number" || field.type === "number" || field.type === "int") {
|
|
4834
|
+
if (field.min !== void 0) {
|
|
4835
|
+
constraints.push(`.min(${field.min})`);
|
|
4836
|
+
}
|
|
4837
|
+
if (field.max !== void 0) {
|
|
4838
|
+
constraints.push(`.max(${field.max})`);
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
if (!field.nullable && !field.hasDefault) {
|
|
4842
|
+
if ((field.tsType === "string" || field.type === "string") && field.min === void 0 && !zodType.includes(".email()") && !zodType.includes(".url()") && !zodType.includes(".uuid()")) {
|
|
4843
|
+
constraints.push(`.min(1, '${formatFieldName(field.name)} is required')`);
|
|
4844
|
+
}
|
|
4845
|
+
}
|
|
4846
|
+
return zodType + constraints.join("");
|
|
4847
|
+
}
|
|
4848
|
+
function getFieldDefault(field) {
|
|
4849
|
+
if (field.hasDefault && field.defaultValue !== void 0) {
|
|
4850
|
+
return formatDefaultValue2(field.defaultValue, field);
|
|
4851
|
+
}
|
|
4852
|
+
if (field.isEnum && field.enumValues && field.enumValues.length > 0) {
|
|
4853
|
+
return `'${field.enumValues[0]}'`;
|
|
4854
|
+
}
|
|
4855
|
+
if (field.isArray) {
|
|
4856
|
+
return "[]";
|
|
4857
|
+
}
|
|
4858
|
+
if (field.isObject && field.shape) {
|
|
4859
|
+
const shapeDefaults = Object.entries(field.shape).map(([key, f]) => `${key}: ${getFieldDefault(f)}`).join(", ");
|
|
4860
|
+
return `{ ${shapeDefaults} }`;
|
|
4861
|
+
}
|
|
4862
|
+
if (field.isRef) {
|
|
4863
|
+
return "''";
|
|
4864
|
+
}
|
|
4865
|
+
if (field.nullable) {
|
|
4866
|
+
return "null";
|
|
4867
|
+
}
|
|
4868
|
+
switch (field.tsType) {
|
|
4869
|
+
case "string":
|
|
4870
|
+
return "''";
|
|
4871
|
+
case "number":
|
|
4872
|
+
return "0";
|
|
4873
|
+
case "boolean":
|
|
4874
|
+
return "false";
|
|
4875
|
+
case "Date":
|
|
4876
|
+
return "new Date()";
|
|
4877
|
+
case "bigint":
|
|
4878
|
+
return "BigInt(0)";
|
|
4879
|
+
default:
|
|
4880
|
+
return "undefined";
|
|
4881
|
+
}
|
|
4882
|
+
}
|
|
4883
|
+
function formatDefaultValue2(value, field) {
|
|
4884
|
+
if (value === null) return "null";
|
|
4885
|
+
if (value === void 0) return "undefined";
|
|
4886
|
+
if (typeof value === "string") {
|
|
4887
|
+
return `'${value.replace(/'/g, "\\'")}'`;
|
|
4888
|
+
}
|
|
4889
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
4890
|
+
return String(value);
|
|
4891
|
+
}
|
|
4892
|
+
if (typeof value === "bigint") {
|
|
4893
|
+
return `BigInt(${value})`;
|
|
4894
|
+
}
|
|
4895
|
+
if (value instanceof Date) {
|
|
4896
|
+
return `new Date('${value.toISOString()}')`;
|
|
4897
|
+
}
|
|
4898
|
+
if (Array.isArray(value)) {
|
|
4899
|
+
return JSON.stringify(value);
|
|
4900
|
+
}
|
|
4901
|
+
if (typeof value === "object") {
|
|
4902
|
+
return JSON.stringify(value);
|
|
4903
|
+
}
|
|
4904
|
+
return "undefined";
|
|
4905
|
+
}
|
|
4906
|
+
function fieldToColumnDef(field) {
|
|
4907
|
+
const parts = [];
|
|
4908
|
+
parts.push(`key: '${field.name}'`);
|
|
4909
|
+
parts.push(`label: '${formatFieldName(field.name)}'`);
|
|
4910
|
+
const columnType = getColumnType(field);
|
|
4911
|
+
parts.push(`type: '${columnType}'`);
|
|
4912
|
+
const sortable = !field.isComputed && !field.isObject && !field.isArray;
|
|
4913
|
+
parts.push(`sortable: ${sortable}`);
|
|
4914
|
+
const filterable = !field.isComputed && !field.isObject;
|
|
4915
|
+
parts.push(`filterable: ${filterable}`);
|
|
4916
|
+
const hidden = field.name === "id" || field.name.endsWith("Id") || field.name === "createdAt" || field.name === "updatedAt";
|
|
4917
|
+
if (hidden) {
|
|
4918
|
+
parts.push(`hidden: true`);
|
|
4919
|
+
}
|
|
4920
|
+
return `{ ${parts.join(", ")} }`;
|
|
4921
|
+
}
|
|
4922
|
+
function getColumnType(field) {
|
|
4923
|
+
if (field.isEnum) return "enum";
|
|
4924
|
+
if (field.isArray) return "array";
|
|
4925
|
+
if (field.isObject) return "object";
|
|
4926
|
+
if (field.isRef) return "ref";
|
|
4927
|
+
const lowerType = field.type.toLowerCase();
|
|
4928
|
+
if (["boolean", "bool"].includes(lowerType)) return "boolean";
|
|
4929
|
+
if (["date", "datetime", "timestamp", "timestamptz"].includes(lowerType)) return "date";
|
|
4930
|
+
if (["time", "timetz"].includes(lowerType)) return "time";
|
|
4931
|
+
if (["number", "int", "integer", "float", "double", "decimal", "numeric", "money", "bigint"].includes(
|
|
4932
|
+
lowerType
|
|
4933
|
+
))
|
|
4934
|
+
return "number";
|
|
4935
|
+
if (field.tsType === "number") return "number";
|
|
4936
|
+
if (lowerType === "email" || field.type.includes("email")) return "email";
|
|
4937
|
+
if (lowerType === "url" || field.type.includes("url")) return "url";
|
|
4938
|
+
if (lowerType === "uuid") return "id";
|
|
4939
|
+
return "text";
|
|
4940
|
+
}
|
|
4941
|
+
function formatFieldName(name) {
|
|
4942
|
+
return name.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
|
|
4943
|
+
}
|
|
4944
|
+
function generateColumnTypes(code) {
|
|
4945
|
+
code.comment("=".repeat(70));
|
|
4946
|
+
code.comment("COLUMN TYPES");
|
|
4947
|
+
code.comment("=".repeat(70));
|
|
4948
|
+
code.line();
|
|
4949
|
+
code.docComment("Column type for table rendering and behavior");
|
|
4950
|
+
code.line(
|
|
4951
|
+
"export type ColumnType = 'text' | 'number' | 'boolean' | 'date' | 'time' | 'email' | 'url' | 'id' | 'enum' | 'ref' | 'array' | 'object';"
|
|
4952
|
+
);
|
|
4953
|
+
code.line();
|
|
4954
|
+
code.docComment("Table column definition");
|
|
4955
|
+
code.block("export interface ColumnDef {", () => {
|
|
4956
|
+
code.line("/** Field key in the data object */");
|
|
4957
|
+
code.line("key: string;");
|
|
4958
|
+
code.line("/** Display label for column header */");
|
|
4959
|
+
code.line("label: string;");
|
|
4960
|
+
code.line("/** Column type for rendering and alignment */");
|
|
4961
|
+
code.line("type: ColumnType;");
|
|
4962
|
+
code.line("/** Whether column is sortable */");
|
|
4963
|
+
code.line("sortable: boolean;");
|
|
4964
|
+
code.line("/** Whether column is filterable */");
|
|
4965
|
+
code.line("filterable: boolean;");
|
|
4966
|
+
code.line("/** Whether column is hidden by default */");
|
|
4967
|
+
code.line("hidden?: boolean;");
|
|
4968
|
+
code.line("/** Custom width (CSS value) */");
|
|
4969
|
+
code.line("width?: string;");
|
|
4970
|
+
code.line("/** Custom render function name */");
|
|
4971
|
+
code.line("render?: string;");
|
|
4972
|
+
});
|
|
4973
|
+
code.line();
|
|
4974
|
+
}
|
|
4975
|
+
var init_form_schemas = __esm({
|
|
4976
|
+
"src/cli/generators/form-schemas.ts"() {
|
|
4977
|
+
"use strict";
|
|
4978
|
+
init_code_builder();
|
|
4979
|
+
}
|
|
4980
|
+
});
|
|
4981
|
+
|
|
4438
4982
|
// src/cli/generators/nextjs-api/route-template.ts
|
|
4439
4983
|
function generateRouteFile(schema, target, _config) {
|
|
4440
4984
|
const hasAuth = target.middleware?.auth !== void 0;
|
|
@@ -6542,10 +7086,14 @@ async function generate(options) {
|
|
|
6542
7086
|
if (analyzedEndpoints.length > 0) {
|
|
6543
7087
|
typesCode += generateEndpointTypes(analyzedEndpoints);
|
|
6544
7088
|
}
|
|
7089
|
+
if (options.withFormSchemas) {
|
|
7090
|
+
typesCode += generateFormSchemas(analyzed);
|
|
7091
|
+
}
|
|
6545
7092
|
await writeOutput4((0, import_node_path6.join)(outputDir, "types.ts"), typesCode, options.dryRun);
|
|
6546
7093
|
const entityCount = analyzed.filter((s) => !s.isJunctionTable).length;
|
|
6547
7094
|
const endpointInfo = analyzedEndpoints.length > 0 ? ` + ${analyzedEndpoints.length} endpoint types` : "";
|
|
6548
|
-
|
|
7095
|
+
const formSchemaInfo = options.withFormSchemas ? " + form schemas" : "";
|
|
7096
|
+
console.log(` \u2713 types.ts (${entityCount} entities + Create/Update/Filter types${endpointInfo}${formSchemaInfo})
|
|
6549
7097
|
`);
|
|
6550
7098
|
console.log(`\u{1F50C} Generating ${adapter} adapter...`);
|
|
6551
7099
|
switch (adapter) {
|
|
@@ -6711,6 +7259,7 @@ var init_generate = __esm({
|
|
|
6711
7259
|
init_client4();
|
|
6712
7260
|
init_pglite();
|
|
6713
7261
|
init_hooks();
|
|
7262
|
+
init_form_schemas();
|
|
6714
7263
|
init_target_registry();
|
|
6715
7264
|
}
|
|
6716
7265
|
});
|
|
@@ -6750,7 +7299,7 @@ function fieldToPgColumn(field) {
|
|
|
6750
7299
|
parts.push("UNIQUE");
|
|
6751
7300
|
}
|
|
6752
7301
|
if (field.hasDefault && field.defaultValue !== void 0) {
|
|
6753
|
-
const defaultVal =
|
|
7302
|
+
const defaultVal = formatDefaultValue3(field.defaultValue, field);
|
|
6754
7303
|
if (defaultVal !== null) {
|
|
6755
7304
|
parts.push(`DEFAULT ${defaultVal}`);
|
|
6756
7305
|
}
|
|
@@ -6761,7 +7310,7 @@ function fieldToPgColumn(field) {
|
|
|
6761
7310
|
}
|
|
6762
7311
|
return parts.join(" ");
|
|
6763
7312
|
}
|
|
6764
|
-
function
|
|
7313
|
+
function formatDefaultValue3(value, field) {
|
|
6765
7314
|
if (value === void 0 || value === null) {
|
|
6766
7315
|
return field.nullable ? "NULL" : null;
|
|
6767
7316
|
}
|
|
@@ -8562,9 +9111,113 @@ var init_generator = __esm({
|
|
|
8562
9111
|
}
|
|
8563
9112
|
});
|
|
8564
9113
|
|
|
9114
|
+
// src/cli/commands/setup-ai.ts
|
|
9115
|
+
var setup_ai_exports = {};
|
|
9116
|
+
__export(setup_ai_exports, {
|
|
9117
|
+
setupAI: () => setupAI
|
|
9118
|
+
});
|
|
9119
|
+
async function setupAI(options = {}) {
|
|
9120
|
+
console.log("\n\u{1F916} Schemock AI Setup\n");
|
|
9121
|
+
const config = await loadConfig(options.config);
|
|
9122
|
+
const outputDir = options.output || process.cwd();
|
|
9123
|
+
const claudeMdPath = (0, import_node_path8.resolve)(outputDir, "CLAUDE.md");
|
|
9124
|
+
let existingContent = "";
|
|
9125
|
+
if ((0, import_node_fs2.existsSync)(claudeMdPath)) {
|
|
9126
|
+
console.log("\u{1F4C4} Found existing CLAUDE.md");
|
|
9127
|
+
existingContent = (0, import_node_fs2.readFileSync)(claudeMdPath, "utf-8");
|
|
9128
|
+
} else {
|
|
9129
|
+
console.log("\u{1F4C4} No existing CLAUDE.md found - will create new file");
|
|
9130
|
+
}
|
|
9131
|
+
const claudeMdResult = generateClaudeMd(config, existingContent);
|
|
9132
|
+
claudeMdResult.path = claudeMdPath;
|
|
9133
|
+
if (claudeMdResult.warnings.length > 0) {
|
|
9134
|
+
console.log("\n\u26A0\uFE0F Warnings:");
|
|
9135
|
+
for (const warning of claudeMdResult.warnings) {
|
|
9136
|
+
console.log(` ${warning}`);
|
|
9137
|
+
}
|
|
9138
|
+
console.log("");
|
|
9139
|
+
}
|
|
9140
|
+
if (options.dryRun) {
|
|
9141
|
+
console.log("\n[DRY RUN] Would write CLAUDE.md:");
|
|
9142
|
+
console.log("\u2500".repeat(60));
|
|
9143
|
+
const sectionMatch = claudeMdResult.content.match(/<!-- SCHEMOCK:START[\s\S]*?SCHEMOCK:END -->/);
|
|
9144
|
+
if (sectionMatch) {
|
|
9145
|
+
console.log(sectionMatch[0]);
|
|
9146
|
+
}
|
|
9147
|
+
console.log("\u2500".repeat(60));
|
|
9148
|
+
} else if (claudeMdResult.modified || claudeMdResult.created) {
|
|
9149
|
+
(0, import_node_fs2.writeFileSync)(claudeMdPath, claudeMdResult.content, "utf-8");
|
|
9150
|
+
if (claudeMdResult.created) {
|
|
9151
|
+
console.log(` \u2713 Created ${claudeMdPath}`);
|
|
9152
|
+
} else {
|
|
9153
|
+
console.log(` \u2713 Updated ${claudeMdPath} (Schemock section)`);
|
|
9154
|
+
}
|
|
9155
|
+
} else {
|
|
9156
|
+
console.log(" \u2139 CLAUDE.md is already up to date");
|
|
9157
|
+
}
|
|
9158
|
+
let cursorResult;
|
|
9159
|
+
if (options.cursor) {
|
|
9160
|
+
const cursorPath = (0, import_node_path8.resolve)(outputDir, ".cursorrules");
|
|
9161
|
+
const cursorExists = (0, import_node_fs2.existsSync)(cursorPath);
|
|
9162
|
+
console.log("\n\u{1F4C4} Generating .cursorrules for Cursor IDE...");
|
|
9163
|
+
const cursorContent = generateCursorRules(config);
|
|
9164
|
+
if (options.dryRun) {
|
|
9165
|
+
console.log("\n[DRY RUN] Would write .cursorrules:");
|
|
9166
|
+
console.log("\u2500".repeat(60));
|
|
9167
|
+
console.log(cursorContent);
|
|
9168
|
+
console.log("\u2500".repeat(60));
|
|
9169
|
+
cursorResult = { created: !cursorExists, modified: true, path: cursorPath };
|
|
9170
|
+
} else {
|
|
9171
|
+
let shouldWrite = true;
|
|
9172
|
+
if (cursorExists && !options.force) {
|
|
9173
|
+
const existingCursor = (0, import_node_fs2.readFileSync)(cursorPath, "utf-8");
|
|
9174
|
+
if (!existingCursor.includes("Schemock Rules for Cursor")) {
|
|
9175
|
+
console.log(" \u26A0\uFE0F Existing .cursorrules was not created by Schemock");
|
|
9176
|
+
console.log(" Use --force to overwrite, or manually add Schemock rules");
|
|
9177
|
+
shouldWrite = false;
|
|
9178
|
+
} else if (existingCursor === cursorContent) {
|
|
9179
|
+
console.log(" \u2139 .cursorrules is already up to date");
|
|
9180
|
+
shouldWrite = false;
|
|
9181
|
+
}
|
|
9182
|
+
}
|
|
9183
|
+
if (shouldWrite) {
|
|
9184
|
+
(0, import_node_fs2.writeFileSync)(cursorPath, cursorContent, "utf-8");
|
|
9185
|
+
console.log(` \u2713 ${cursorExists ? "Updated" : "Created"} ${cursorPath}`);
|
|
9186
|
+
}
|
|
9187
|
+
cursorResult = { created: !cursorExists, modified: shouldWrite, path: cursorPath };
|
|
9188
|
+
}
|
|
9189
|
+
}
|
|
9190
|
+
console.log("\n\u2705 AI setup complete!\n");
|
|
9191
|
+
console.log("What this does:");
|
|
9192
|
+
console.log(" \u2022 CLAUDE.md tells Claude Code about your generated files");
|
|
9193
|
+
console.log(" \u2022 Helps AI avoid modifying auto-generated code");
|
|
9194
|
+
console.log(" \u2022 Provides schema DSL reference for AI assistance");
|
|
9195
|
+
if (options.cursor) {
|
|
9196
|
+
console.log(" \u2022 .cursorrules provides similar guidance for Cursor IDE");
|
|
9197
|
+
}
|
|
9198
|
+
console.log("\nNext steps:");
|
|
9199
|
+
console.log(" 1. Commit CLAUDE.md to your repository");
|
|
9200
|
+
console.log(" 2. Claude Code will now understand your Schemock project");
|
|
9201
|
+
console.log("");
|
|
9202
|
+
return {
|
|
9203
|
+
claudeMd: claudeMdResult,
|
|
9204
|
+
cursorRules: cursorResult
|
|
9205
|
+
};
|
|
9206
|
+
}
|
|
9207
|
+
var import_node_fs2, import_node_path8;
|
|
9208
|
+
var init_setup_ai = __esm({
|
|
9209
|
+
"src/cli/commands/setup-ai.ts"() {
|
|
9210
|
+
"use strict";
|
|
9211
|
+
import_node_fs2 = require("fs");
|
|
9212
|
+
import_node_path8 = require("path");
|
|
9213
|
+
init_config();
|
|
9214
|
+
init_claude_md();
|
|
9215
|
+
}
|
|
9216
|
+
});
|
|
9217
|
+
|
|
8565
9218
|
// src/cli.ts
|
|
8566
|
-
var
|
|
8567
|
-
var
|
|
9219
|
+
var import_node_fs3 = require("fs");
|
|
9220
|
+
var import_node_path9 = require("path");
|
|
8568
9221
|
function parseArgs(args) {
|
|
8569
9222
|
const options = {};
|
|
8570
9223
|
const positional = [];
|
|
@@ -8601,6 +9254,12 @@ function parseArgs(args) {
|
|
|
8601
9254
|
options.exclude = args[++i].split(",");
|
|
8602
9255
|
} else if (arg === "--readme") {
|
|
8603
9256
|
options.readme = true;
|
|
9257
|
+
} else if (arg === "--with-form-schemas") {
|
|
9258
|
+
options.withFormSchemas = true;
|
|
9259
|
+
} else if (arg === "--cursor") {
|
|
9260
|
+
options.cursor = true;
|
|
9261
|
+
} else if (arg === "--force") {
|
|
9262
|
+
options.force = true;
|
|
8604
9263
|
} else if (!arg.startsWith("-")) {
|
|
8605
9264
|
positional.push(arg);
|
|
8606
9265
|
}
|
|
@@ -8622,6 +9281,7 @@ Commands:
|
|
|
8622
9281
|
Generate OpenAPI 3.0 specification
|
|
8623
9282
|
generate:postman [--output <file>]
|
|
8624
9283
|
Generate Postman collection
|
|
9284
|
+
setup:ai [options] Generate CLAUDE.md for AI tool integration
|
|
8625
9285
|
help Show this help message
|
|
8626
9286
|
version Show version
|
|
8627
9287
|
|
|
@@ -8633,6 +9293,8 @@ Generate Options:
|
|
|
8633
9293
|
Applies to ALL targets, overrides config
|
|
8634
9294
|
--exclude <entities> Exclude these entities (comma-separated)
|
|
8635
9295
|
Applies to ALL targets, overrides config
|
|
9296
|
+
--with-form-schemas Generate Zod validation schemas, form defaults,
|
|
9297
|
+
and table column metadata
|
|
8636
9298
|
--watch, -w Watch mode - regenerate on schema changes
|
|
8637
9299
|
--dry-run Show what would be generated without writing files
|
|
8638
9300
|
--verbose, -v Verbose output
|
|
@@ -8646,6 +9308,11 @@ SQL Generation Options (generate:sql):
|
|
|
8646
9308
|
--readme Generate README.md documentation
|
|
8647
9309
|
--dry-run Show what would be generated without writing files
|
|
8648
9310
|
|
|
9311
|
+
AI Setup Options (setup:ai):
|
|
9312
|
+
--cursor Also generate .cursorrules for Cursor IDE
|
|
9313
|
+
--force Overwrite existing files without checking
|
|
9314
|
+
--dry-run Show what would be generated without writing
|
|
9315
|
+
|
|
8649
9316
|
Other Options:
|
|
8650
9317
|
--format, -f <format> Output format (json, yaml) for OpenAPI
|
|
8651
9318
|
--template, -t <name> Template name for init
|
|
@@ -8662,6 +9329,9 @@ Examples:
|
|
|
8662
9329
|
schemock generate:sql --only tables,indexes,rls
|
|
8663
9330
|
schemock generate:openapi --output api.yaml --format yaml
|
|
8664
9331
|
schemock generate:postman --output collection.json
|
|
9332
|
+
schemock setup:ai # Generate CLAUDE.md
|
|
9333
|
+
schemock setup:ai --cursor # Also generate .cursorrules
|
|
9334
|
+
schemock setup:ai --dry-run # Preview without writing
|
|
8665
9335
|
|
|
8666
9336
|
Entity Filtering (in config):
|
|
8667
9337
|
targets: [
|
|
@@ -8672,9 +9342,9 @@ Entity Filtering (in config):
|
|
|
8672
9342
|
}
|
|
8673
9343
|
function showVersion() {
|
|
8674
9344
|
try {
|
|
8675
|
-
const pkgPath = (0,
|
|
8676
|
-
if ((0,
|
|
8677
|
-
const pkg = JSON.parse((0,
|
|
9345
|
+
const pkgPath = (0, import_node_path9.resolve)(__dirname, "../package.json");
|
|
9346
|
+
if ((0, import_node_fs3.existsSync)(pkgPath)) {
|
|
9347
|
+
const pkg = JSON.parse((0, import_node_fs3.readFileSync)(pkgPath, "utf-8"));
|
|
8678
9348
|
console.log(`schemock v${pkg.version}`);
|
|
8679
9349
|
return;
|
|
8680
9350
|
}
|
|
@@ -8689,13 +9359,13 @@ Initializing Schemock project with template: ${template}
|
|
|
8689
9359
|
`);
|
|
8690
9360
|
const dirs = ["src/schemas"];
|
|
8691
9361
|
for (const dir of dirs) {
|
|
8692
|
-
if (!(0,
|
|
8693
|
-
(0,
|
|
9362
|
+
if (!(0, import_node_fs3.existsSync)(dir)) {
|
|
9363
|
+
(0, import_node_fs3.mkdirSync)(dir, { recursive: true });
|
|
8694
9364
|
console.log(` Created ${dir}/`);
|
|
8695
9365
|
}
|
|
8696
9366
|
}
|
|
8697
9367
|
const schemaPath = "src/schemas/user.ts";
|
|
8698
|
-
if (!(0,
|
|
9368
|
+
if (!(0, import_node_fs3.existsSync)(schemaPath)) {
|
|
8699
9369
|
const schemaContent = `import { defineData, field, hasMany, belongsTo } from 'schemock/schema';
|
|
8700
9370
|
|
|
8701
9371
|
/**
|
|
@@ -8739,11 +9409,17 @@ export const commentSchema = defineData('comment', {
|
|
|
8739
9409
|
},
|
|
8740
9410
|
});
|
|
8741
9411
|
`;
|
|
8742
|
-
(0,
|
|
9412
|
+
(0, import_node_fs3.writeFileSync)(schemaPath, schemaContent);
|
|
8743
9413
|
console.log(` Created ${schemaPath}`);
|
|
8744
9414
|
}
|
|
8745
9415
|
const configPath = "schemock.config.ts";
|
|
8746
|
-
|
|
9416
|
+
const defaultConfig = {
|
|
9417
|
+
schemas: "./src/schemas/**/*.ts",
|
|
9418
|
+
output: "./src/generated",
|
|
9419
|
+
adapter: "mock",
|
|
9420
|
+
apiPrefix: "/api"
|
|
9421
|
+
};
|
|
9422
|
+
if (!(0, import_node_fs3.existsSync)(configPath)) {
|
|
8747
9423
|
const configContent = `import { defineConfig } from 'schemock/cli';
|
|
8748
9424
|
|
|
8749
9425
|
export default defineConfig({
|
|
@@ -8780,15 +9456,37 @@ export default defineConfig({
|
|
|
8780
9456
|
},
|
|
8781
9457
|
});
|
|
8782
9458
|
`;
|
|
8783
|
-
(0,
|
|
9459
|
+
(0, import_node_fs3.writeFileSync)(configPath, configContent);
|
|
8784
9460
|
console.log(` Created ${configPath}`);
|
|
8785
9461
|
}
|
|
9462
|
+
console.log("\n\u{1F916} Setting up AI configuration...");
|
|
9463
|
+
const { generateClaudeMd: generateClaudeMd2 } = await Promise.resolve().then(() => (init_claude_md(), claude_md_exports));
|
|
9464
|
+
const claudeMdPath = (0, import_node_path9.resolve)("CLAUDE.md");
|
|
9465
|
+
let existingClaudeMd = "";
|
|
9466
|
+
if ((0, import_node_fs3.existsSync)(claudeMdPath)) {
|
|
9467
|
+
existingClaudeMd = (0, import_node_fs3.readFileSync)(claudeMdPath, "utf-8");
|
|
9468
|
+
}
|
|
9469
|
+
const claudeResult = generateClaudeMd2(defaultConfig, existingClaudeMd);
|
|
9470
|
+
if (claudeResult.modified || claudeResult.created) {
|
|
9471
|
+
(0, import_node_fs3.writeFileSync)(claudeMdPath, claudeResult.content, "utf-8");
|
|
9472
|
+
if (claudeResult.created) {
|
|
9473
|
+
console.log(" Created CLAUDE.md");
|
|
9474
|
+
} else {
|
|
9475
|
+
console.log(" Updated CLAUDE.md (added Schemock section)");
|
|
9476
|
+
}
|
|
9477
|
+
} else {
|
|
9478
|
+
console.log(" CLAUDE.md already has Schemock configuration");
|
|
9479
|
+
}
|
|
8786
9480
|
console.log("\n\u2713 Schemock initialized successfully!\n");
|
|
8787
9481
|
console.log("Next steps:");
|
|
8788
9482
|
console.log(" 1. Review and customize schemas in src/schemas/");
|
|
8789
9483
|
console.log(" 2. Run: npx schemock generate");
|
|
8790
9484
|
console.log(" 3. Import { useUsers, useCreateUser } from ./src/generated");
|
|
8791
9485
|
console.log("");
|
|
9486
|
+
console.log("AI Integration:");
|
|
9487
|
+
console.log(" \u2022 CLAUDE.md helps Claude Code understand your project");
|
|
9488
|
+
console.log(' \u2022 Run "npx schemock setup:ai --cursor" for Cursor IDE support');
|
|
9489
|
+
console.log("");
|
|
8792
9490
|
}
|
|
8793
9491
|
async function generateCommand(options) {
|
|
8794
9492
|
const { generate: generate2 } = await Promise.resolve().then(() => (init_generate(), generate_exports));
|
|
@@ -8800,7 +9498,8 @@ async function generateCommand(options) {
|
|
|
8800
9498
|
dryRun: options.dryRun,
|
|
8801
9499
|
verbose: options.verbose,
|
|
8802
9500
|
only: options.only,
|
|
8803
|
-
exclude: options.exclude
|
|
9501
|
+
exclude: options.exclude,
|
|
9502
|
+
withFormSchemas: options.withFormSchemas
|
|
8804
9503
|
});
|
|
8805
9504
|
}
|
|
8806
9505
|
async function generateSQLCommand(options) {
|
|
@@ -8835,7 +9534,7 @@ async function generateOpenAPICommand(options) {
|
|
|
8835
9534
|
`);
|
|
8836
9535
|
return;
|
|
8837
9536
|
}
|
|
8838
|
-
const outputPath = (0,
|
|
9537
|
+
const outputPath = (0, import_node_path9.resolve)(options.output);
|
|
8839
9538
|
const format = options.format || (outputPath.endsWith(".yaml") || outputPath.endsWith(".yml") ? "yaml" : "json");
|
|
8840
9539
|
const spec = generateOpenAPI2({
|
|
8841
9540
|
title: "API Documentation",
|
|
@@ -8843,11 +9542,21 @@ async function generateOpenAPICommand(options) {
|
|
|
8843
9542
|
description: "Generated by Schemock"
|
|
8844
9543
|
});
|
|
8845
9544
|
const output = format === "yaml" ? toYAML(spec) : JSON.stringify(spec, null, 2);
|
|
8846
|
-
(0,
|
|
8847
|
-
(0,
|
|
9545
|
+
(0, import_node_fs3.mkdirSync)((0, import_node_path9.dirname)(outputPath), { recursive: true });
|
|
9546
|
+
(0, import_node_fs3.writeFileSync)(outputPath, output);
|
|
8848
9547
|
console.log(` Generated: ${outputPath}`);
|
|
8849
9548
|
console.log("\n\u2713 OpenAPI specification generated successfully!\n");
|
|
8850
9549
|
}
|
|
9550
|
+
async function setupAICommand(options) {
|
|
9551
|
+
const { setupAI: setupAI2 } = await Promise.resolve().then(() => (init_setup_ai(), setup_ai_exports));
|
|
9552
|
+
await setupAI2({
|
|
9553
|
+
config: options.config,
|
|
9554
|
+
cursor: options.cursor,
|
|
9555
|
+
force: options.force,
|
|
9556
|
+
dryRun: options.dryRun,
|
|
9557
|
+
output: options.output
|
|
9558
|
+
});
|
|
9559
|
+
}
|
|
8851
9560
|
async function generatePostmanCommand(options) {
|
|
8852
9561
|
const { generatePostmanCollection: generatePostmanCollection2, registerSchemasForPostman: registerSchemasForPostman2 } = await Promise.resolve().then(() => (init_generator(), generator_exports));
|
|
8853
9562
|
console.log("\nGenerating Postman collection...");
|
|
@@ -8867,14 +9576,14 @@ async function generatePostmanCommand(options) {
|
|
|
8867
9576
|
`);
|
|
8868
9577
|
return;
|
|
8869
9578
|
}
|
|
8870
|
-
const outputPath = (0,
|
|
9579
|
+
const outputPath = (0, import_node_path9.resolve)(options.output);
|
|
8871
9580
|
const collection = generatePostmanCollection2({
|
|
8872
9581
|
name: "API Collection",
|
|
8873
9582
|
baseUrl: "http://localhost:3000",
|
|
8874
9583
|
description: "Generated by Schemock"
|
|
8875
9584
|
});
|
|
8876
|
-
(0,
|
|
8877
|
-
(0,
|
|
9585
|
+
(0, import_node_fs3.mkdirSync)((0, import_node_path9.dirname)(outputPath), { recursive: true });
|
|
9586
|
+
(0, import_node_fs3.writeFileSync)(outputPath, JSON.stringify(collection, null, 2));
|
|
8878
9587
|
console.log(` Generated: ${outputPath}`);
|
|
8879
9588
|
console.log("\n\u2713 Postman collection generated successfully!\n");
|
|
8880
9589
|
}
|
|
@@ -8940,6 +9649,9 @@ async function main() {
|
|
|
8940
9649
|
case "generate:sql":
|
|
8941
9650
|
await generateSQLCommand(options);
|
|
8942
9651
|
break;
|
|
9652
|
+
case "setup:ai":
|
|
9653
|
+
await setupAICommand(options);
|
|
9654
|
+
break;
|
|
8943
9655
|
default:
|
|
8944
9656
|
console.error(`Unknown command: ${command}`);
|
|
8945
9657
|
console.log('Run "schemock help" for usage information.');
|