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/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
- console.log(` \u2713 types.ts (${entityCount} entities + Create/Update/Filter types${endpointInfo})
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 = formatDefaultValue2(field.defaultValue, field);
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 formatDefaultValue2(value, field) {
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 import_node_fs2 = require("fs");
8567
- var import_node_path8 = require("path");
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, import_node_path8.resolve)(__dirname, "../package.json");
8676
- if ((0, import_node_fs2.existsSync)(pkgPath)) {
8677
- const pkg = JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf-8"));
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, import_node_fs2.existsSync)(dir)) {
8693
- (0, import_node_fs2.mkdirSync)(dir, { recursive: true });
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, import_node_fs2.existsSync)(schemaPath)) {
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, import_node_fs2.writeFileSync)(schemaPath, schemaContent);
9412
+ (0, import_node_fs3.writeFileSync)(schemaPath, schemaContent);
8743
9413
  console.log(` Created ${schemaPath}`);
8744
9414
  }
8745
9415
  const configPath = "schemock.config.ts";
8746
- if (!(0, import_node_fs2.existsSync)(configPath)) {
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, import_node_fs2.writeFileSync)(configPath, configContent);
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, import_node_path8.resolve)(options.output);
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, import_node_fs2.mkdirSync)((0, import_node_path8.dirname)(outputPath), { recursive: true });
8847
- (0, import_node_fs2.writeFileSync)(outputPath, output);
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, import_node_path8.resolve)(options.output);
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, import_node_fs2.mkdirSync)((0, import_node_path8.dirname)(outputPath), { recursive: true });
8877
- (0, import_node_fs2.writeFileSync)(outputPath, JSON.stringify(collection, null, 2));
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.');