routesync 1.0.15 → 1.0.17

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.
Files changed (3) hide show
  1. package/README.md +14 -0
  2. package/dist/cli.js +49 -9
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -249,6 +249,20 @@ const api = defineApi({
249
249
  }, config)
250
250
  ```
251
251
 
252
+ > **Note on Laravel Auto-generation:** When using `routesync sync` or `routesync scan` with the `--zod` flag, RouteSync uses PHP Reflection to automatically generate Zod schemas based on your backend validation rules.
253
+ >
254
+ > **Important:** To ensure your schemas are detected automatically, you **must use Laravel `FormRequest` classes**. Inline `$request->validate([...])` calls inside Controller methods cannot be reliably extracted.
255
+ >
256
+ > ```php
257
+ > // ✅ DO THIS: RouteSync will generate Zod schemas automatically
258
+ > public function store(StoreProductRequest $request)
259
+ >
260
+ > // ❌ AVOID THIS: Validation rules will be ignored
261
+ > public function store(Request $request) {
262
+ > $request->validate([...]);
263
+ > }
264
+ > ```
265
+
252
266
  ---
253
267
 
254
268
  ### Auto-generate TanStack hooks from defineApi
package/dist/cli.js CHANGED
@@ -8406,6 +8406,31 @@ foreach ($routes as $route) {
8406
8406
  }
8407
8407
  }
8408
8408
  }
8409
+
8410
+ // Fallback: Try to parse $request->validate([...]) from source code
8411
+ if (empty($schema)) {
8412
+ $fileName = $reflector->getFileName();
8413
+ $startLine = $reflector->getStartLine();
8414
+ $endLine = $reflector->getEndLine();
8415
+
8416
+ if ($fileName && $startLine !== false && $endLine !== false) {
8417
+ $lines = file($fileName);
8418
+ // startLine is 1-indexed
8419
+ $methodSource = implode("", array_slice($lines, $startLine - 1, $endLine - $startLine + 1));
8420
+
8421
+ // Look for $request->validate([ ... ])
8422
+ if (preg_match('/\\\\$request->validate\\\\s*\\\\(\\\\s*\\\\[(.*?)\\\\]\\\\s*\\\\)/s', $methodSource, $matches)) {
8423
+ $rulesString = $matches[1];
8424
+ // Match 'field' => 'rules'
8425
+ preg_match_all('~[\\'"]([a-zA-Z0-9_.*]+)[\\'"]\\\\s*=>\\\\s*[\\'"](.*?)[\\'"]~', $rulesString, $ruleMatches);
8426
+ if (!empty($ruleMatches[1])) {
8427
+ foreach ($ruleMatches[1] as $index => $field) {
8428
+ $schema[$field] = $ruleMatches[2][$index];
8429
+ }
8430
+ }
8431
+ }
8432
+ }
8433
+ }
8409
8434
  } catch (\\Exception $e) {}
8410
8435
  }
8411
8436
  }
@@ -8751,6 +8776,11 @@ var TypeGenerator = class {
8751
8776
  const isOptional = col.nullable ? "?" : "";
8752
8777
  lines.push(` ${col.name}${isOptional}: ${tsType}`);
8753
8778
  }
8779
+ if (model.appends && model.appends.length > 0) {
8780
+ for (const append of model.appends) {
8781
+ lines.push(` ${append}?: unknown`);
8782
+ }
8783
+ }
8754
8784
  lines.push(`}`);
8755
8785
  lines.push(``);
8756
8786
  }
@@ -8770,17 +8800,26 @@ var TypeGenerator = class {
8770
8800
  }
8771
8801
  }
8772
8802
  if (hasSchemas) {
8803
+ const generatedSchemas = /* @__PURE__ */ new Map();
8773
8804
  for (const route of manifest.routes) {
8774
- if (route.schema && Object.keys(route.schema).length > 0) {
8775
- const actionName = toMethodName(route);
8776
- const schemaName = actionName + "Schema";
8805
+ if (route.schema && route.schema.rules && Object.keys(route.schema.rules).length > 0) {
8806
+ let actionName = toMethodName(route);
8807
+ let schemaName = actionName + "Schema";
8808
+ if (generatedSchemas.has(schemaName)) {
8809
+ const count = generatedSchemas.get(schemaName) + 1;
8810
+ generatedSchemas.set(schemaName, count);
8811
+ actionName = actionName + String(count);
8812
+ schemaName = actionName + "Schema";
8813
+ } else {
8814
+ generatedSchemas.set(schemaName, 1);
8815
+ }
8777
8816
  lines.push(`export const ${schemaName} = z.object({`);
8778
- for (const [field, rules] of Object.entries(route.schema)) {
8817
+ for (const [field, rules] of Object.entries(route.schema.rules)) {
8779
8818
  const ruleStr = Array.isArray(rules) ? rules.join("|") : String(rules);
8780
8819
  let zodRule = "z.string()";
8781
- if (ruleStr.includes("numeric") || ruleStr.includes("integer")) {
8820
+ if (ruleStr.includes("numeric") || ruleStr.includes("integer") || ruleStr.includes("int")) {
8782
8821
  zodRule = "z.number()";
8783
- } else if (ruleStr.includes("boolean")) {
8822
+ } else if (ruleStr.includes("boolean") || ruleStr.includes("bool")) {
8784
8823
  zodRule = "z.boolean()";
8785
8824
  } else if (ruleStr.includes("array")) {
8786
8825
  zodRule = "z.array(z.unknown())";
@@ -8797,7 +8836,8 @@ var TypeGenerator = class {
8797
8836
  if (ruleStr.includes("nullable")) {
8798
8837
  zodRule += ".nullable()";
8799
8838
  }
8800
- lines.push(` ${field}: ${zodRule},`);
8839
+ const safeField = field.match(/^[a-zA-Z_$][a-zA-Z0-9_$]*$/) ? field : `"${field}"`;
8840
+ lines.push(` ${safeField}: ${zodRule},`);
8801
8841
  }
8802
8842
  lines.push(`})`);
8803
8843
  lines.push(`export type ${toTypeName(actionName + "Payload")} = z.infer<typeof ${schemaName}>`);
@@ -9100,7 +9140,7 @@ var ModelGenerator = class {
9100
9140
  };
9101
9141
 
9102
9142
  // packages/cli/src/commands/generate.ts
9103
- var generateCommand = new Command("generate").description("Generate typed SDK, types, and hooks from route manifest").option("-m, --manifest <path>", "Path to route manifest", "routesync.manifest.json").option("-o, --output <path>", "Output directory", "src/api").option("--no-hooks", "Skip generating React hooks").option("--next-actions", "Generate Next.js Server Actions").option("--msw", "Generate MSW Mock Handlers").option("--echo", "Generate Laravel Echo Hooks").action(async (options) => {
9143
+ var generateCommand = new Command("generate").description("Generate typed SDK, types, and hooks from route manifest").option("-m, --manifest <path>", "Path to route manifest", "routesync.manifest.json").option("-o, --output <path>", "Output directory", "src/api").option("--no-hooks", "Skip generating React hooks").option("--next-actions", "Generate Next.js Server Actions").option("--msw", "Generate MSW Mock Handlers").option("--echo", "Generate Laravel Echo Hooks").option("--zod", "Generate Zod schemas for validation").action(async (options) => {
9104
9144
  const spinner = ora("Generating SDK...").start();
9105
9145
  try {
9106
9146
  if (!import_fs_extra11.default.existsSync(options.manifest)) {
@@ -9113,7 +9153,7 @@ var generateCommand = new Command("generate").description("Generate typed SDK, t
9113
9153
  spinner.text = "Generating types...";
9114
9154
  await TypeGenerator.generate(manifest, options.output);
9115
9155
  spinner.text = "Generating SDK...";
9116
- await SDKGenerator.generate(manifest, options.output);
9156
+ await SDKGenerator.generate(manifest, options.output, options);
9117
9157
  if (options.hooks !== false) {
9118
9158
  spinner.text = "Generating hooks...";
9119
9159
  await HookGenerator.generate(manifest, options.output);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "routesync",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "description": "Laravel routes to typed frontend SDKs.",
5
5
  "main": "./dist/sdk.js",
6
6
  "module": "./dist/sdk.mjs",