vite-plugin-ferry 0.1.2 → 0.1.4

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 CHANGED
@@ -10,10 +10,14 @@ in sync with your backend.
10
10
  - **Enums** — Generates types and runtime constants from `app/Enums/`
11
11
  - **Resources** — Generates response types from `app/Http/Resources/`
12
12
 
13
+ ## Requirements
14
+
15
+ - **TypeScript ^5.0** — Required as a peer dependency for code generation
16
+
13
17
  ## Installation
14
18
 
15
19
  ```bash
16
- npm install vite-plugin-ferry --save-dev
20
+ npm install vite-plugin-ferry typescript@^5 --save-dev
17
21
  ```
18
22
 
19
23
  Add it to your `vite.config.ts`:
@@ -33,9 +37,249 @@ Import your backend types directly in your frontend code:
33
37
 
34
38
  ```ts
35
39
  import { OrderStatus } from '@ferry/enums';
36
- import { UserResource } from '@ferry/resources';
40
+ import type { UserResource } from '@ferry/resources';
41
+ ```
42
+
43
+ ## Examples
44
+
45
+ ### Enums
46
+
47
+ #### String-backed enum with labels
48
+
49
+ When your enum has a `label()` method, Ferry generates a typed constant object:
50
+
51
+ ```php
52
+ // app/Enums/OrderStatus.php
53
+ enum OrderStatus: string
54
+ {
55
+ case Pending = 'pending';
56
+ case Shipped = 'shipped';
57
+ case Delivered = 'delivered';
58
+
59
+ public function label(): string
60
+ {
61
+ return match ($this) {
62
+ self::Pending => 'Pending Order',
63
+ self::Shipped => 'Shipped',
64
+ self::Delivered => 'Delivered',
65
+ };
66
+ }
67
+ }
68
+ ```
69
+
70
+ Generates:
71
+
72
+ ```ts
73
+ // @ferry/enums
74
+ export declare const OrderStatus: {
75
+ Pending: { value: 'pending'; label: 'Pending Order' };
76
+ Shipped: { value: 'shipped'; label: 'Shipped' };
77
+ Delivered: { value: 'delivered'; label: 'Delivered' };
78
+ };
79
+ ```
80
+
81
+ #### String-backed enum without labels
82
+
83
+ Simple string enums become TypeScript enums:
84
+
85
+ ```php
86
+ // app/Enums/Role.php
87
+ enum Role: string
88
+ {
89
+ case ADMIN = 'admin';
90
+ case USER = 'user';
91
+ case GUEST = 'guest';
92
+ }
37
93
  ```
38
94
 
95
+ Generates:
96
+
97
+ ```ts
98
+ // @ferry/enums
99
+ export enum Role {
100
+ ADMIN = 'admin',
101
+ USER = 'user',
102
+ GUEST = 'guest',
103
+ }
104
+ ```
105
+
106
+ #### Int-backed enum
107
+
108
+ Integer enums work the same way:
109
+
110
+ ```php
111
+ // app/Enums/Priority.php
112
+ enum Priority: int
113
+ {
114
+ case LOW = 1;
115
+ case MEDIUM = 2;
116
+ case HIGH = 3;
117
+ case URGENT = 4;
118
+ }
119
+ ```
120
+
121
+ Generates:
122
+
123
+ ```ts
124
+ // @ferry/enums
125
+ export enum Priority {
126
+ LOW = 1,
127
+ MEDIUM = 2,
128
+ HIGH = 3,
129
+ URGENT = 4,
130
+ }
131
+ ```
132
+
133
+ #### Unit enum (no backing type)
134
+
135
+ Unit enums use their case names as values:
136
+
137
+ ```php
138
+ // app/Enums/Color.php
139
+ enum Color
140
+ {
141
+ case RED;
142
+ case GREEN;
143
+ case BLUE;
144
+ }
145
+ ```
146
+
147
+ Generates:
148
+
149
+ ```ts
150
+ // @ferry/enums
151
+ export enum Color {
152
+ RED = 'RED',
153
+ GREEN = 'GREEN',
154
+ BLUE = 'BLUE',
155
+ }
156
+ ```
157
+
158
+ ### Resources
159
+
160
+ #### Basic resource
161
+
162
+ ```php
163
+ // app/Http/Resources/UserResource.php
164
+ class UserResource extends JsonResource
165
+ {
166
+ public function toArray(Request $request): array
167
+ {
168
+ return [
169
+ 'id' => $this->resource->id,
170
+ 'name' => $this->resource->name,
171
+ 'email' => $this->resource->email,
172
+ 'is_admin' => $this->resource->is_admin,
173
+ 'created_at' => $this->resource->created_at,
174
+ ];
175
+ }
176
+ }
177
+ ```
178
+
179
+ Generates:
180
+
181
+ ```ts
182
+ // @ferry/resources
183
+ export type UserResource = {
184
+ id: string;
185
+ name: string;
186
+ email: string;
187
+ is_admin: boolean;
188
+ created_at: string;
189
+ };
190
+ ```
191
+
192
+ #### Resource with relations
193
+
194
+ Fields using `whenLoaded()` become optional and resolve to the correct resource type:
195
+
196
+ ```php
197
+ // app/Http/Resources/PostResource.php
198
+ class PostResource extends JsonResource
199
+ {
200
+ public function toArray(Request $request): array
201
+ {
202
+ return [
203
+ 'id' => $this->resource->id,
204
+ 'title' => $this->resource->title,
205
+ 'slug' => $this->resource->slug,
206
+ 'is_published' => $this->resource->is_published,
207
+ 'author' => UserResource::make($this->whenLoaded('author')),
208
+ 'comments' => CommentResource::collection($this->whenLoaded('comments')),
209
+ 'created_at' => $this->resource->created_at,
210
+ ];
211
+ }
212
+ }
213
+ ```
214
+
215
+ Generates:
216
+
217
+ ```ts
218
+ // @ferry/resources
219
+ export type PostResource = {
220
+ id: string;
221
+ title: string;
222
+ slug: string;
223
+ is_published: boolean;
224
+ author?: UserResource[];
225
+ comments?: CommentResource[];
226
+ created_at: string;
227
+ };
228
+ ```
229
+
230
+ #### Resource with nested objects
231
+
232
+ Inline array structures become typed objects:
233
+
234
+ ```php
235
+ // app/Http/Resources/OrderResource.php
236
+ class OrderResource extends JsonResource
237
+ {
238
+ public function toArray(Request $request): array
239
+ {
240
+ return [
241
+ 'id' => $this->resource->id,
242
+ 'total' => $this->resource->total,
243
+ 'status' => $this->resource->status,
244
+ 'items' => $this->resource->items,
245
+ 'user' => $this->whenLoaded('user'),
246
+ 'shipping_address' => [
247
+ 'street' => $this->resource->address_street,
248
+ 'city' => $this->resource->address_city,
249
+ 'zip' => $this->resource->address_zip,
250
+ ],
251
+ 'created_at' => $this->resource->created_at,
252
+ ];
253
+ }
254
+ }
255
+ ```
256
+
257
+ Generates:
258
+
259
+ ```ts
260
+ // @ferry/resources
261
+ export type OrderResource = {
262
+ id: string;
263
+ total: string;
264
+ status: string;
265
+ items: string;
266
+ user?: UserResource;
267
+ shipping_address: { street: string; city: string; zip: string };
268
+ created_at: string;
269
+ };
270
+ ```
271
+
272
+ ## Publishing
273
+
274
+ To publish a new version:
275
+
276
+ ```bash
277
+ npm version patch # or minor, major
278
+ git push --follow-tags
279
+ ```
280
+
281
+ This bumps the version, creates a commit and tag, then pushes both to trigger the publish workflow.
282
+
39
283
  ## License
40
284
 
41
285
  See [LICENSE](LICENSE) for details.
@@ -3,6 +3,7 @@ export type EnumGeneratorOptions = {
3
3
  enumsDir: string;
4
4
  outputDir: string;
5
5
  packageName: string;
6
+ prettyPrint?: boolean;
6
7
  };
7
8
  /**
8
9
  * Generate TypeScript type declarations for enums.
@@ -11,9 +12,10 @@ export declare function generateEnumTypeScript(enums: Record<string, EnumDefinit
11
12
  /**
12
13
  * Generate runtime JavaScript for enums.
13
14
  */
14
- export declare function generateEnumRuntime(enums: Record<string, EnumDefinition>): string;
15
+ export declare function generateEnumRuntime(enums: Record<string, EnumDefinition>, prettyPrint?: boolean): string;
15
16
  /**
16
17
  * Collect all enum definitions from the enums directory.
18
+ * This is a plugin-level function that handles file I/O.
17
19
  */
18
20
  export declare function collectEnums(enumsDir: string): Record<string, EnumDefinition>;
19
21
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"enums.d.ts","sourceRoot":"","sources":["../../src/generators/enums.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE5E,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,MAAM,CAyCpF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,MAAM,CAyBjF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAuB7E;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CA6BjE"}
1
+ {"version":3,"file":"enums.d.ts","sourceRoot":"","sources":["../../src/generators/enums.ts"],"names":[],"mappings":"AAIA,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAc/E,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,MAAM,CA4BpF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,EAAE,WAAW,UAAO,GAAG,MAAM,CAgCrG;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA0B7E;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CA6BjE"}
@@ -1,81 +1,71 @@
1
+ import ts from 'typescript';
1
2
  import { existsSync } from 'node:fs';
2
3
  import { join } from 'node:path';
3
- import { getPhpFiles, writeFileEnsureDir } from '../utils/file.js';
4
- import { parseEnumFile } from '../utils/php-parser.js';
4
+ import { getPhpFiles, readFileSafe, writeFileEnsureDir } from '../utils/file.js';
5
+ import { parseEnumContent } from '../utils/php-parser.js';
6
+ import { printNodes, createEnum, createConstObject, createObjectLiteral, createDeclareConstWithType, createTypeLiteral, createStringLiteral, createNumericLiteral, createExportDefault, printNode, } from '../utils/ts-generator.js';
5
7
  /**
6
8
  * Generate TypeScript type declarations for enums.
7
9
  */
8
10
  export function generateEnumTypeScript(enums) {
9
- const lines = [];
10
- lines.push('// This file is auto-generated by the primcloud Vite plugin.');
11
- lines.push('// Do not edit directly.');
12
- lines.push('');
11
+ const nodes = [];
13
12
  for (const enumName of Object.keys(enums)) {
14
13
  const enumDef = enums[enumName];
15
14
  const hasLabels = enumDef.cases.some((c) => c.label);
16
15
  if (hasLabels) {
17
- // Generate a const object with typed properties for enums with labels
18
- lines.push(`export declare const ${enumDef.name}: {`);
19
- for (const c of enumDef.cases) {
20
- const val = String(c.value).replace(/'/g, "\\'");
21
- const label = c.label ? String(c.label).replace(/'/g, "\\'") : val;
22
- lines.push(` ${c.key}: { value: '${val}'; label: '${label}' };`);
23
- }
24
- lines.push('};');
25
- lines.push('');
16
+ // Generate a declare const with typed properties for enums with labels
17
+ const properties = enumDef.cases.map((c) => ({
18
+ name: c.key,
19
+ type: createTypeLiteral([
20
+ { name: 'value', type: ts.factory.createLiteralTypeNode(createStringLiteral(String(c.value))) },
21
+ {
22
+ name: 'label',
23
+ type: ts.factory.createLiteralTypeNode(createStringLiteral(c.label || String(c.value))),
24
+ },
25
+ ]),
26
+ }));
27
+ nodes.push(createDeclareConstWithType(enumDef.name, createTypeLiteral(properties)));
26
28
  }
27
29
  else {
28
- // Generate a traditional enum for enums without labels
29
- lines.push(`export enum ${enumDef.name} {`);
30
- for (const c of enumDef.cases) {
31
- const val = c.value;
32
- if (enumDef.backing === 'int' || enumDef.backing === 'integer') {
33
- if (!isNaN(Number(val))) {
34
- lines.push(` ${c.key} = ${val},`);
35
- }
36
- else {
37
- lines.push(` ${c.key} = '${val}',`);
38
- }
39
- }
40
- else {
41
- lines.push(` ${c.key} = '${String(val).replace(/'/g, "\\'")}',`);
42
- }
43
- }
44
- lines.push('}');
45
- lines.push('');
30
+ // Generate a traditional enum
31
+ nodes.push(createEnum(enumDef.name, enumDef.cases.map((c) => ({ key: c.key, value: c.value }))));
46
32
  }
47
33
  }
48
- return lines.join('\n');
34
+ return nodes.length > 0 ? printNodes(nodes) + '\n' : '';
49
35
  }
50
36
  /**
51
37
  * Generate runtime JavaScript for enums.
52
38
  */
53
- export function generateEnumRuntime(enums) {
54
- const lines = [];
55
- lines.push('// Auto-generated by primcloud Vite plugin');
56
- lines.push('');
39
+ export function generateEnumRuntime(enums, prettyPrint = true) {
40
+ const nodes = [];
57
41
  for (const enumName of Object.keys(enums)) {
58
42
  const enumDef = enums[enumName];
59
43
  const hasLabels = enumDef.cases.some((c) => c.label);
60
- lines.push(`export const ${enumDef.name} = {`);
61
- for (const c of enumDef.cases) {
62
- const val = String(c.value).replace(/'/g, "\\'");
44
+ const properties = enumDef.cases.map((c) => {
45
+ let value;
63
46
  if (hasLabels) {
64
- const label = c.label ? String(c.label).replace(/'/g, "\\'") : val;
65
- lines.push(` ${c.key}: { value: '${val}', label: '${label}' },`);
47
+ value = createObjectLiteral([
48
+ { key: 'value', value: createStringLiteral(String(c.value)) },
49
+ { key: 'label', value: createStringLiteral(c.label || String(c.value)) },
50
+ ], prettyPrint);
51
+ }
52
+ else if (typeof c.value === 'number') {
53
+ value = createNumericLiteral(c.value);
66
54
  }
67
55
  else {
68
- lines.push(` ${c.key}: '${val}',`);
56
+ value = createStringLiteral(String(c.value));
69
57
  }
70
- }
71
- lines.push('};');
72
- lines.push('');
58
+ return { key: c.key, value };
59
+ });
60
+ nodes.push(createConstObject(enumDef.name, properties));
73
61
  }
74
- lines.push('export default {};');
75
- return lines.join('\n');
62
+ // Add export default {}
63
+ nodes.push(createExportDefault(ts.factory.createObjectLiteralExpression([])));
64
+ return nodes.map(printNode).join('\n\n') + '\n';
76
65
  }
77
66
  /**
78
67
  * Collect all enum definitions from the enums directory.
68
+ * This is a plugin-level function that handles file I/O.
79
69
  */
80
70
  export function collectEnums(enumsDir) {
81
71
  const enums = {};
@@ -86,7 +76,10 @@ export function collectEnums(enumsDir) {
86
76
  for (const file of enumFiles) {
87
77
  try {
88
78
  const enumPath = join(enumsDir, file);
89
- const def = parseEnumFile(enumPath);
79
+ const content = readFileSafe(enumPath);
80
+ if (!content)
81
+ continue;
82
+ const def = parseEnumContent(content);
90
83
  if (def) {
91
84
  enums[def.name] = def;
92
85
  }
@@ -102,7 +95,7 @@ export function collectEnums(enumsDir) {
102
95
  * Generate enum files (TypeScript declarations and runtime JavaScript).
103
96
  */
104
97
  export function generateEnums(options) {
105
- const { enumsDir, outputDir, packageName } = options;
98
+ const { enumsDir, outputDir, packageName, prettyPrint = true } = options;
106
99
  // Collect all enums
107
100
  const enums = collectEnums(enumsDir);
108
101
  // Generate TypeScript declarations
@@ -110,7 +103,7 @@ export function generateEnums(options) {
110
103
  const dtsPath = join(outputDir, 'index.d.ts');
111
104
  writeFileEnsureDir(dtsPath, dtsContent);
112
105
  // Generate runtime JavaScript
113
- const jsContent = generateEnumRuntime(enums);
106
+ const jsContent = generateEnumRuntime(enums, prettyPrint);
114
107
  const jsPath = join(outputDir, 'index.js');
115
108
  writeFileEnsureDir(jsPath, jsContent);
116
109
  // Generate package.json
@@ -1,18 +1,17 @@
1
+ import { type ResourceFieldInfo } from '../utils/php-parser.js';
1
2
  export type ResourceGeneratorOptions = {
2
3
  resourcesDir: string;
3
4
  enumsDir: string;
4
5
  modelsDir: string;
5
6
  outputDir: string;
6
7
  packageName: string;
8
+ prettyPrint?: boolean;
7
9
  };
8
- type FieldInfo = {
9
- type: string;
10
- optional: boolean;
11
- };
10
+ export type FieldInfo = ResourceFieldInfo;
12
11
  /**
13
12
  * Generate TypeScript type declarations for resources.
14
13
  */
15
- export declare function generateResourceTypeScript(resources: Record<string, Record<string, FieldInfo>>, fallbacks: string[], referencedEnums: Set<string>): string;
14
+ export declare function generateResourceTypeScript(resources: Record<string, Record<string, ResourceFieldInfo>>, fallbacks: string[], referencedEnums: Set<string>): string;
16
15
  /**
17
16
  * Generate runtime JavaScript for resources.
18
17
  * Resources are type-only, so this just exports an empty object.
@@ -22,5 +21,4 @@ export declare function generateResourceRuntime(): string;
22
21
  * Generate resource type files (TypeScript declarations and runtime JavaScript).
23
22
  */
24
23
  export declare function generateResources(options: ResourceGeneratorOptions): void;
25
- export {};
26
24
  //# sourceMappingURL=resources.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../src/generators/resources.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,wBAAwB,GAAG;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,KAAK,SAAS,GAAG;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAiOF;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,EACpD,SAAS,EAAE,MAAM,EAAE,EACnB,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,GAC3B,MAAM,CAmCR;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAOhD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CAqEzE"}
1
+ {"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../src/generators/resources.ts"],"names":[],"mappings":"AAIA,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,wBAAwB,CAAC;AAUhC,MAAM,MAAM,wBAAwB,GAAG;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAGF,MAAM,MAAM,SAAS,GAAG,iBAAiB,CAAC;AAE1C;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,EAC5D,SAAS,EAAE,MAAM,EAAE,EACnB,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,GAC3B,MAAM,CAuCR;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CAoEzE"}