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