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/index.js CHANGED
@@ -4272,6 +4272,307 @@ function generateEntityHooks(code, schema) {
4272
4272
  code.line();
4273
4273
  }
4274
4274
 
4275
+ // src/cli/generators/form-schemas.ts
4276
+ function generateFormSchemas(schemas) {
4277
+ const code = new CodeBuilder();
4278
+ code.line();
4279
+ code.comment("=".repeat(70));
4280
+ code.comment("FORM SCHEMAS - Zod validation, defaults, and column metadata");
4281
+ code.comment("=".repeat(70));
4282
+ code.line();
4283
+ code.line("import { z } from 'zod';");
4284
+ code.line();
4285
+ for (const schema of schemas) {
4286
+ if (schema.isJunctionTable) continue;
4287
+ generateEntityFormSchemas(code, schema);
4288
+ }
4289
+ generateColumnTypes(code);
4290
+ return code.toString();
4291
+ }
4292
+ function generateEntityFormSchemas(code, schema) {
4293
+ const { pascalName, fields } = schema;
4294
+ const formFields = fields.filter((f) => f.name !== "id" && !f.readOnly && !f.isComputed);
4295
+ code.multiDocComment([
4296
+ `Zod validation schema for ${pascalName} forms`,
4297
+ "",
4298
+ "Use with react-hook-form:",
4299
+ "```typescript",
4300
+ "import { useForm } from 'react-hook-form';",
4301
+ "import { zodResolver } from '@hookform/resolvers/zod';",
4302
+ "",
4303
+ "const form = useForm({",
4304
+ ` resolver: zodResolver(${pascalName}FormSchema),`,
4305
+ ` defaultValues: ${pascalName}FormDefaults,`,
4306
+ "});",
4307
+ "```"
4308
+ ]);
4309
+ code.block(`export const ${pascalName}FormSchema = z.object({`, () => {
4310
+ for (const field of formFields) {
4311
+ const zodType = fieldToZodType(field);
4312
+ code.line(`${field.name}: ${zodType},`);
4313
+ }
4314
+ }, "});");
4315
+ code.line();
4316
+ code.docComment(`Default values for ${pascalName} form initialization`);
4317
+ code.block(`export const ${pascalName}FormDefaults: z.input<typeof ${pascalName}FormSchema> = {`, () => {
4318
+ for (const field of formFields) {
4319
+ const defaultValue = getFieldDefault(field);
4320
+ code.line(`${field.name}: ${defaultValue},`);
4321
+ }
4322
+ }, "};");
4323
+ code.line();
4324
+ code.docComment(`Inferred type from ${pascalName}FormSchema`);
4325
+ code.line(`export type ${pascalName}FormData = z.infer<typeof ${pascalName}FormSchema>;`);
4326
+ code.line();
4327
+ const tableFields = fields.filter((f) => !f.isComputed || f.name === "id");
4328
+ code.docComment(`Table column definitions for ${pascalName}`);
4329
+ code.block(`export const ${pascalName}TableColumns: ColumnDef[] = [`, () => {
4330
+ for (const field of tableFields) {
4331
+ const columnDef = fieldToColumnDef(field);
4332
+ code.line(`${columnDef},`);
4333
+ }
4334
+ }, "];");
4335
+ code.line();
4336
+ const columnKeys = tableFields.map((f) => `'${f.name}'`).join(" | ");
4337
+ code.docComment(`Valid column keys for ${pascalName} table`);
4338
+ code.line(`export type ${pascalName}ColumnKey = ${columnKeys};`);
4339
+ code.line();
4340
+ }
4341
+ function fieldToZodType(field) {
4342
+ let zodType;
4343
+ if (field.isEnum && field.enumValues && field.enumValues.length > 0) {
4344
+ const enumLiterals = field.enumValues.map((v) => `'${v}'`).join(", ");
4345
+ zodType = `z.enum([${enumLiterals}])`;
4346
+ } else if (field.isArray && field.itemType) {
4347
+ const itemZod = fieldToZodType(field.itemType);
4348
+ zodType = `z.array(${itemZod})`;
4349
+ } else if (field.isObject && field.shape) {
4350
+ const shapeFields = Object.entries(field.shape).map(([key, f]) => `${key}: ${fieldToZodType(f)}`).join(", ");
4351
+ zodType = `z.object({ ${shapeFields} })`;
4352
+ } else if (field.isRef) {
4353
+ zodType = "z.string().uuid()";
4354
+ } else {
4355
+ zodType = baseTypeToZod(field.type, field);
4356
+ }
4357
+ zodType = applyConstraints(zodType, field);
4358
+ if (field.nullable) {
4359
+ zodType = `${zodType}.nullable()`;
4360
+ }
4361
+ if (field.hasDefault) {
4362
+ zodType = `${zodType}.optional()`;
4363
+ }
4364
+ return zodType;
4365
+ }
4366
+ function baseTypeToZod(type, field) {
4367
+ const lowerType = type.toLowerCase();
4368
+ if (["string", "text", "varchar", "char", "uuid", "citext"].includes(lowerType) || lowerType.startsWith("varchar")) {
4369
+ if (lowerType === "uuid") {
4370
+ return "z.string().uuid()";
4371
+ }
4372
+ return "z.string()";
4373
+ }
4374
+ if (lowerType === "email" || field.type.includes("email")) {
4375
+ return "z.string().email()";
4376
+ }
4377
+ if (lowerType === "url" || field.type.includes("url")) {
4378
+ return "z.string().url()";
4379
+ }
4380
+ if (["int", "integer", "smallint", "serial", "smallserial"].includes(lowerType)) {
4381
+ return "z.number().int()";
4382
+ }
4383
+ if (["bigint", "bigserial"].includes(lowerType)) {
4384
+ return "z.bigint()";
4385
+ }
4386
+ if (["float", "double", "real", "double precision"].includes(lowerType)) {
4387
+ return "z.number()";
4388
+ }
4389
+ if (["decimal", "numeric", "money"].includes(lowerType)) {
4390
+ return 'z.string().regex(/^-?\\d+(\\.\\d+)?$/, "Must be a valid decimal number")';
4391
+ }
4392
+ if (["boolean", "bool"].includes(lowerType)) {
4393
+ return "z.boolean()";
4394
+ }
4395
+ if (["date", "datetime", "timestamp", "timestamptz"].includes(lowerType)) {
4396
+ return "z.coerce.date()";
4397
+ }
4398
+ if (["time", "timetz", "interval"].includes(lowerType)) {
4399
+ return "z.string()";
4400
+ }
4401
+ if (["json", "jsonb"].includes(lowerType)) {
4402
+ return "z.unknown()";
4403
+ }
4404
+ switch (field.tsType) {
4405
+ case "string":
4406
+ return "z.string()";
4407
+ case "number":
4408
+ return "z.number()";
4409
+ case "boolean":
4410
+ return "z.boolean()";
4411
+ case "Date":
4412
+ return "z.coerce.date()";
4413
+ case "bigint":
4414
+ return "z.bigint()";
4415
+ default:
4416
+ return "z.unknown()";
4417
+ }
4418
+ }
4419
+ function applyConstraints(zodType, field) {
4420
+ const constraints = [];
4421
+ if (field.tsType === "string" || field.type === "string" || field.type === "text") {
4422
+ if (field.min !== void 0) {
4423
+ constraints.push(`.min(${field.min})`);
4424
+ }
4425
+ if (field.max !== void 0) {
4426
+ constraints.push(`.max(${field.max})`);
4427
+ }
4428
+ if (field.pattern) {
4429
+ const escapedPattern = field.pattern.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
4430
+ constraints.push(`.regex(/${escapedPattern}/)`);
4431
+ }
4432
+ }
4433
+ if (field.tsType === "number" || field.type === "number" || field.type === "int") {
4434
+ if (field.min !== void 0) {
4435
+ constraints.push(`.min(${field.min})`);
4436
+ }
4437
+ if (field.max !== void 0) {
4438
+ constraints.push(`.max(${field.max})`);
4439
+ }
4440
+ }
4441
+ if (!field.nullable && !field.hasDefault) {
4442
+ if ((field.tsType === "string" || field.type === "string") && field.min === void 0 && !zodType.includes(".email()") && !zodType.includes(".url()") && !zodType.includes(".uuid()")) {
4443
+ constraints.push(`.min(1, '${formatFieldName(field.name)} is required')`);
4444
+ }
4445
+ }
4446
+ return zodType + constraints.join("");
4447
+ }
4448
+ function getFieldDefault(field) {
4449
+ if (field.hasDefault && field.defaultValue !== void 0) {
4450
+ return formatDefaultValue2(field.defaultValue);
4451
+ }
4452
+ if (field.isEnum && field.enumValues && field.enumValues.length > 0) {
4453
+ return `'${field.enumValues[0]}'`;
4454
+ }
4455
+ if (field.isArray) {
4456
+ return "[]";
4457
+ }
4458
+ if (field.isObject && field.shape) {
4459
+ const shapeDefaults = Object.entries(field.shape).map(([key, f]) => `${key}: ${getFieldDefault(f)}`).join(", ");
4460
+ return `{ ${shapeDefaults} }`;
4461
+ }
4462
+ if (field.isRef) {
4463
+ return "''";
4464
+ }
4465
+ if (field.nullable) {
4466
+ return "null";
4467
+ }
4468
+ switch (field.tsType) {
4469
+ case "string":
4470
+ return "''";
4471
+ case "number":
4472
+ return "0";
4473
+ case "boolean":
4474
+ return "false";
4475
+ case "Date":
4476
+ return "new Date()";
4477
+ case "bigint":
4478
+ return "BigInt(0)";
4479
+ default:
4480
+ return "undefined";
4481
+ }
4482
+ }
4483
+ function formatDefaultValue2(value, field) {
4484
+ if (value === null) return "null";
4485
+ if (value === void 0) return "undefined";
4486
+ if (typeof value === "string") {
4487
+ return `'${value.replace(/'/g, "\\'")}'`;
4488
+ }
4489
+ if (typeof value === "number" || typeof value === "boolean") {
4490
+ return String(value);
4491
+ }
4492
+ if (typeof value === "bigint") {
4493
+ return `BigInt(${value})`;
4494
+ }
4495
+ if (value instanceof Date) {
4496
+ return `new Date('${value.toISOString()}')`;
4497
+ }
4498
+ if (Array.isArray(value)) {
4499
+ return JSON.stringify(value);
4500
+ }
4501
+ if (typeof value === "object") {
4502
+ return JSON.stringify(value);
4503
+ }
4504
+ return "undefined";
4505
+ }
4506
+ function fieldToColumnDef(field) {
4507
+ const parts = [];
4508
+ parts.push(`key: '${field.name}'`);
4509
+ parts.push(`label: '${formatFieldName(field.name)}'`);
4510
+ const columnType = getColumnType(field);
4511
+ parts.push(`type: '${columnType}'`);
4512
+ const sortable = !field.isComputed && !field.isObject && !field.isArray;
4513
+ parts.push(`sortable: ${sortable}`);
4514
+ const filterable = !field.isComputed && !field.isObject;
4515
+ parts.push(`filterable: ${filterable}`);
4516
+ const hidden = field.name === "id" || field.name.endsWith("Id") || field.name === "createdAt" || field.name === "updatedAt";
4517
+ if (hidden) {
4518
+ parts.push(`hidden: true`);
4519
+ }
4520
+ return `{ ${parts.join(", ")} }`;
4521
+ }
4522
+ function getColumnType(field) {
4523
+ if (field.isEnum) return "enum";
4524
+ if (field.isArray) return "array";
4525
+ if (field.isObject) return "object";
4526
+ if (field.isRef) return "ref";
4527
+ const lowerType = field.type.toLowerCase();
4528
+ if (["boolean", "bool"].includes(lowerType)) return "boolean";
4529
+ if (["date", "datetime", "timestamp", "timestamptz"].includes(lowerType)) return "date";
4530
+ if (["time", "timetz"].includes(lowerType)) return "time";
4531
+ if (["number", "int", "integer", "float", "double", "decimal", "numeric", "money", "bigint"].includes(
4532
+ lowerType
4533
+ ))
4534
+ return "number";
4535
+ if (field.tsType === "number") return "number";
4536
+ if (lowerType === "email" || field.type.includes("email")) return "email";
4537
+ if (lowerType === "url" || field.type.includes("url")) return "url";
4538
+ if (lowerType === "uuid") return "id";
4539
+ return "text";
4540
+ }
4541
+ function formatFieldName(name) {
4542
+ return name.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
4543
+ }
4544
+ function generateColumnTypes(code) {
4545
+ code.comment("=".repeat(70));
4546
+ code.comment("COLUMN TYPES");
4547
+ code.comment("=".repeat(70));
4548
+ code.line();
4549
+ code.docComment("Column type for table rendering and behavior");
4550
+ code.line(
4551
+ "export type ColumnType = 'text' | 'number' | 'boolean' | 'date' | 'time' | 'email' | 'url' | 'id' | 'enum' | 'ref' | 'array' | 'object';"
4552
+ );
4553
+ code.line();
4554
+ code.docComment("Table column definition");
4555
+ code.block("export interface ColumnDef {", () => {
4556
+ code.line("/** Field key in the data object */");
4557
+ code.line("key: string;");
4558
+ code.line("/** Display label for column header */");
4559
+ code.line("label: string;");
4560
+ code.line("/** Column type for rendering and alignment */");
4561
+ code.line("type: ColumnType;");
4562
+ code.line("/** Whether column is sortable */");
4563
+ code.line("sortable: boolean;");
4564
+ code.line("/** Whether column is filterable */");
4565
+ code.line("filterable: boolean;");
4566
+ code.line("/** Whether column is hidden by default */");
4567
+ code.line("hidden?: boolean;");
4568
+ code.line("/** Custom width (CSS value) */");
4569
+ code.line("width?: string;");
4570
+ code.line("/** Custom render function name */");
4571
+ code.line("render?: string;");
4572
+ });
4573
+ code.line();
4574
+ }
4575
+
4275
4576
  // src/cli/generators/nextjs-api/route-template.ts
4276
4577
  function generateRouteFile(schema, target, _config) {
4277
4578
  const hasAuth = target.middleware?.auth !== void 0;
@@ -6298,10 +6599,14 @@ async function generate(options) {
6298
6599
  if (analyzedEndpoints.length > 0) {
6299
6600
  typesCode += generateEndpointTypes(analyzedEndpoints);
6300
6601
  }
6602
+ if (options.withFormSchemas) {
6603
+ typesCode += generateFormSchemas(analyzed);
6604
+ }
6301
6605
  await writeOutput4(path.join(outputDir, "types.ts"), typesCode, options.dryRun);
6302
6606
  const entityCount = analyzed.filter((s) => !s.isJunctionTable).length;
6303
6607
  const endpointInfo = analyzedEndpoints.length > 0 ? ` + ${analyzedEndpoints.length} endpoint types` : "";
6304
- console.log(` \u2713 types.ts (${entityCount} entities + Create/Update/Filter types${endpointInfo})
6608
+ const formSchemaInfo = options.withFormSchemas ? " + form schemas" : "";
6609
+ console.log(` \u2713 types.ts (${entityCount} entities + Create/Update/Filter types${endpointInfo}${formSchemaInfo})
6305
6610
  `);
6306
6611
  console.log(`\u{1F50C} Generating ${adapter} adapter...`);
6307
6612
  switch (adapter) {
@@ -6446,6 +6751,319 @@ async function writeOutput4(path, content, dryRun) {
6446
6751
  await promises.writeFile(path, content, "utf-8");
6447
6752
  }
6448
6753
 
6754
+ // src/cli/generators/claude-md.ts
6755
+ var SECTION_START_MARKER = "<!-- SCHEMOCK:START - AI instructions for Schemock. Do not remove this marker -->";
6756
+ var SECTION_END_MARKER = "<!-- SCHEMOCK:END -->";
6757
+ function generateSchemockSection(config) {
6758
+ const lines = [];
6759
+ lines.push(SECTION_START_MARKER);
6760
+ lines.push("");
6761
+ lines.push("## Schemock - AI Instructions");
6762
+ lines.push("");
6763
+ lines.push("This project uses [Schemock](https://github.com/prajyot-tote/schemock) for schema-first code generation.");
6764
+ lines.push("");
6765
+ lines.push("### Generated Files - DO NOT MODIFY");
6766
+ lines.push("");
6767
+ lines.push("The following directories contain auto-generated code. **NEVER edit these files directly.**");
6768
+ lines.push("Changes will be overwritten on next `npx schemock generate`.");
6769
+ lines.push("");
6770
+ const outputDirs = getOutputDirectories(config);
6771
+ for (const dir of outputDirs) {
6772
+ lines.push(`- \`${dir}/**/*\``);
6773
+ }
6774
+ lines.push("");
6775
+ lines.push("### How to Make Changes");
6776
+ lines.push("");
6777
+ lines.push("To modify generated types, hooks, or clients:");
6778
+ lines.push("");
6779
+ lines.push(`1. **Edit schema files** in \`${config.schemas.replace("/**/*.ts", "/")}\``);
6780
+ lines.push("2. **Run generation**: `npx schemock generate`");
6781
+ lines.push("3. **Import from generated directory**");
6782
+ lines.push("");
6783
+ lines.push("### Schema DSL Quick Reference");
6784
+ lines.push("");
6785
+ lines.push("```typescript");
6786
+ lines.push("import { defineData, field, hasMany, belongsTo } from 'schemock/schema';");
6787
+ lines.push("");
6788
+ lines.push("export const userSchema = defineData('user', {");
6789
+ lines.push(" id: field.uuid(),");
6790
+ lines.push(" email: field.email().unique(),");
6791
+ lines.push(" name: field.string(),");
6792
+ lines.push(" role: field.enum(['admin', 'user']).default('user'),");
6793
+ lines.push(" avatar: field.url().nullable(),");
6794
+ lines.push(" createdAt: field.timestamp().default(new Date()),");
6795
+ lines.push("});");
6796
+ lines.push("");
6797
+ lines.push("export const postSchema = defineData('post', {");
6798
+ lines.push(" id: field.uuid(),");
6799
+ lines.push(" title: field.string(),");
6800
+ lines.push(" content: field.text(),");
6801
+ lines.push(" authorId: field.ref('user'),");
6802
+ lines.push("}, {");
6803
+ lines.push(" relations: {");
6804
+ lines.push(" author: belongsTo('user', 'authorId'),");
6805
+ lines.push(" },");
6806
+ lines.push("});");
6807
+ lines.push("```");
6808
+ lines.push("");
6809
+ lines.push("### Available Field Types");
6810
+ lines.push("");
6811
+ lines.push("| Type | Description | Example |");
6812
+ lines.push("|------|-------------|---------|");
6813
+ lines.push("| `field.uuid()` | UUID primary key | `id: field.uuid()` |");
6814
+ lines.push("| `field.string()` | Text string | `name: field.string()` |");
6815
+ lines.push("| `field.text()` | Long text | `content: field.text()` |");
6816
+ lines.push("| `field.email()` | Email address | `email: field.email()` |");
6817
+ lines.push("| `field.url()` | URL string | `avatar: field.url()` |");
6818
+ lines.push("| `field.int()` | Integer number | `age: field.int()` |");
6819
+ lines.push("| `field.float()` | Decimal number | `price: field.float()` |");
6820
+ lines.push("| `field.boolean()` | True/false | `active: field.boolean()` |");
6821
+ lines.push("| `field.enum([...])` | Enum values | `status: field.enum(['draft', 'published'])` |");
6822
+ lines.push("| `field.timestamp()` | Date/time | `createdAt: field.timestamp()` |");
6823
+ lines.push("| `field.date()` | Date only | `birthDate: field.date()` |");
6824
+ lines.push("| `field.ref('entity')` | Foreign key | `authorId: field.ref('user')` |");
6825
+ lines.push("| `field.json()` | JSON object | `metadata: field.json()` |");
6826
+ lines.push("");
6827
+ lines.push("### Field Modifiers");
6828
+ lines.push("");
6829
+ lines.push("- `.nullable()` - Field can be null");
6830
+ lines.push("- `.default(value)` - Default value");
6831
+ lines.push("- `.unique()` - Must be unique");
6832
+ lines.push("- `.index()` - Create database index");
6833
+ lines.push("");
6834
+ lines.push("### Relations");
6835
+ lines.push("");
6836
+ lines.push("```typescript");
6837
+ lines.push("import { hasMany, belongsTo, hasOne, manyToMany } from 'schemock/schema';");
6838
+ lines.push("");
6839
+ lines.push("// One-to-many: User has many Posts");
6840
+ lines.push("hasMany('post', 'authorId')");
6841
+ lines.push("");
6842
+ lines.push("// Many-to-one: Post belongs to User");
6843
+ lines.push("belongsTo('user', 'authorId')");
6844
+ lines.push("");
6845
+ lines.push("// One-to-one: User has one Profile");
6846
+ lines.push("hasOne('profile', 'userId')");
6847
+ lines.push("");
6848
+ lines.push("// Many-to-many: Post has many Tags");
6849
+ lines.push("manyToMany('tag', 'post_tags')");
6850
+ lines.push("```");
6851
+ lines.push("");
6852
+ lines.push("### Common Tasks");
6853
+ lines.push("");
6854
+ lines.push("| Task | What to do |");
6855
+ lines.push("|------|------------|");
6856
+ lines.push("| Add new entity | Create new schema file in `src/schemas/`, run `npx schemock generate` |");
6857
+ lines.push("| Add field | Edit schema file, run `npx schemock generate` |");
6858
+ lines.push("| Add relation | Add to schema `relations` object, run `npx schemock generate` |");
6859
+ lines.push("| Change field type | Edit schema file, run `npx schemock generate` |");
6860
+ lines.push("| Fix generated code bug | Report issue, don't edit generated files |");
6861
+ lines.push("");
6862
+ lines.push("### CLI Commands");
6863
+ lines.push("");
6864
+ lines.push("```bash");
6865
+ lines.push("# Generate all code from schemas");
6866
+ lines.push("npx schemock generate");
6867
+ lines.push("");
6868
+ lines.push("# Generate for specific adapter");
6869
+ lines.push("npx schemock generate --adapter supabase");
6870
+ lines.push("");
6871
+ lines.push("# Generate SQL migrations");
6872
+ lines.push("npx schemock generate:sql");
6873
+ lines.push("");
6874
+ lines.push("# Dry run (show what would be generated)");
6875
+ lines.push("npx schemock generate --dry-run");
6876
+ lines.push("```");
6877
+ lines.push("");
6878
+ lines.push(SECTION_END_MARKER);
6879
+ return lines.join("\n");
6880
+ }
6881
+ function getOutputDirectories(config) {
6882
+ const dirs = /* @__PURE__ */ new Set();
6883
+ if (config.output) {
6884
+ dirs.add(normalizeOutputPath(config.output));
6885
+ }
6886
+ if (config.targets && config.targets.length > 0) {
6887
+ for (const target of config.targets) {
6888
+ dirs.add(normalizeOutputPath(target.output));
6889
+ }
6890
+ }
6891
+ if (dirs.size === 0) {
6892
+ dirs.add("./src/generated");
6893
+ }
6894
+ return Array.from(dirs).sort();
6895
+ }
6896
+ function normalizeOutputPath(path) {
6897
+ if (!path.startsWith("./") && !path.startsWith("/")) {
6898
+ return `./${path}`;
6899
+ }
6900
+ return path;
6901
+ }
6902
+ function mergeClaudeMdContent(existingContent, schemockSection) {
6903
+ const startIndex = existingContent.indexOf(SECTION_START_MARKER);
6904
+ const endIndex = existingContent.indexOf(SECTION_END_MARKER);
6905
+ if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
6906
+ const before = existingContent.substring(0, startIndex);
6907
+ const after = existingContent.substring(endIndex + SECTION_END_MARKER.length);
6908
+ const oldSection = existingContent.substring(startIndex, endIndex + SECTION_END_MARKER.length);
6909
+ if (oldSection === schemockSection) {
6910
+ return { content: existingContent, wasUpdated: false };
6911
+ }
6912
+ return {
6913
+ content: before + schemockSection + after,
6914
+ wasUpdated: true
6915
+ };
6916
+ }
6917
+ const separator = existingContent.trim() ? "\n\n" : "";
6918
+ return {
6919
+ content: existingContent.trim() + separator + schemockSection + "\n",
6920
+ wasUpdated: true
6921
+ };
6922
+ }
6923
+ function validateExistingContent(content) {
6924
+ const warnings = [];
6925
+ const startCount = (content.match(/SCHEMOCK:START/g) || []).length;
6926
+ const endCount = (content.match(/SCHEMOCK:END/g) || []).length;
6927
+ if (startCount !== endCount) {
6928
+ warnings.push(
6929
+ `Found mismatched Schemock markers (${startCount} START, ${endCount} END). Section will be appended instead of replaced.`
6930
+ );
6931
+ }
6932
+ if (startCount > 1 || endCount > 1) {
6933
+ warnings.push(
6934
+ "Found multiple Schemock sections. Only the first will be replaced."
6935
+ );
6936
+ }
6937
+ return {
6938
+ isValid: warnings.length === 0,
6939
+ warnings
6940
+ };
6941
+ }
6942
+ function generateClaudeMd(config, existingContent = "") {
6943
+ const warnings = [];
6944
+ if (existingContent) {
6945
+ const validation = validateExistingContent(existingContent);
6946
+ warnings.push(...validation.warnings);
6947
+ }
6948
+ const schemockSection = generateSchemockSection(config);
6949
+ const { content, wasUpdated } = mergeClaudeMdContent(existingContent, schemockSection);
6950
+ return {
6951
+ created: !existingContent,
6952
+ modified: wasUpdated,
6953
+ content,
6954
+ path: "CLAUDE.md",
6955
+ warnings
6956
+ };
6957
+ }
6958
+ function generateCursorRules(config) {
6959
+ const outputDirs = getOutputDirectories(config);
6960
+ const lines = [];
6961
+ lines.push("# Schemock Rules for Cursor");
6962
+ lines.push("");
6963
+ lines.push("## Generated Files - DO NOT MODIFY");
6964
+ lines.push("");
6965
+ lines.push("Never edit files in these directories:");
6966
+ for (const dir of outputDirs) {
6967
+ lines.push(`- ${dir}/**/*`);
6968
+ }
6969
+ lines.push("");
6970
+ lines.push("To make changes, edit schema files and run: npx schemock generate");
6971
+ lines.push("");
6972
+ lines.push(`Schema files location: ${config.schemas}`);
6973
+ lines.push("");
6974
+ return lines.join("\n");
6975
+ }
6976
+
6977
+ // src/cli/commands/setup-ai.ts
6978
+ async function setupAI(options = {}) {
6979
+ console.log("\n\u{1F916} Schemock AI Setup\n");
6980
+ const config = await loadConfig(options.config);
6981
+ const outputDir = options.output || process.cwd();
6982
+ const claudeMdPath = path.resolve(outputDir, "CLAUDE.md");
6983
+ let existingContent = "";
6984
+ if (fs.existsSync(claudeMdPath)) {
6985
+ console.log("\u{1F4C4} Found existing CLAUDE.md");
6986
+ existingContent = fs.readFileSync(claudeMdPath, "utf-8");
6987
+ } else {
6988
+ console.log("\u{1F4C4} No existing CLAUDE.md found - will create new file");
6989
+ }
6990
+ const claudeMdResult = generateClaudeMd(config, existingContent);
6991
+ claudeMdResult.path = claudeMdPath;
6992
+ if (claudeMdResult.warnings.length > 0) {
6993
+ console.log("\n\u26A0\uFE0F Warnings:");
6994
+ for (const warning of claudeMdResult.warnings) {
6995
+ console.log(` ${warning}`);
6996
+ }
6997
+ console.log("");
6998
+ }
6999
+ if (options.dryRun) {
7000
+ console.log("\n[DRY RUN] Would write CLAUDE.md:");
7001
+ console.log("\u2500".repeat(60));
7002
+ const sectionMatch = claudeMdResult.content.match(/<!-- SCHEMOCK:START[\s\S]*?SCHEMOCK:END -->/);
7003
+ if (sectionMatch) {
7004
+ console.log(sectionMatch[0]);
7005
+ }
7006
+ console.log("\u2500".repeat(60));
7007
+ } else if (claudeMdResult.modified || claudeMdResult.created) {
7008
+ fs.writeFileSync(claudeMdPath, claudeMdResult.content, "utf-8");
7009
+ if (claudeMdResult.created) {
7010
+ console.log(` \u2713 Created ${claudeMdPath}`);
7011
+ } else {
7012
+ console.log(` \u2713 Updated ${claudeMdPath} (Schemock section)`);
7013
+ }
7014
+ } else {
7015
+ console.log(" \u2139 CLAUDE.md is already up to date");
7016
+ }
7017
+ let cursorResult;
7018
+ if (options.cursor) {
7019
+ const cursorPath = path.resolve(outputDir, ".cursorrules");
7020
+ const cursorExists = fs.existsSync(cursorPath);
7021
+ console.log("\n\u{1F4C4} Generating .cursorrules for Cursor IDE...");
7022
+ const cursorContent = generateCursorRules(config);
7023
+ if (options.dryRun) {
7024
+ console.log("\n[DRY RUN] Would write .cursorrules:");
7025
+ console.log("\u2500".repeat(60));
7026
+ console.log(cursorContent);
7027
+ console.log("\u2500".repeat(60));
7028
+ cursorResult = { created: !cursorExists, modified: true, path: cursorPath };
7029
+ } else {
7030
+ let shouldWrite = true;
7031
+ if (cursorExists && !options.force) {
7032
+ const existingCursor = fs.readFileSync(cursorPath, "utf-8");
7033
+ if (!existingCursor.includes("Schemock Rules for Cursor")) {
7034
+ console.log(" \u26A0\uFE0F Existing .cursorrules was not created by Schemock");
7035
+ console.log(" Use --force to overwrite, or manually add Schemock rules");
7036
+ shouldWrite = false;
7037
+ } else if (existingCursor === cursorContent) {
7038
+ console.log(" \u2139 .cursorrules is already up to date");
7039
+ shouldWrite = false;
7040
+ }
7041
+ }
7042
+ if (shouldWrite) {
7043
+ fs.writeFileSync(cursorPath, cursorContent, "utf-8");
7044
+ console.log(` \u2713 ${cursorExists ? "Updated" : "Created"} ${cursorPath}`);
7045
+ }
7046
+ cursorResult = { created: !cursorExists, modified: shouldWrite, path: cursorPath };
7047
+ }
7048
+ }
7049
+ console.log("\n\u2705 AI setup complete!\n");
7050
+ console.log("What this does:");
7051
+ console.log(" \u2022 CLAUDE.md tells Claude Code about your generated files");
7052
+ console.log(" \u2022 Helps AI avoid modifying auto-generated code");
7053
+ console.log(" \u2022 Provides schema DSL reference for AI assistance");
7054
+ if (options.cursor) {
7055
+ console.log(" \u2022 .cursorrules provides similar guidance for Cursor IDE");
7056
+ }
7057
+ console.log("\nNext steps:");
7058
+ console.log(" 1. Commit CLAUDE.md to your repository");
7059
+ console.log(" 2. Claude Code will now understand your Schemock project");
7060
+ console.log("");
7061
+ return {
7062
+ claudeMd: claudeMdResult,
7063
+ cursorRules: cursorResult
7064
+ };
7065
+ }
7066
+
6449
7067
  exports.CodeBuilder = CodeBuilder;
6450
7068
  exports.analyzeSchemas = analyzeSchemas;
6451
7069
  exports.defineConfig = defineConfig;
@@ -6453,20 +7071,26 @@ exports.discoverSchemas = discoverSchemas;
6453
7071
  exports.fieldToFakerCall = fieldToFakerCall;
6454
7072
  exports.fieldToTsType = fieldToTsType;
6455
7073
  exports.generate = generate;
7074
+ exports.generateClaudeMd = generateClaudeMd;
7075
+ exports.generateCursorRules = generateCursorRules;
6456
7076
  exports.generateFetchClient = generateFetchClient;
6457
7077
  exports.generateFirebaseClient = generateFirebaseClient;
7078
+ exports.generateFormSchemas = generateFormSchemas;
6458
7079
  exports.generateHooks = generateHooks;
6459
7080
  exports.generateMockClient = generateMockClient;
6460
7081
  exports.generateMockDb = generateMockDb;
6461
7082
  exports.generateMockHandlers = generateMockHandlers;
7083
+ exports.generateSchemockSection = generateSchemockSection;
6462
7084
  exports.generateSeed = generateSeed;
6463
7085
  exports.generateSupabaseClient = generateSupabaseClient;
6464
7086
  exports.generateTypes = generateTypes;
6465
7087
  exports.getDefaultConfig = getDefaultConfig;
6466
7088
  exports.getRelativePath = getRelativePath;
6467
7089
  exports.loadConfig = loadConfig;
7090
+ exports.mergeClaudeMdContent = mergeClaudeMdContent;
6468
7091
  exports.pluralize = pluralize;
6469
7092
  exports.primitiveToTs = primitiveToTs;
7093
+ exports.setupAI = setupAI;
6470
7094
  exports.toCamelCase = toCamelCase;
6471
7095
  exports.toPascalCase = toPascalCase;
6472
7096
  //# sourceMappingURL=index.js.map