@strapi2front/generators 0.1.5 → 0.3.0

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/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import path7 from 'path';
1
+ import path9 from 'path';
2
2
  import * as prettier from 'prettier';
3
3
  import fs from 'fs/promises';
4
4
 
5
- // src/types/generator.ts
5
+ // src/output/typescript/types.ts
6
6
  async function formatCode(code) {
7
7
  try {
8
8
  return await prettier.format(code, {
@@ -38,7 +38,7 @@ async function ensureDir(dirPath) {
38
38
  }
39
39
  }
40
40
  async function writeFile(filePath, content) {
41
- const dir = path7.dirname(filePath);
41
+ const dir = path9.dirname(filePath);
42
42
  await ensureDir(dir);
43
43
  await fs.writeFile(filePath, content, "utf-8");
44
44
  }
@@ -65,7 +65,7 @@ async function deleteFile(filePath) {
65
65
  async function listFiles(dirPath, extension) {
66
66
  try {
67
67
  const entries = await fs.readdir(dirPath, { withFileTypes: true });
68
- let files = entries.filter((entry) => entry.isFile()).map((entry) => path7.join(dirPath, entry.name));
68
+ let files = entries.filter((entry) => entry.isFile()).map((entry) => path9.join(dirPath, entry.name));
69
69
  if (extension) {
70
70
  files = files.filter((file) => file.endsWith(extension));
71
71
  }
@@ -96,33 +96,179 @@ function pluralize(word) {
96
96
  return word + "s";
97
97
  }
98
98
 
99
- // src/types/generator.ts
100
- async function generateTypes(schema, options) {
99
+ // src/shared/type-mapper.ts
100
+ function mapAttributeToType(attr, _components) {
101
+ switch (attr.type) {
102
+ case "string":
103
+ case "text":
104
+ case "richtext":
105
+ case "email":
106
+ case "password":
107
+ case "uid":
108
+ return { type: "string", needsImport: false };
109
+ case "blocks":
110
+ return {
111
+ type: "BlocksContent",
112
+ needsImport: true,
113
+ import: { name: "BlocksContent", from: "utils" }
114
+ };
115
+ case "integer":
116
+ case "biginteger":
117
+ case "float":
118
+ case "decimal":
119
+ return { type: "number", needsImport: false };
120
+ case "boolean":
121
+ return { type: "boolean", needsImport: false };
122
+ case "date":
123
+ case "time":
124
+ case "datetime":
125
+ case "timestamp":
126
+ return { type: "string", needsImport: false };
127
+ case "json":
128
+ return { type: "unknown", needsImport: false };
129
+ case "enumeration":
130
+ if ("enum" in attr && attr.enum) {
131
+ return {
132
+ type: attr.enum.map((v) => `'${v}'`).join(" | "),
133
+ needsImport: false
134
+ };
135
+ }
136
+ return { type: "string", needsImport: false };
137
+ case "media":
138
+ if ("multiple" in attr && attr.multiple) {
139
+ return {
140
+ type: "StrapiMedia[]",
141
+ needsImport: true,
142
+ import: { name: "StrapiMedia", from: "utils" }
143
+ };
144
+ }
145
+ return {
146
+ type: "StrapiMedia | null",
147
+ needsImport: true,
148
+ import: { name: "StrapiMedia", from: "utils" }
149
+ };
150
+ case "relation":
151
+ if ("target" in attr && attr.target) {
152
+ const targetName = attr.target.split(".").pop() || "unknown";
153
+ const typeName = toPascalCase(targetName);
154
+ const isMany = attr.relation === "oneToMany" || attr.relation === "manyToMany";
155
+ return {
156
+ type: isMany ? `${typeName}[]` : `${typeName} | null`,
157
+ needsImport: true,
158
+ import: { name: typeName, from: targetName, isRelation: true }
159
+ };
160
+ }
161
+ return { type: "unknown", needsImport: false };
162
+ case "component":
163
+ if ("component" in attr && attr.component) {
164
+ const componentName = attr.component.split(".").pop() || "unknown";
165
+ const typeName = toPascalCase(componentName);
166
+ if ("repeatable" in attr && attr.repeatable) {
167
+ return {
168
+ type: `${typeName}[]`,
169
+ needsImport: true,
170
+ import: { name: typeName, from: componentName, isComponent: true }
171
+ };
172
+ }
173
+ return {
174
+ type: `${typeName} | null`,
175
+ needsImport: true,
176
+ import: { name: typeName, from: componentName, isComponent: true }
177
+ };
178
+ }
179
+ return { type: "unknown", needsImport: false };
180
+ case "dynamiczone":
181
+ if ("components" in attr && attr.components) {
182
+ const types = attr.components.map((c) => {
183
+ const name = c.split(".").pop() || "unknown";
184
+ return toPascalCase(name);
185
+ });
186
+ return {
187
+ type: `(${types.join(" | ")})[]`,
188
+ needsImport: true
189
+ // Dynamic zones need multiple imports, handled separately
190
+ };
191
+ }
192
+ return { type: "unknown[]", needsImport: false };
193
+ default:
194
+ return { type: "unknown", needsImport: false };
195
+ }
196
+ }
197
+ function getAttributeComment(attr) {
198
+ const parts = [];
199
+ if (attr.required) {
200
+ parts.push("Required");
201
+ }
202
+ if ("minLength" in attr && attr.minLength !== void 0) {
203
+ parts.push(`Min length: ${attr.minLength}`);
204
+ }
205
+ if ("maxLength" in attr && attr.maxLength !== void 0) {
206
+ parts.push(`Max length: ${attr.maxLength}`);
207
+ }
208
+ if ("min" in attr && attr.min !== void 0) {
209
+ parts.push(`Min: ${attr.min}`);
210
+ }
211
+ if ("max" in attr && attr.max !== void 0) {
212
+ parts.push(`Max: ${attr.max}`);
213
+ }
214
+ return parts.length > 0 ? parts.join(", ") : null;
215
+ }
216
+ function extractDependencies(attributes, selfName) {
217
+ const relations = /* @__PURE__ */ new Set();
218
+ const components = /* @__PURE__ */ new Set();
219
+ for (const attr of Object.values(attributes)) {
220
+ if (attr.type === "relation" && "target" in attr && attr.target) {
221
+ const targetName = attr.target.split(".").pop() || "";
222
+ const typeName = toPascalCase(targetName);
223
+ if (typeName !== selfName && targetName) {
224
+ relations.add(targetName);
225
+ }
226
+ }
227
+ if (attr.type === "component" && "component" in attr && attr.component) {
228
+ const componentName = attr.component.split(".").pop() || "";
229
+ if (componentName) {
230
+ components.add(componentName);
231
+ }
232
+ }
233
+ if (attr.type === "dynamiczone" && "components" in attr && attr.components) {
234
+ for (const comp of attr.components) {
235
+ const componentName = comp.split(".").pop() || "";
236
+ if (componentName) {
237
+ components.add(componentName);
238
+ }
239
+ }
240
+ }
241
+ }
242
+ return { relations, components };
243
+ }
244
+
245
+ // src/output/typescript/types.ts
246
+ async function generateTypeScriptTypes(schema, options) {
101
247
  const { outputDir } = options;
102
248
  const generatedFiles = [];
103
- await ensureDir(path7.join(outputDir, "collections"));
104
- await ensureDir(path7.join(outputDir, "components"));
105
- const utilsPath = path7.join(outputDir, "utils.ts");
249
+ await ensureDir(path9.join(outputDir, "collections"));
250
+ await ensureDir(path9.join(outputDir, "components"));
251
+ const utilsPath = path9.join(outputDir, "utils.ts");
106
252
  const strapiVersion = options.strapiVersion ?? "v5";
107
253
  await writeFile(utilsPath, await formatCode(generateUtilityTypes(options.blocksRendererInstalled ?? false, strapiVersion)));
108
254
  generatedFiles.push(utilsPath);
109
255
  for (const collection of schema.collections) {
110
256
  const fileName = `${toKebabCase(collection.singularName)}.ts`;
111
- const filePath = path7.join(outputDir, "collections", fileName);
257
+ const filePath = path9.join(outputDir, "collections", fileName);
112
258
  const content = generateCollectionType(collection, schema.components);
113
259
  await writeFile(filePath, await formatCode(content));
114
260
  generatedFiles.push(filePath);
115
261
  }
116
262
  for (const single of schema.singles) {
117
263
  const fileName = `${toKebabCase(single.singularName)}.ts`;
118
- const filePath = path7.join(outputDir, "collections", fileName);
264
+ const filePath = path9.join(outputDir, "collections", fileName);
119
265
  const content = generateSingleType(single, schema.components);
120
266
  await writeFile(filePath, await formatCode(content));
121
267
  generatedFiles.push(filePath);
122
268
  }
123
269
  for (const component of schema.components) {
124
270
  const fileName = `${toKebabCase(component.name)}.ts`;
125
- const filePath = path7.join(outputDir, "components", fileName);
271
+ const filePath = path9.join(outputDir, "components", fileName);
126
272
  const content = generateComponentType(component);
127
273
  await writeFile(filePath, await formatCode(content));
128
274
  generatedFiles.push(filePath);
@@ -306,34 +452,6 @@ ${rawResponseTypes}
306
452
  ${blocksContentType}
307
453
  `;
308
454
  }
309
- function extractDependencies(attributes, selfName) {
310
- const relations = /* @__PURE__ */ new Set();
311
- const components = /* @__PURE__ */ new Set();
312
- for (const attr of Object.values(attributes)) {
313
- if (attr.type === "relation" && "target" in attr && attr.target) {
314
- const targetName = attr.target.split(".").pop() || "";
315
- const typeName = toPascalCase(targetName);
316
- if (typeName !== selfName && targetName) {
317
- relations.add(targetName);
318
- }
319
- }
320
- if (attr.type === "component" && "component" in attr && attr.component) {
321
- const componentName = attr.component.split(".").pop() || "";
322
- if (componentName) {
323
- components.add(componentName);
324
- }
325
- }
326
- if (attr.type === "dynamiczone" && "components" in attr && attr.components) {
327
- for (const comp of attr.components) {
328
- const componentName = comp.split(".").pop() || "";
329
- if (componentName) {
330
- components.add(componentName);
331
- }
332
- }
333
- }
334
- }
335
- return { relations, components };
336
- }
337
455
  function generateDependencyImports(relations, components) {
338
456
  const imports = [];
339
457
  for (const relation of relations) {
@@ -461,195 +579,369 @@ ${attributes}
461
579
  function generateAttributes(attributes, components) {
462
580
  const lines = [];
463
581
  for (const [name, attr] of Object.entries(attributes)) {
464
- const tsType = attributeToTsType(attr);
582
+ const mappedType = mapAttributeToType(attr);
465
583
  const optional = attr.required ? "" : "?";
466
584
  const comment = getAttributeComment(attr);
467
585
  if (comment) {
468
586
  lines.push(` /** ${comment} */`);
469
587
  }
470
- lines.push(` ${name}${optional}: ${tsType};`);
588
+ lines.push(` ${name}${optional}: ${mappedType.type};`);
471
589
  }
472
590
  return lines.join("\n");
473
591
  }
474
- function attributeToTsType(attr, _components) {
475
- switch (attr.type) {
476
- case "string":
477
- case "text":
478
- case "richtext":
479
- case "email":
480
- case "password":
481
- case "uid":
482
- return "string";
483
- case "blocks":
484
- return "BlocksContent";
485
- case "integer":
486
- case "biginteger":
487
- case "float":
488
- case "decimal":
489
- return "number";
490
- case "boolean":
491
- return "boolean";
492
- case "date":
493
- case "time":
494
- case "datetime":
495
- case "timestamp":
496
- return "string";
497
- case "json":
498
- return "unknown";
499
- case "enumeration":
500
- if ("enum" in attr && attr.enum) {
501
- return attr.enum.map((v) => `'${v}'`).join(" | ");
502
- }
503
- return "string";
504
- case "media":
505
- if ("multiple" in attr && attr.multiple) {
506
- return "StrapiMedia[]";
507
- }
508
- return "StrapiMedia | null";
509
- case "relation":
510
- if ("target" in attr && attr.target) {
511
- const targetName = toPascalCase(attr.target.split(".").pop() || "unknown");
512
- const isMany = attr.relation === "oneToMany" || attr.relation === "manyToMany";
513
- return isMany ? `${targetName}[]` : `${targetName} | null`;
514
- }
515
- return "unknown";
516
- case "component":
517
- if ("component" in attr && attr.component) {
518
- const componentName = toPascalCase(attr.component.split(".").pop() || "unknown");
519
- if ("repeatable" in attr && attr.repeatable) {
520
- return `${componentName}[]`;
521
- }
522
- return `${componentName} | null`;
523
- }
524
- return "unknown";
525
- case "dynamiczone":
526
- if ("components" in attr && attr.components) {
527
- const types = attr.components.map((c) => toPascalCase(c.split(".").pop() || "unknown"));
528
- return `(${types.join(" | ")})[]`;
529
- }
530
- return "unknown[]";
531
- default:
532
- return "unknown";
533
- }
534
- }
535
- function getAttributeComment(attr) {
536
- const parts = [];
537
- if (attr.required) {
538
- parts.push("Required");
539
- }
540
- if ("minLength" in attr && attr.minLength !== void 0) {
541
- parts.push(`Min length: ${attr.minLength}`);
542
- }
543
- if ("maxLength" in attr && attr.maxLength !== void 0) {
544
- parts.push(`Max length: ${attr.maxLength}`);
545
- }
546
- if ("min" in attr && attr.min !== void 0) {
547
- parts.push(`Min: ${attr.min}`);
548
- }
549
- if ("max" in attr && attr.max !== void 0) {
550
- parts.push(`Max: ${attr.max}`);
551
- }
552
- return parts.length > 0 ? parts.join(", ") : null;
553
- }
554
- async function generateServices(schema, options) {
555
- const { outputDir, typesImportPath, strapiVersion = "v5" } = options;
592
+ async function generateJSDocTypes(schema, options) {
593
+ const { outputDir } = options;
556
594
  const generatedFiles = [];
557
- await ensureDir(outputDir);
595
+ await ensureDir(path9.join(outputDir, "collections"));
596
+ await ensureDir(path9.join(outputDir, "components"));
597
+ const utilsPath = path9.join(outputDir, "utils.js");
598
+ const strapiVersion = options.strapiVersion ?? "v5";
599
+ await writeFile(utilsPath, generateUtilityTypes2(strapiVersion));
600
+ generatedFiles.push(utilsPath);
558
601
  for (const collection of schema.collections) {
559
- const fileName = `${toKebabCase(collection.singularName)}.service.ts`;
560
- const filePath = path7.join(outputDir, fileName);
561
- const content = generateCollectionService(collection, typesImportPath, strapiVersion);
562
- await writeFile(filePath, await formatCode(content));
602
+ const fileName = `${toKebabCase(collection.singularName)}.js`;
603
+ const filePath = path9.join(outputDir, "collections", fileName);
604
+ const content = generateCollectionType2(collection, schema.components, strapiVersion);
605
+ await writeFile(filePath, content);
563
606
  generatedFiles.push(filePath);
564
607
  }
565
608
  for (const single of schema.singles) {
566
- const fileName = `${toKebabCase(single.singularName)}.service.ts`;
567
- const filePath = path7.join(outputDir, fileName);
568
- const content = generateSingleService(single, typesImportPath, strapiVersion);
569
- await writeFile(filePath, await formatCode(content));
609
+ const fileName = `${toKebabCase(single.singularName)}.js`;
610
+ const filePath = path9.join(outputDir, "collections", fileName);
611
+ const content = generateSingleType2(single, schema.components, strapiVersion);
612
+ await writeFile(filePath, content);
613
+ generatedFiles.push(filePath);
614
+ }
615
+ for (const component of schema.components) {
616
+ const fileName = `${toKebabCase(component.name)}.js`;
617
+ const filePath = path9.join(outputDir, "components", fileName);
618
+ const content = generateComponentType2(component, strapiVersion);
619
+ await writeFile(filePath, content);
570
620
  generatedFiles.push(filePath);
571
621
  }
572
622
  return generatedFiles;
573
623
  }
574
- function generateCollectionService(collection, typesImportPath, strapiVersion) {
575
- const typeName = toPascalCase(collection.singularName);
576
- const serviceName = toCamelCase(collection.singularName) + "Service";
577
- const fileName = toKebabCase(collection.singularName);
578
- const endpoint = collection.pluralName;
624
+ function generateUtilityTypes2(strapiVersion) {
579
625
  const isV4 = strapiVersion === "v4";
580
- const idParam = isV4 ? "id: number" : "documentId: string";
581
- const idName = isV4 ? "id" : "documentId";
582
- const omitFields = isV4 ? "'id' | 'createdAt' | 'updatedAt' | 'publishedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt' | 'publishedAt'";
583
- const hasSlug = "slug" in collection.attributes;
584
- const { localized, draftAndPublish } = collection;
585
- const imports = [
586
- `import { collection } from '../client';`,
587
- `import type { ${typeName}, ${typeName}Filters } from '${typesImportPath}/collections/${fileName}';`,
588
- `import type { StrapiPagination } from '${typesImportPath}/utils';`
589
- ];
590
- if (localized) {
591
- imports.push(`import type { Locale } from '../locales';`);
592
- }
593
- const findManyOptionsFields = [
594
- ` filters?: ${typeName}Filters;`,
595
- ` pagination?: {`,
596
- ` /** Page number (1-indexed) - use with pageSize */`,
597
- ` page?: number;`,
598
- ` /** Number of items per page (default: 25) - use with page */`,
599
- ` pageSize?: number;`,
600
- ` /** Offset to start from (0-indexed) - use with limit */`,
601
- ` start?: number;`,
602
- ` /** Maximum number of items to return - use with start */`,
603
- ` limit?: number;`,
604
- ` };`,
605
- ` sort?: string | string[];`,
606
- ` populate?: string | string[] | Record<string, unknown>;`
607
- ];
608
- if (localized) {
609
- findManyOptionsFields.push(` locale?: Locale;`);
610
- }
611
- if (draftAndPublish) {
612
- findManyOptionsFields.push(` status?: 'draft' | 'published';`);
626
+ const baseEntityFields = isV4 ? ` * @property {number} id
627
+ * @property {string} createdAt
628
+ * @property {string} updatedAt
629
+ * @property {string|null} publishedAt` : ` * @property {number} id
630
+ * @property {string} documentId
631
+ * @property {string} createdAt
632
+ * @property {string} updatedAt
633
+ * @property {string|null} publishedAt`;
634
+ const mediaFields = isV4 ? ` * @property {number} id` : ` * @property {number} id
635
+ * @property {string} documentId`;
636
+ return `/**
637
+ * Strapi utility types (JSDoc)
638
+ * Generated by strapi2front
639
+ * Strapi version: ${strapiVersion}
640
+ *
641
+ * These types provide IntelliSense support in JavaScript projects.
642
+ */
643
+
644
+ /**
645
+ * Strapi media format
646
+ * @typedef {Object} StrapiMediaFormat
647
+ * @property {string} name
648
+ * @property {string} hash
649
+ * @property {string} ext
650
+ * @property {string} mime
651
+ * @property {number} width
652
+ * @property {number} height
653
+ * @property {number} size
654
+ * @property {string} url
655
+ */
656
+
657
+ /**
658
+ * Strapi media object
659
+ * @typedef {Object} StrapiMedia
660
+ ${mediaFields}
661
+ * @property {string} name
662
+ * @property {string|null} alternativeText
663
+ * @property {string|null} caption
664
+ * @property {number} width
665
+ * @property {number} height
666
+ * @property {{thumbnail?: StrapiMediaFormat, small?: StrapiMediaFormat, medium?: StrapiMediaFormat, large?: StrapiMediaFormat}|null} formats
667
+ * @property {string} hash
668
+ * @property {string} ext
669
+ * @property {string} mime
670
+ * @property {number} size
671
+ * @property {string} url
672
+ * @property {string|null} previewUrl
673
+ * @property {string} provider
674
+ * @property {string} createdAt
675
+ * @property {string} updatedAt
676
+ */
677
+
678
+ /**
679
+ * Strapi pagination
680
+ * @typedef {Object} StrapiPagination
681
+ * @property {number} page
682
+ * @property {number} pageSize
683
+ * @property {number} pageCount
684
+ * @property {number} total
685
+ */
686
+
687
+ /**
688
+ * Strapi response wrapper
689
+ * @template T
690
+ * @typedef {Object} StrapiResponse
691
+ * @property {T} data
692
+ * @property {{pagination?: StrapiPagination}} meta
693
+ */
694
+
695
+ /**
696
+ * Strapi list response
697
+ * @template T
698
+ * @typedef {Object} StrapiListResponse
699
+ * @property {T[]} data
700
+ * @property {{pagination: StrapiPagination}} meta
701
+ */
702
+
703
+ /**
704
+ * Base entity fields
705
+ * @typedef {Object} StrapiBaseEntity
706
+ ${baseEntityFields}
707
+ */
708
+
709
+ /**
710
+ * Blocks content type (rich text)
711
+ * @typedef {unknown[]} BlocksContent
712
+ */
713
+
714
+ // Export empty object to make this a module
715
+ export {};
716
+ `;
717
+ }
718
+ function generateCollectionType2(collection, components, strapiVersion) {
719
+ const typeName = toPascalCase(collection.singularName);
720
+ const isV4 = strapiVersion === "v4";
721
+ const { relations, components: componentDeps } = extractDependencies(collection.attributes, typeName);
722
+ const imports = generateJSDocImports(relations, componentDeps, "collection");
723
+ const properties = generateJSDocProperties(collection.attributes);
724
+ const baseFields = isV4 ? ` * @property {number} id
725
+ * @property {string} createdAt
726
+ * @property {string} updatedAt
727
+ * @property {string|null} publishedAt` : ` * @property {number} id
728
+ * @property {string} documentId
729
+ * @property {string} createdAt
730
+ * @property {string} updatedAt
731
+ * @property {string|null} publishedAt`;
732
+ return `/**
733
+ * ${collection.displayName}
734
+ * ${collection.description || ""}
735
+ * Generated by strapi2front
736
+ * Strapi version: ${strapiVersion}
737
+ */
738
+
739
+ ${imports}
740
+
741
+ /**
742
+ * ${collection.displayName}
743
+ * @typedef {Object} ${typeName}
744
+ ${baseFields}
745
+ ${properties}
746
+ */
747
+
748
+ /**
749
+ * ${typeName} filters for querying
750
+ * @typedef {Object} ${typeName}Filters
751
+ * @property {number|{$eq?: number, $ne?: number, $in?: number[], $notIn?: number[]}} [id]
752
+ * @property {string|{$eq?: string, $ne?: string}} [documentId]
753
+ * @property {string|{$eq?: string, $gt?: string, $gte?: string, $lt?: string, $lte?: string}} [createdAt]
754
+ * @property {string|{$eq?: string, $gt?: string, $gte?: string, $lt?: string, $lte?: string}} [updatedAt]
755
+ * @property {string|null|{$eq?: string, $ne?: string, $null?: boolean}} [publishedAt]
756
+ * @property {${typeName}Filters[]} [$and]
757
+ * @property {${typeName}Filters[]} [$or]
758
+ * @property {${typeName}Filters} [$not]
759
+ */
760
+
761
+ // Export empty object to make this a module
762
+ export {};
763
+ `;
764
+ }
765
+ function generateSingleType2(single, components, strapiVersion) {
766
+ const typeName = toPascalCase(single.singularName);
767
+ const isV4 = strapiVersion === "v4";
768
+ const { relations, components: componentDeps } = extractDependencies(single.attributes, typeName);
769
+ const imports = generateJSDocImports(relations, componentDeps, "collection");
770
+ const properties = generateJSDocProperties(single.attributes);
771
+ const baseFields = isV4 ? ` * @property {number} id
772
+ * @property {string} createdAt
773
+ * @property {string} updatedAt` : ` * @property {number} id
774
+ * @property {string} documentId
775
+ * @property {string} createdAt
776
+ * @property {string} updatedAt`;
777
+ return `/**
778
+ * ${single.displayName}
779
+ * ${single.description || ""}
780
+ * Generated by strapi2front
781
+ * Strapi version: ${strapiVersion}
782
+ */
783
+
784
+ ${imports}
785
+
786
+ /**
787
+ * ${single.displayName}
788
+ * @typedef {Object} ${typeName}
789
+ ${baseFields}
790
+ ${properties}
791
+ */
792
+
793
+ // Export empty object to make this a module
794
+ export {};
795
+ `;
796
+ }
797
+ function generateComponentType2(component, strapiVersion) {
798
+ const typeName = toPascalCase(component.name);
799
+ const { relations, components: componentDeps } = extractDependencies(component.attributes, typeName);
800
+ const imports = generateJSDocImports(relations, componentDeps, "component");
801
+ const properties = generateJSDocProperties(component.attributes);
802
+ return `/**
803
+ * ${component.displayName} component
804
+ * Category: ${component.category}
805
+ * ${component.description || ""}
806
+ * Generated by strapi2front
807
+ * Strapi version: ${strapiVersion}
808
+ */
809
+
810
+ ${imports}
811
+
812
+ /**
813
+ * ${component.displayName}
814
+ * @typedef {Object} ${typeName}
815
+ * @property {number} id
816
+ ${properties}
817
+ */
818
+
819
+ // Export empty object to make this a module
820
+ export {};
821
+ `;
822
+ }
823
+ function generateJSDocImports(relations, components, context) {
824
+ const imports = [];
825
+ imports.push(`/** @typedef {import('../utils').StrapiMedia} StrapiMedia */`);
826
+ imports.push(`/** @typedef {import('../utils').BlocksContent} BlocksContent */`);
827
+ for (const relation of relations) {
828
+ const typeName = toPascalCase(relation);
829
+ const fileName = toKebabCase(relation);
830
+ if (context === "component") {
831
+ imports.push(`/** @typedef {import('../collections/${fileName}').${typeName}} ${typeName} */`);
832
+ } else {
833
+ imports.push(`/** @typedef {import('./${fileName}').${typeName}} ${typeName} */`);
834
+ }
613
835
  }
614
- const findOneOptionsFields = [
615
- ` populate?: string | string[] | Record<string, unknown>;`
616
- ];
617
- if (localized) {
618
- findOneOptionsFields.push(` locale?: Locale;`);
836
+ for (const component of components) {
837
+ const typeName = toPascalCase(component);
838
+ const fileName = toKebabCase(component);
839
+ if (context === "component") {
840
+ imports.push(`/** @typedef {import('./${fileName}').${typeName}} ${typeName} */`);
841
+ } else {
842
+ imports.push(`/** @typedef {import('../components/${fileName}').${typeName}} ${typeName} */`);
843
+ }
619
844
  }
620
- if (draftAndPublish) {
621
- findOneOptionsFields.push(` status?: 'draft' | 'published';`);
845
+ return imports.join("\n");
846
+ }
847
+ function generateJSDocProperties(attributes, components) {
848
+ const lines = [];
849
+ for (const [name, attr] of Object.entries(attributes)) {
850
+ const mappedType = mapAttributeToType(attr);
851
+ const jsDocType = convertToJSDocType(mappedType.type);
852
+ const comment = getAttributeComment(attr);
853
+ const commentPart = comment ? ` - ${comment}` : "";
854
+ if (attr.required) {
855
+ lines.push(` * @property {${jsDocType}} ${name}${commentPart}`);
856
+ } else {
857
+ lines.push(` * @property {${jsDocType}} [${name}]${commentPart}`);
858
+ }
622
859
  }
623
- const findParams = [
624
- ` filters: options.filters,`,
625
- ` pagination: options.pagination,`,
626
- ` sort: options.sort,`,
627
- ` populate: options.populate,`
628
- ];
629
- if (localized) {
630
- findParams.push(` locale: options.locale,`);
860
+ return lines.join("\n");
861
+ }
862
+ function convertToJSDocType(tsType) {
863
+ if (tsType.includes(" | null")) {
864
+ const baseType = tsType.replace(" | null", "");
865
+ return `${baseType}|null`;
631
866
  }
632
- if (draftAndPublish) {
633
- findParams.push(` status: options.status,`);
867
+ if (tsType.endsWith("[]")) {
868
+ const baseType = tsType.slice(0, -2);
869
+ if (baseType.startsWith("(") && baseType.endsWith(")")) {
870
+ return `Array<${baseType.slice(1, -1).replace(/ \| /g, "|")}>`;
871
+ }
872
+ return `${baseType}[]`;
634
873
  }
635
- const findOneParams = [
636
- ` populate: options.populate,`
637
- ];
638
- if (localized) {
639
- findOneParams.push(` locale: options.locale,`);
874
+ if (tsType.includes("'") && tsType.includes(" | ")) {
875
+ return "string";
640
876
  }
641
- if (draftAndPublish) {
642
- findOneParams.push(` status: options.status,`);
877
+ return tsType;
878
+ }
879
+
880
+ // src/types/generator.ts
881
+ async function generateTypes(schema, options) {
882
+ const outputFormat = options.outputFormat ?? "typescript";
883
+ if (outputFormat === "jsdoc") {
884
+ return generateJSDocTypes(schema, {
885
+ outputDir: options.outputDir,
886
+ blocksRendererInstalled: options.blocksRendererInstalled,
887
+ strapiVersion: options.strapiVersion
888
+ });
643
889
  }
644
- const findBySlugParams = [
645
- ` populate: options.populate,`
646
- ];
647
- if (localized) {
648
- findBySlugParams.push(` locale: options.locale,`);
890
+ return generateTypeScriptTypes(schema, {
891
+ outputDir: options.outputDir,
892
+ blocksRendererInstalled: options.blocksRendererInstalled,
893
+ strapiVersion: options.strapiVersion
894
+ });
895
+ }
896
+ async function generateJSDocServices(schema, options) {
897
+ const { outputDir, typesImportPath, strapiVersion = "v5" } = options;
898
+ const generatedFiles = [];
899
+ await ensureDir(outputDir);
900
+ for (const collection of schema.collections) {
901
+ const fileName = `${toKebabCase(collection.singularName)}.service.js`;
902
+ const filePath = path9.join(outputDir, fileName);
903
+ const content = generateCollectionService(collection, typesImportPath, strapiVersion);
904
+ await writeFile(filePath, content);
905
+ generatedFiles.push(filePath);
649
906
  }
650
- if (draftAndPublish) {
651
- findBySlugParams.push(` status: options.status,`);
907
+ for (const single of schema.singles) {
908
+ const fileName = `${toKebabCase(single.singularName)}.service.js`;
909
+ const filePath = path9.join(outputDir, fileName);
910
+ const content = generateSingleService(single, typesImportPath, strapiVersion);
911
+ await writeFile(filePath, content);
912
+ generatedFiles.push(filePath);
652
913
  }
914
+ return generatedFiles;
915
+ }
916
+ function generateCollectionService(collection, typesImportPath, strapiVersion) {
917
+ const typeName = toPascalCase(collection.singularName);
918
+ const serviceName = toCamelCase(collection.singularName) + "Service";
919
+ const fileName = toKebabCase(collection.singularName);
920
+ const endpoint = collection.pluralName;
921
+ const isV4 = strapiVersion === "v4";
922
+ const idParam = isV4 ? "id" : "documentId";
923
+ const idType = isV4 ? "number" : "string";
924
+ const hasSlug = "slug" in collection.attributes;
925
+ const { localized, draftAndPublish } = collection;
926
+ const localeOption = localized ? "\n * @property {string} [locale] - Locale code" : "";
927
+ const statusOption = draftAndPublish ? "\n * @property {'draft'|'published'} [status] - Publication status" : "";
928
+ const findBySlugMethod = hasSlug ? `
929
+ /**
930
+ * Find one ${collection.singularName} by slug
931
+ * @param {string} slug - The slug to search for
932
+ * @param {FindOneOptions} [options={}] - Query options
933
+ * @returns {Promise<${typeName}|null>}
934
+ */
935
+ async findBySlug(slug, options = {}) {
936
+ const { data } = await this.findMany({
937
+ filters: { slug: { $eq: slug } },
938
+ pagination: { pageSize: 1 },
939
+ populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
940
+ });
941
+
942
+ return data[0] || null;
943
+ },
944
+ ` : "";
653
945
  return `/**
654
946
  * ${collection.displayName} Service
655
947
  * ${collection.description || ""}
@@ -657,26 +949,41 @@ function generateCollectionService(collection, typesImportPath, strapiVersion) {
657
949
  * Strapi version: ${strapiVersion}
658
950
  */
659
951
 
660
- ${imports.join("\n")}
952
+ import { collection } from '../client.js';
661
953
 
662
- export interface FindManyOptions {
663
- ${findManyOptionsFields.join("\n")}
664
- }
954
+ /** @typedef {import('${typesImportPath}/collections/${fileName}').${typeName}} ${typeName} */
955
+ /** @typedef {import('${typesImportPath}/collections/${fileName}').${typeName}Filters} ${typeName}Filters */
956
+ /** @typedef {import('${typesImportPath}/utils').StrapiPagination} StrapiPagination */
957
+ ${localized ? `/** @typedef {import('../locales').Locale} Locale */` : ""}
665
958
 
666
- export interface FindOneOptions {
667
- ${findOneOptionsFields.join("\n")}
668
- }
959
+ /**
960
+ * @typedef {Object} FindManyOptions
961
+ * @property {${typeName}Filters} [filters] - Filter conditions
962
+ * @property {{page?: number, pageSize?: number, start?: number, limit?: number}} [pagination] - Pagination options
963
+ * @property {string|string[]} [sort] - Sort order
964
+ * @property {string|string[]|Object} [populate] - Relations to populate${localeOption}${statusOption}
965
+ */
669
966
 
670
- // Create typed collection helper
671
- const ${toCamelCase(collection.singularName)}Collection = collection<${typeName}>('${endpoint}');
967
+ /**
968
+ * @typedef {Object} FindOneOptions
969
+ * @property {string|string[]|Object} [populate] - Relations to populate${localeOption}${statusOption}
970
+ */
971
+
972
+ /** @type {ReturnType<typeof collection<${typeName}>>} */
973
+ const ${toCamelCase(collection.singularName)}Collection = collection('${endpoint}');
672
974
 
673
975
  export const ${serviceName} = {
674
976
  /**
675
977
  * Find multiple ${collection.pluralName}
978
+ * @param {FindManyOptions} [options={}] - Query options
979
+ * @returns {Promise<{data: ${typeName}[], pagination: StrapiPagination}>}
676
980
  */
677
- async findMany(options: FindManyOptions = {}): Promise<{ data: ${typeName}[]; pagination: StrapiPagination }> {
981
+ async findMany(options = {}) {
678
982
  const response = await ${toCamelCase(collection.singularName)}Collection.find({
679
- ${findParams.join("\n")}
983
+ filters: options.filters,
984
+ pagination: options.pagination,
985
+ sort: options.sort,
986
+ populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
680
987
  });
681
988
 
682
989
  return {
@@ -687,9 +994,12 @@ ${findParams.join("\n")}
687
994
 
688
995
  /**
689
996
  * Find all ${collection.pluralName} (handles pagination automatically)
997
+ * @param {Omit<FindManyOptions, 'pagination'>} [options={}] - Query options
998
+ * @returns {Promise<${typeName}[]>}
690
999
  */
691
- async findAll(options: Omit<FindManyOptions, 'pagination'> = {}): Promise<${typeName}[]> {
692
- const allItems: ${typeName}[] = [];
1000
+ async findAll(options = {}) {
1001
+ /** @type {${typeName}[]} */
1002
+ const allItems = [];
693
1003
  let page = 1;
694
1004
  let hasMore = true;
695
1005
 
@@ -708,64 +1018,62 @@ ${findParams.join("\n")}
708
1018
  },
709
1019
 
710
1020
  /**
711
- * Find one ${collection.singularName} by ${idName}
1021
+ * Find one ${collection.singularName} by ${idParam}
1022
+ * @param {${idType}} ${idParam} - The ${idParam} to find
1023
+ * @param {FindOneOptions} [options={}] - Query options
1024
+ * @returns {Promise<${typeName}|null>}
712
1025
  */
713
- async findOne(${idParam}, options: FindOneOptions = {}): Promise<${typeName} | null> {
1026
+ async findOne(${idParam}, options = {}) {
714
1027
  try {
715
- const response = await ${toCamelCase(collection.singularName)}Collection.findOne(${idName}, {
716
- ${findOneParams.join("\n")}
1028
+ const response = await ${toCamelCase(collection.singularName)}Collection.findOne(${idParam}, {
1029
+ populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
717
1030
  });
718
1031
 
719
1032
  return response.data;
720
1033
  } catch (error) {
721
- // Return null if not found
722
1034
  if (error instanceof Error && error.message.includes('404')) {
723
1035
  return null;
724
1036
  }
725
1037
  throw error;
726
1038
  }
727
1039
  },
728
- ${hasSlug ? `
729
- /**
730
- * Find one ${collection.singularName} by slug
731
- */
732
- async findBySlug(slug: string, options: FindOneOptions = {}): Promise<${typeName} | null> {
733
- const { data } = await this.findMany({
734
- filters: { slug: { $eq: slug } } as ${typeName}Filters,
735
- pagination: { pageSize: 1 },
736
- ${findBySlugParams.join("\n")}
737
- });
738
-
739
- return data[0] || null;
740
- },
741
- ` : ""}
1040
+ ${findBySlugMethod}
742
1041
  /**
743
1042
  * Create a new ${collection.singularName}
1043
+ * @param {Partial<${typeName}>} data - The data to create
1044
+ * @returns {Promise<${typeName}>}
744
1045
  */
745
- async create(data: Partial<Omit<${typeName}, ${omitFields}>>): Promise<${typeName}> {
1046
+ async create(data) {
746
1047
  const response = await ${toCamelCase(collection.singularName)}Collection.create({ data });
747
1048
  return response.data;
748
1049
  },
749
1050
 
750
1051
  /**
751
1052
  * Update a ${collection.singularName}
1053
+ * @param {${idType}} ${idParam} - The ${idParam} to update
1054
+ * @param {Partial<${typeName}>} data - The data to update
1055
+ * @returns {Promise<${typeName}>}
752
1056
  */
753
- async update(${idParam}, data: Partial<Omit<${typeName}, ${omitFields}>>): Promise<${typeName}> {
754
- const response = await ${toCamelCase(collection.singularName)}Collection.update(${idName}, { data });
1057
+ async update(${idParam}, data) {
1058
+ const response = await ${toCamelCase(collection.singularName)}Collection.update(${idParam}, { data });
755
1059
  return response.data;
756
1060
  },
757
1061
 
758
1062
  /**
759
1063
  * Delete a ${collection.singularName}
1064
+ * @param {${idType}} ${idParam} - The ${idParam} to delete
1065
+ * @returns {Promise<void>}
760
1066
  */
761
- async delete(${idParam}): Promise<void> {
762
- await ${toCamelCase(collection.singularName)}Collection.delete(${idName});
1067
+ async delete(${idParam}) {
1068
+ await ${toCamelCase(collection.singularName)}Collection.delete(${idParam});
763
1069
  },
764
1070
 
765
1071
  /**
766
1072
  * Count ${collection.pluralName}
1073
+ * @param {${typeName}Filters} [filters] - Filter conditions
1074
+ * @returns {Promise<number>}
767
1075
  */
768
- async count(filters?: ${typeName}Filters): Promise<number> {
1076
+ async count(filters) {
769
1077
  const { pagination } = await this.findMany({
770
1078
  filters,
771
1079
  pagination: { pageSize: 1 },
@@ -781,34 +1089,9 @@ function generateSingleService(single, typesImportPath, strapiVersion) {
781
1089
  const serviceName = toCamelCase(single.singularName) + "Service";
782
1090
  const fileName = toKebabCase(single.singularName);
783
1091
  const endpoint = single.singularName;
784
- const isV4 = strapiVersion === "v4";
785
- const omitFields = isV4 ? "'id' | 'createdAt' | 'updatedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt'";
786
1092
  const { localized, draftAndPublish } = single;
787
- const imports = [
788
- `import { single } from '../client';`,
789
- `import type { ${typeName} } from '${typesImportPath}/collections/${fileName}';`
790
- ];
791
- if (localized) {
792
- imports.push(`import type { Locale } from '../locales';`);
793
- }
794
- const findOptionsFields = [
795
- ` populate?: string | string[] | Record<string, unknown>;`
796
- ];
797
- if (localized) {
798
- findOptionsFields.push(` locale?: Locale;`);
799
- }
800
- if (draftAndPublish) {
801
- findOptionsFields.push(` status?: 'draft' | 'published';`);
802
- }
803
- const findParams = [
804
- ` populate: options.populate,`
805
- ];
806
- if (localized) {
807
- findParams.push(` locale: options.locale,`);
808
- }
809
- if (draftAndPublish) {
810
- findParams.push(` status: options.status,`);
811
- }
1093
+ const localeOption = localized ? "\n * @property {string} [locale] - Locale code" : "";
1094
+ const statusOption = draftAndPublish ? "\n * @property {'draft'|'published'} [status] - Publication status" : "";
812
1095
  return `/**
813
1096
  * ${single.displayName} Service (Single Type)
814
1097
  * ${single.description || ""}
@@ -816,23 +1099,29 @@ function generateSingleService(single, typesImportPath, strapiVersion) {
816
1099
  * Strapi version: ${strapiVersion}
817
1100
  */
818
1101
 
819
- ${imports.join("\n")}
1102
+ import { single } from '../client.js';
820
1103
 
821
- export interface FindOptions {
822
- ${findOptionsFields.join("\n")}
823
- }
1104
+ /** @typedef {import('${typesImportPath}/collections/${fileName}').${typeName}} ${typeName} */
1105
+ ${localized ? `/** @typedef {import('../locales').Locale} Locale */` : ""}
824
1106
 
825
- // Create typed single helper
826
- const ${toCamelCase(single.singularName)}Single = single<${typeName}>('${endpoint}');
1107
+ /**
1108
+ * @typedef {Object} FindOptions
1109
+ * @property {string|string[]|Object} [populate] - Relations to populate${localeOption}${statusOption}
1110
+ */
1111
+
1112
+ /** @type {ReturnType<typeof single<${typeName}>>} */
1113
+ const ${toCamelCase(single.singularName)}Single = single('${endpoint}');
827
1114
 
828
1115
  export const ${serviceName} = {
829
1116
  /**
830
1117
  * Get ${single.displayName}
1118
+ * @param {FindOptions} [options={}] - Query options
1119
+ * @returns {Promise<${typeName}|null>}
831
1120
  */
832
- async find(options: FindOptions = {}): Promise<${typeName} | null> {
1121
+ async find(options = {}) {
833
1122
  try {
834
1123
  const response = await ${toCamelCase(single.singularName)}Single.find({
835
- ${findParams.join("\n")}
1124
+ populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
836
1125
  });
837
1126
 
838
1127
  return response.data;
@@ -846,249 +1135,388 @@ ${findParams.join("\n")}
846
1135
 
847
1136
  /**
848
1137
  * Update ${single.displayName}
1138
+ * @param {Partial<${typeName}>} data - The data to update
1139
+ * @returns {Promise<${typeName}>}
849
1140
  */
850
- async update(data: Partial<Omit<${typeName}, ${omitFields}>>): Promise<${typeName}> {
1141
+ async update(data) {
851
1142
  const response = await ${toCamelCase(single.singularName)}Single.update({ data });
852
1143
  return response.data;
853
1144
  },
854
1145
 
855
1146
  /**
856
1147
  * Delete ${single.displayName}
1148
+ * @returns {Promise<void>}
857
1149
  */
858
- async delete(): Promise<void> {
1150
+ async delete() {
859
1151
  await ${toCamelCase(single.singularName)}Single.delete();
860
1152
  },
861
1153
  };
862
1154
  `;
863
1155
  }
864
- async function generateActions(schema, options) {
865
- const { outputDir, servicesImportPath, strapiVersion = "v5" } = options;
1156
+
1157
+ // src/services/generator.ts
1158
+ async function generateServices(schema, options) {
1159
+ const outputFormat = options.outputFormat ?? "typescript";
1160
+ if (outputFormat === "jsdoc") {
1161
+ return generateJSDocServices(schema, {
1162
+ outputDir: options.outputDir,
1163
+ typesImportPath: options.typesImportPath,
1164
+ strapiVersion: options.strapiVersion
1165
+ });
1166
+ }
1167
+ return generateTypeScriptServices(schema, options);
1168
+ }
1169
+ async function generateTypeScriptServices(schema, options) {
1170
+ const { outputDir, typesImportPath, strapiVersion = "v5" } = options;
866
1171
  const generatedFiles = [];
867
1172
  await ensureDir(outputDir);
868
1173
  for (const collection of schema.collections) {
869
- const fileName = `${toKebabCase(collection.singularName)}.ts`;
870
- const filePath = path7.join(outputDir, fileName);
871
- const content = generateCollectionActions(collection, servicesImportPath, strapiVersion);
1174
+ const fileName = `${toKebabCase(collection.singularName)}.service.ts`;
1175
+ const filePath = path9.join(outputDir, fileName);
1176
+ const content = generateCollectionService2(collection, typesImportPath, strapiVersion);
872
1177
  await writeFile(filePath, await formatCode(content));
873
1178
  generatedFiles.push(filePath);
874
1179
  }
875
1180
  for (const single of schema.singles) {
876
- const fileName = `${toKebabCase(single.singularName)}.ts`;
877
- const filePath = path7.join(outputDir, fileName);
878
- const content = generateSingleActions(single, servicesImportPath);
1181
+ const fileName = `${toKebabCase(single.singularName)}.service.ts`;
1182
+ const filePath = path9.join(outputDir, fileName);
1183
+ const content = generateSingleService2(single, typesImportPath, strapiVersion);
879
1184
  await writeFile(filePath, await formatCode(content));
880
1185
  generatedFiles.push(filePath);
881
1186
  }
882
1187
  return generatedFiles;
883
1188
  }
884
- function generateCollectionActions(collection, servicesImportPath, strapiVersion) {
1189
+ function generateCollectionService2(collection, typesImportPath, strapiVersion) {
1190
+ const typeName = toPascalCase(collection.singularName);
885
1191
  const serviceName = toCamelCase(collection.singularName) + "Service";
886
- const actionsName = toCamelCase(collection.singularName);
887
1192
  const fileName = toKebabCase(collection.singularName);
1193
+ const endpoint = collection.pluralName;
888
1194
  const isV4 = strapiVersion === "v4";
889
- const idInputSchema = isV4 ? "z.number().int().positive()" : "z.string().min(1)";
890
- const idParamName = isV4 ? "id" : "documentId";
891
- const idComment = isV4 ? "id" : "documentId";
1195
+ const idParam = isV4 ? "id: number" : "documentId: string";
1196
+ const idName = isV4 ? "id" : "documentId";
1197
+ const omitFields = isV4 ? "'id' | 'createdAt' | 'updatedAt' | 'publishedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt' | 'publishedAt'";
892
1198
  const hasSlug = "slug" in collection.attributes;
1199
+ const { localized, draftAndPublish } = collection;
1200
+ const imports = [
1201
+ `import { collection } from '../client';`,
1202
+ `import type { ${typeName}, ${typeName}Filters } from '${typesImportPath}/collections/${fileName}';`,
1203
+ `import type { StrapiPagination } from '${typesImportPath}/utils';`
1204
+ ];
1205
+ if (localized) {
1206
+ imports.push(`import type { Locale } from '../locales';`);
1207
+ }
1208
+ const findManyOptionsFields = [
1209
+ ` filters?: ${typeName}Filters;`,
1210
+ ` pagination?: {`,
1211
+ ` /** Page number (1-indexed) - use with pageSize */`,
1212
+ ` page?: number;`,
1213
+ ` /** Number of items per page (default: 25) - use with page */`,
1214
+ ` pageSize?: number;`,
1215
+ ` /** Offset to start from (0-indexed) - use with limit */`,
1216
+ ` start?: number;`,
1217
+ ` /** Maximum number of items to return - use with start */`,
1218
+ ` limit?: number;`,
1219
+ ` };`,
1220
+ ` sort?: string | string[];`,
1221
+ ` populate?: string | string[] | Record<string, unknown>;`
1222
+ ];
1223
+ if (localized) {
1224
+ findManyOptionsFields.push(` locale?: Locale;`);
1225
+ }
1226
+ if (draftAndPublish) {
1227
+ findManyOptionsFields.push(` status?: 'draft' | 'published';`);
1228
+ }
1229
+ const findOneOptionsFields = [
1230
+ ` populate?: string | string[] | Record<string, unknown>;`
1231
+ ];
1232
+ if (localized) {
1233
+ findOneOptionsFields.push(` locale?: Locale;`);
1234
+ }
1235
+ if (draftAndPublish) {
1236
+ findOneOptionsFields.push(` status?: 'draft' | 'published';`);
1237
+ }
1238
+ const findParams = [
1239
+ ` filters: options.filters,`,
1240
+ ` pagination: options.pagination,`,
1241
+ ` sort: options.sort,`,
1242
+ ` populate: options.populate,`
1243
+ ];
1244
+ if (localized) {
1245
+ findParams.push(` locale: options.locale,`);
1246
+ }
1247
+ if (draftAndPublish) {
1248
+ findParams.push(` status: options.status,`);
1249
+ }
1250
+ const findOneParams = [
1251
+ ` populate: options.populate,`
1252
+ ];
1253
+ if (localized) {
1254
+ findOneParams.push(` locale: options.locale,`);
1255
+ }
1256
+ if (draftAndPublish) {
1257
+ findOneParams.push(` status: options.status,`);
1258
+ }
1259
+ const findBySlugParams = [
1260
+ ` populate: options.populate,`
1261
+ ];
1262
+ if (localized) {
1263
+ findBySlugParams.push(` locale: options.locale,`);
1264
+ }
1265
+ if (draftAndPublish) {
1266
+ findBySlugParams.push(` status: options.status,`);
1267
+ }
893
1268
  return `/**
894
- * ${collection.displayName} Actions
1269
+ * ${collection.displayName} Service
895
1270
  * ${collection.description || ""}
896
1271
  * Generated by strapi2front
897
1272
  * Strapi version: ${strapiVersion}
898
1273
  */
899
1274
 
900
- import { defineAction, ActionError } from 'astro:actions';
901
- import { z } from 'astro:schema';
902
- import { ${serviceName} } from '${servicesImportPath}/${fileName}.service';
1275
+ ${imports.join("\n")}
903
1276
 
904
- /**
905
- * Pagination input schema
906
- */
907
- const paginationSchema = z.object({
908
- page: z.number().int().positive().optional().default(1),
909
- pageSize: z.number().int().positive().max(100).optional().default(25),
910
- }).optional();
1277
+ export interface FindManyOptions {
1278
+ ${findManyOptionsFields.join("\n")}
1279
+ }
911
1280
 
912
- /**
913
- * ${collection.displayName} actions
914
- */
915
- export const ${actionsName} = {
1281
+ export interface FindOneOptions {
1282
+ ${findOneOptionsFields.join("\n")}
1283
+ }
1284
+
1285
+ // Create typed collection helper
1286
+ const ${toCamelCase(collection.singularName)}Collection = collection<${typeName}>('${endpoint}');
1287
+
1288
+ export const ${serviceName} = {
916
1289
  /**
917
- * Get all ${collection.pluralName} with pagination
1290
+ * Find multiple ${collection.pluralName}
918
1291
  */
919
- getAll: defineAction({
920
- input: z.object({
921
- pagination: paginationSchema,
922
- sort: z.union([z.string(), z.array(z.string())]).optional(),
923
- }).optional(),
924
- handler: async (input) => {
925
- try {
926
- const result = await ${serviceName}.findMany({
927
- pagination: input?.pagination,
928
- sort: input?.sort,
929
- });
1292
+ async findMany(options: FindManyOptions = {}): Promise<{ data: ${typeName}[]; pagination: StrapiPagination }> {
1293
+ const response = await ${toCamelCase(collection.singularName)}Collection.find({
1294
+ ${findParams.join("\n")}
1295
+ });
930
1296
 
931
- return result;
932
- } catch (error) {
933
- throw new ActionError({
934
- code: 'INTERNAL_SERVER_ERROR',
935
- message: error instanceof Error ? error.message : 'Failed to fetch ${collection.pluralName}',
936
- });
937
- }
938
- },
939
- }),
1297
+ return {
1298
+ data: response.data,
1299
+ pagination: response.meta.pagination,
1300
+ };
1301
+ },
940
1302
 
941
1303
  /**
942
- * Get a single ${collection.singularName} by ${idComment}
1304
+ * Find all ${collection.pluralName} (handles pagination automatically)
943
1305
  */
944
- getOne: defineAction({
945
- input: z.object({
946
- ${idParamName}: ${idInputSchema},
947
- populate: z.union([z.string(), z.array(z.string())]).optional(),
948
- }),
949
- handler: async ({ ${idParamName}, populate }) => {
950
- try {
951
- const result = await ${serviceName}.findOne(${idParamName}, { populate });
1306
+ async findAll(options: Omit<FindManyOptions, 'pagination'> = {}): Promise<${typeName}[]> {
1307
+ const allItems: ${typeName}[] = [];
1308
+ let page = 1;
1309
+ let hasMore = true;
952
1310
 
953
- if (!result) {
954
- throw new ActionError({
955
- code: 'NOT_FOUND',
956
- message: '${collection.displayName} not found',
957
- });
958
- }
1311
+ while (hasMore) {
1312
+ const { data, pagination } = await this.findMany({
1313
+ ...options,
1314
+ pagination: { page, pageSize: 100 },
1315
+ });
959
1316
 
960
- return result;
961
- } catch (error) {
962
- if (error instanceof ActionError) throw error;
963
- throw new ActionError({
964
- code: 'INTERNAL_SERVER_ERROR',
965
- message: error instanceof Error ? error.message : 'Failed to fetch ${collection.singularName}',
966
- });
1317
+ allItems.push(...data);
1318
+ hasMore = page < pagination.pageCount;
1319
+ page++;
1320
+ }
1321
+
1322
+ return allItems;
1323
+ },
1324
+
1325
+ /**
1326
+ * Find one ${collection.singularName} by ${idName}
1327
+ */
1328
+ async findOne(${idParam}, options: FindOneOptions = {}): Promise<${typeName} | null> {
1329
+ try {
1330
+ const response = await ${toCamelCase(collection.singularName)}Collection.findOne(${idName}, {
1331
+ ${findOneParams.join("\n")}
1332
+ });
1333
+
1334
+ return response.data;
1335
+ } catch (error) {
1336
+ // Return null if not found
1337
+ if (error instanceof Error && error.message.includes('404')) {
1338
+ return null;
967
1339
  }
968
- },
969
- }),
1340
+ throw error;
1341
+ }
1342
+ },
970
1343
  ${hasSlug ? `
971
1344
  /**
972
- * Get a single ${collection.singularName} by slug
1345
+ * Find one ${collection.singularName} by slug
973
1346
  */
974
- getBySlug: defineAction({
975
- input: z.object({
976
- slug: z.string().min(1),
977
- populate: z.union([z.string(), z.array(z.string())]).optional(),
978
- }),
979
- handler: async ({ slug, populate }) => {
980
- try {
981
- const result = await ${serviceName}.findBySlug(slug, { populate });
982
-
983
- if (!result) {
984
- throw new ActionError({
985
- code: 'NOT_FOUND',
986
- message: '${collection.displayName} not found',
987
- });
988
- }
1347
+ async findBySlug(slug: string, options: FindOneOptions = {}): Promise<${typeName} | null> {
1348
+ const { data } = await this.findMany({
1349
+ filters: { slug: { $eq: slug } } as ${typeName}Filters,
1350
+ pagination: { pageSize: 1 },
1351
+ ${findBySlugParams.join("\n")}
1352
+ });
989
1353
 
990
- return result;
991
- } catch (error) {
992
- if (error instanceof ActionError) throw error;
993
- throw new ActionError({
994
- code: 'INTERNAL_SERVER_ERROR',
995
- message: error instanceof Error ? error.message : 'Failed to fetch ${collection.singularName}',
996
- });
997
- }
998
- },
999
- }),
1354
+ return data[0] || null;
1355
+ },
1000
1356
  ` : ""}
1001
1357
  /**
1002
1358
  * Create a new ${collection.singularName}
1003
1359
  */
1004
- create: defineAction({
1005
- input: z.object({
1006
- data: z.record(z.unknown()),
1007
- }),
1008
- handler: async ({ data }) => {
1009
- try {
1010
- const result = await ${serviceName}.create(data);
1011
- return result;
1012
- } catch (error) {
1013
- throw new ActionError({
1014
- code: 'INTERNAL_SERVER_ERROR',
1015
- message: error instanceof Error ? error.message : 'Failed to create ${collection.singularName}',
1016
- });
1017
- }
1018
- },
1019
- }),
1360
+ async create(data: Partial<Omit<${typeName}, ${omitFields}>>): Promise<${typeName}> {
1361
+ const response = await ${toCamelCase(collection.singularName)}Collection.create({ data });
1362
+ return response.data;
1363
+ },
1020
1364
 
1021
1365
  /**
1022
1366
  * Update a ${collection.singularName}
1023
1367
  */
1024
- update: defineAction({
1025
- input: z.object({
1026
- ${idParamName}: ${idInputSchema},
1027
- data: z.record(z.unknown()),
1028
- }),
1029
- handler: async ({ ${idParamName}, data }) => {
1030
- try {
1031
- const result = await ${serviceName}.update(${idParamName}, data);
1032
- return result;
1033
- } catch (error) {
1034
- throw new ActionError({
1035
- code: 'INTERNAL_SERVER_ERROR',
1036
- message: error instanceof Error ? error.message : 'Failed to update ${collection.singularName}',
1037
- });
1038
- }
1039
- },
1040
- }),
1368
+ async update(${idParam}, data: Partial<Omit<${typeName}, ${omitFields}>>): Promise<${typeName}> {
1369
+ const response = await ${toCamelCase(collection.singularName)}Collection.update(${idName}, { data });
1370
+ return response.data;
1371
+ },
1041
1372
 
1042
1373
  /**
1043
1374
  * Delete a ${collection.singularName}
1044
1375
  */
1045
- delete: defineAction({
1046
- input: z.object({
1047
- ${idParamName}: ${idInputSchema},
1048
- }),
1049
- handler: async ({ ${idParamName} }) => {
1050
- try {
1051
- await ${serviceName}.delete(${idParamName});
1052
- return { success: true };
1053
- } catch (error) {
1054
- throw new ActionError({
1055
- code: 'INTERNAL_SERVER_ERROR',
1056
- message: error instanceof Error ? error.message : 'Failed to delete ${collection.singularName}',
1057
- });
1058
- }
1059
- },
1060
- }),
1376
+ async delete(${idParam}): Promise<void> {
1377
+ await ${toCamelCase(collection.singularName)}Collection.delete(${idName});
1378
+ },
1061
1379
 
1062
1380
  /**
1063
1381
  * Count ${collection.pluralName}
1064
1382
  */
1065
- count: defineAction({
1066
- input: z.object({
1067
- filters: z.record(z.unknown()).optional(),
1068
- }).optional(),
1069
- handler: async (input) => {
1070
- try {
1071
- const count = await ${serviceName}.count(input?.filters as any);
1072
- return { count };
1073
- } catch (error) {
1074
- throw new ActionError({
1075
- code: 'INTERNAL_SERVER_ERROR',
1076
- message: error instanceof Error ? error.message : 'Failed to count ${collection.pluralName}',
1077
- });
1078
- }
1079
- },
1080
- }),
1383
+ async count(filters?: ${typeName}Filters): Promise<number> {
1384
+ const { pagination } = await this.findMany({
1385
+ filters,
1386
+ pagination: { pageSize: 1 },
1387
+ });
1388
+
1389
+ return pagination.total;
1390
+ },
1081
1391
  };
1082
1392
  `;
1083
1393
  }
1084
- function generateSingleActions(single, servicesImportPath) {
1394
+ function generateSingleService2(single, typesImportPath, strapiVersion) {
1395
+ const typeName = toPascalCase(single.singularName);
1085
1396
  const serviceName = toCamelCase(single.singularName) + "Service";
1086
- const actionsName = toCamelCase(single.singularName);
1087
1397
  const fileName = toKebabCase(single.singularName);
1398
+ const endpoint = single.singularName;
1399
+ const isV4 = strapiVersion === "v4";
1400
+ const omitFields = isV4 ? "'id' | 'createdAt' | 'updatedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt'";
1401
+ const { localized, draftAndPublish } = single;
1402
+ const imports = [
1403
+ `import { single } from '../client';`,
1404
+ `import type { ${typeName} } from '${typesImportPath}/collections/${fileName}';`
1405
+ ];
1406
+ if (localized) {
1407
+ imports.push(`import type { Locale } from '../locales';`);
1408
+ }
1409
+ const findOptionsFields = [
1410
+ ` populate?: string | string[] | Record<string, unknown>;`
1411
+ ];
1412
+ if (localized) {
1413
+ findOptionsFields.push(` locale?: Locale;`);
1414
+ }
1415
+ if (draftAndPublish) {
1416
+ findOptionsFields.push(` status?: 'draft' | 'published';`);
1417
+ }
1418
+ const findParams = [
1419
+ ` populate: options.populate,`
1420
+ ];
1421
+ if (localized) {
1422
+ findParams.push(` locale: options.locale,`);
1423
+ }
1424
+ if (draftAndPublish) {
1425
+ findParams.push(` status: options.status,`);
1426
+ }
1088
1427
  return `/**
1089
- * ${single.displayName} Actions (Single Type)
1428
+ * ${single.displayName} Service (Single Type)
1090
1429
  * ${single.description || ""}
1091
1430
  * Generated by strapi2front
1431
+ * Strapi version: ${strapiVersion}
1432
+ */
1433
+
1434
+ ${imports.join("\n")}
1435
+
1436
+ export interface FindOptions {
1437
+ ${findOptionsFields.join("\n")}
1438
+ }
1439
+
1440
+ // Create typed single helper
1441
+ const ${toCamelCase(single.singularName)}Single = single<${typeName}>('${endpoint}');
1442
+
1443
+ export const ${serviceName} = {
1444
+ /**
1445
+ * Get ${single.displayName}
1446
+ */
1447
+ async find(options: FindOptions = {}): Promise<${typeName} | null> {
1448
+ try {
1449
+ const response = await ${toCamelCase(single.singularName)}Single.find({
1450
+ ${findParams.join("\n")}
1451
+ });
1452
+
1453
+ return response.data;
1454
+ } catch (error) {
1455
+ if (error instanceof Error && error.message.includes('404')) {
1456
+ return null;
1457
+ }
1458
+ throw error;
1459
+ }
1460
+ },
1461
+
1462
+ /**
1463
+ * Update ${single.displayName}
1464
+ */
1465
+ async update(data: Partial<Omit<${typeName}, ${omitFields}>>): Promise<${typeName}> {
1466
+ const response = await ${toCamelCase(single.singularName)}Single.update({ data });
1467
+ return response.data;
1468
+ },
1469
+
1470
+ /**
1471
+ * Delete ${single.displayName}
1472
+ */
1473
+ async delete(): Promise<void> {
1474
+ await ${toCamelCase(single.singularName)}Single.delete();
1475
+ },
1476
+ };
1477
+ `;
1478
+ }
1479
+ function isAstroActionsSupported(astroVersion) {
1480
+ if (!astroVersion) return false;
1481
+ const match = astroVersion.replace(/^[\^~]/, "").match(/^(\d+)/);
1482
+ const majorVersion = match ? parseInt(match[1], 10) : null;
1483
+ return majorVersion !== null && majorVersion >= 4;
1484
+ }
1485
+ async function generateAstroActions(schema, options) {
1486
+ const { outputDir, servicesImportPath, strapiVersion = "v5" } = options;
1487
+ const generatedFiles = [];
1488
+ await ensureDir(outputDir);
1489
+ for (const collection of schema.collections) {
1490
+ const fileName = `${toKebabCase(collection.singularName)}.ts`;
1491
+ const filePath = path9.join(outputDir, fileName);
1492
+ const content = generateCollectionActions(collection, servicesImportPath, strapiVersion);
1493
+ await writeFile(filePath, await formatCode(content));
1494
+ generatedFiles.push(filePath);
1495
+ }
1496
+ for (const single of schema.singles) {
1497
+ const fileName = `${toKebabCase(single.singularName)}.ts`;
1498
+ const filePath = path9.join(outputDir, fileName);
1499
+ const content = generateSingleActions(single, servicesImportPath);
1500
+ await writeFile(filePath, await formatCode(content));
1501
+ generatedFiles.push(filePath);
1502
+ }
1503
+ return generatedFiles;
1504
+ }
1505
+ function generateCollectionActions(collection, servicesImportPath, strapiVersion) {
1506
+ const serviceName = toCamelCase(collection.singularName) + "Service";
1507
+ const actionsName = toCamelCase(collection.singularName);
1508
+ const fileName = toKebabCase(collection.singularName);
1509
+ const isV4 = strapiVersion === "v4";
1510
+ const idInputSchema = isV4 ? "z.number().int().positive()" : "z.string().min(1)";
1511
+ const idParamName = isV4 ? "id" : "documentId";
1512
+ const idComment = isV4 ? "id" : "documentId";
1513
+ const hasSlug = "slug" in collection.attributes;
1514
+ return `/**
1515
+ * ${collection.displayName} Actions
1516
+ * ${collection.description || ""}
1517
+ * Generated by strapi2front
1518
+ * Framework: Astro
1519
+ * Strapi version: ${strapiVersion}
1092
1520
  */
1093
1521
 
1094
1522
  import { defineAction, ActionError } from 'astro:actions';
@@ -1096,26 +1524,58 @@ import { z } from 'astro:schema';
1096
1524
  import { ${serviceName} } from '${servicesImportPath}/${fileName}.service';
1097
1525
 
1098
1526
  /**
1099
- * ${single.displayName} actions
1527
+ * Pagination input schema
1528
+ */
1529
+ const paginationSchema = z.object({
1530
+ page: z.number().int().positive().optional().default(1),
1531
+ pageSize: z.number().int().positive().max(100).optional().default(25),
1532
+ }).optional();
1533
+
1534
+ /**
1535
+ * ${collection.displayName} actions
1100
1536
  */
1101
1537
  export const ${actionsName} = {
1102
1538
  /**
1103
- * Get ${single.displayName}
1539
+ * Get all ${collection.pluralName} with pagination
1104
1540
  */
1105
- get: defineAction({
1541
+ getAll: defineAction({
1106
1542
  input: z.object({
1107
- populate: z.union([z.string(), z.array(z.string())]).optional(),
1543
+ pagination: paginationSchema,
1544
+ sort: z.union([z.string(), z.array(z.string())]).optional(),
1108
1545
  }).optional(),
1109
1546
  handler: async (input) => {
1110
1547
  try {
1111
- const result = await ${serviceName}.find({
1112
- populate: input?.populate,
1548
+ const result = await ${serviceName}.findMany({
1549
+ pagination: input?.pagination,
1550
+ sort: input?.sort,
1113
1551
  });
1114
1552
 
1553
+ return result;
1554
+ } catch (error) {
1555
+ throw new ActionError({
1556
+ code: 'INTERNAL_SERVER_ERROR',
1557
+ message: error instanceof Error ? error.message : 'Failed to fetch ${collection.pluralName}',
1558
+ });
1559
+ }
1560
+ },
1561
+ }),
1562
+
1563
+ /**
1564
+ * Get a single ${collection.singularName} by ${idComment}
1565
+ */
1566
+ getOne: defineAction({
1567
+ input: z.object({
1568
+ ${idParamName}: ${idInputSchema},
1569
+ populate: z.union([z.string(), z.array(z.string())]).optional(),
1570
+ }),
1571
+ handler: async ({ ${idParamName}, populate }) => {
1572
+ try {
1573
+ const result = await ${serviceName}.findOne(${idParamName}, { populate });
1574
+
1115
1575
  if (!result) {
1116
1576
  throw new ActionError({
1117
1577
  code: 'NOT_FOUND',
1118
- message: '${single.displayName} not found',
1578
+ message: '${collection.displayName} not found',
1119
1579
  });
1120
1580
  }
1121
1581
 
@@ -1124,22 +1584,185 @@ export const ${actionsName} = {
1124
1584
  if (error instanceof ActionError) throw error;
1125
1585
  throw new ActionError({
1126
1586
  code: 'INTERNAL_SERVER_ERROR',
1127
- message: error instanceof Error ? error.message : 'Failed to fetch ${single.singularName}',
1587
+ message: error instanceof Error ? error.message : 'Failed to fetch ${collection.singularName}',
1128
1588
  });
1129
1589
  }
1130
1590
  },
1131
1591
  }),
1132
-
1592
+ ${hasSlug ? `
1133
1593
  /**
1134
- * Update ${single.displayName}
1594
+ * Get a single ${collection.singularName} by slug
1135
1595
  */
1136
- update: defineAction({
1596
+ getBySlug: defineAction({
1137
1597
  input: z.object({
1138
- data: z.record(z.unknown()),
1598
+ slug: z.string().min(1),
1599
+ populate: z.union([z.string(), z.array(z.string())]).optional(),
1139
1600
  }),
1140
- handler: async ({ data }) => {
1601
+ handler: async ({ slug, populate }) => {
1141
1602
  try {
1142
- const result = await ${serviceName}.update(data);
1603
+ const result = await ${serviceName}.findBySlug(slug, { populate });
1604
+
1605
+ if (!result) {
1606
+ throw new ActionError({
1607
+ code: 'NOT_FOUND',
1608
+ message: '${collection.displayName} not found',
1609
+ });
1610
+ }
1611
+
1612
+ return result;
1613
+ } catch (error) {
1614
+ if (error instanceof ActionError) throw error;
1615
+ throw new ActionError({
1616
+ code: 'INTERNAL_SERVER_ERROR',
1617
+ message: error instanceof Error ? error.message : 'Failed to fetch ${collection.singularName}',
1618
+ });
1619
+ }
1620
+ },
1621
+ }),
1622
+ ` : ""}
1623
+ /**
1624
+ * Create a new ${collection.singularName}
1625
+ */
1626
+ create: defineAction({
1627
+ input: z.object({
1628
+ data: z.record(z.unknown()),
1629
+ }),
1630
+ handler: async ({ data }) => {
1631
+ try {
1632
+ const result = await ${serviceName}.create(data);
1633
+ return result;
1634
+ } catch (error) {
1635
+ throw new ActionError({
1636
+ code: 'INTERNAL_SERVER_ERROR',
1637
+ message: error instanceof Error ? error.message : 'Failed to create ${collection.singularName}',
1638
+ });
1639
+ }
1640
+ },
1641
+ }),
1642
+
1643
+ /**
1644
+ * Update a ${collection.singularName}
1645
+ */
1646
+ update: defineAction({
1647
+ input: z.object({
1648
+ ${idParamName}: ${idInputSchema},
1649
+ data: z.record(z.unknown()),
1650
+ }),
1651
+ handler: async ({ ${idParamName}, data }) => {
1652
+ try {
1653
+ const result = await ${serviceName}.update(${idParamName}, data);
1654
+ return result;
1655
+ } catch (error) {
1656
+ throw new ActionError({
1657
+ code: 'INTERNAL_SERVER_ERROR',
1658
+ message: error instanceof Error ? error.message : 'Failed to update ${collection.singularName}',
1659
+ });
1660
+ }
1661
+ },
1662
+ }),
1663
+
1664
+ /**
1665
+ * Delete a ${collection.singularName}
1666
+ */
1667
+ delete: defineAction({
1668
+ input: z.object({
1669
+ ${idParamName}: ${idInputSchema},
1670
+ }),
1671
+ handler: async ({ ${idParamName} }) => {
1672
+ try {
1673
+ await ${serviceName}.delete(${idParamName});
1674
+ return { success: true };
1675
+ } catch (error) {
1676
+ throw new ActionError({
1677
+ code: 'INTERNAL_SERVER_ERROR',
1678
+ message: error instanceof Error ? error.message : 'Failed to delete ${collection.singularName}',
1679
+ });
1680
+ }
1681
+ },
1682
+ }),
1683
+
1684
+ /**
1685
+ * Count ${collection.pluralName}
1686
+ */
1687
+ count: defineAction({
1688
+ input: z.object({
1689
+ filters: z.record(z.unknown()).optional(),
1690
+ }).optional(),
1691
+ handler: async (input) => {
1692
+ try {
1693
+ const count = await ${serviceName}.count(input?.filters as any);
1694
+ return { count };
1695
+ } catch (error) {
1696
+ throw new ActionError({
1697
+ code: 'INTERNAL_SERVER_ERROR',
1698
+ message: error instanceof Error ? error.message : 'Failed to count ${collection.pluralName}',
1699
+ });
1700
+ }
1701
+ },
1702
+ }),
1703
+ };
1704
+ `;
1705
+ }
1706
+ function generateSingleActions(single, servicesImportPath) {
1707
+ const serviceName = toCamelCase(single.singularName) + "Service";
1708
+ const actionsName = toCamelCase(single.singularName);
1709
+ const fileName = toKebabCase(single.singularName);
1710
+ return `/**
1711
+ * ${single.displayName} Actions (Single Type)
1712
+ * ${single.description || ""}
1713
+ * Generated by strapi2front
1714
+ * Framework: Astro
1715
+ */
1716
+
1717
+ import { defineAction, ActionError } from 'astro:actions';
1718
+ import { z } from 'astro:schema';
1719
+ import { ${serviceName} } from '${servicesImportPath}/${fileName}.service';
1720
+
1721
+ /**
1722
+ * ${single.displayName} actions
1723
+ */
1724
+ export const ${actionsName} = {
1725
+ /**
1726
+ * Get ${single.displayName}
1727
+ */
1728
+ get: defineAction({
1729
+ input: z.object({
1730
+ populate: z.union([z.string(), z.array(z.string())]).optional(),
1731
+ }).optional(),
1732
+ handler: async (input) => {
1733
+ try {
1734
+ const result = await ${serviceName}.find({
1735
+ populate: input?.populate,
1736
+ });
1737
+
1738
+ if (!result) {
1739
+ throw new ActionError({
1740
+ code: 'NOT_FOUND',
1741
+ message: '${single.displayName} not found',
1742
+ });
1743
+ }
1744
+
1745
+ return result;
1746
+ } catch (error) {
1747
+ if (error instanceof ActionError) throw error;
1748
+ throw new ActionError({
1749
+ code: 'INTERNAL_SERVER_ERROR',
1750
+ message: error instanceof Error ? error.message : 'Failed to fetch ${single.singularName}',
1751
+ });
1752
+ }
1753
+ },
1754
+ }),
1755
+
1756
+ /**
1757
+ * Update ${single.displayName}
1758
+ */
1759
+ update: defineAction({
1760
+ input: z.object({
1761
+ data: z.record(z.unknown()),
1762
+ }),
1763
+ handler: async ({ data }) => {
1764
+ try {
1765
+ const result = await ${serviceName}.update(data);
1143
1766
  return result;
1144
1767
  } catch (error) {
1145
1768
  throw new ActionError({
@@ -1152,24 +1775,34 @@ export const ${actionsName} = {
1152
1775
  };
1153
1776
  `;
1154
1777
  }
1778
+
1779
+ // src/actions/generator.ts
1780
+ async function generateActions(schema, options) {
1781
+ return generateAstroActions(schema, {
1782
+ outputDir: options.outputDir,
1783
+ servicesImportPath: options.servicesImportPath,
1784
+ strapiVersion: options.strapiVersion
1785
+ });
1786
+ }
1155
1787
  async function generateClient(options) {
1156
- const { outputDir, strapiVersion = "v5" } = options;
1788
+ const { outputDir, strapiVersion = "v5", apiPrefix = "/api" } = options;
1157
1789
  const generatedFiles = [];
1158
1790
  await ensureDir(outputDir);
1159
- const filePath = path7.join(outputDir, "client.ts");
1160
- const content = generateClientFile(strapiVersion);
1791
+ const filePath = path9.join(outputDir, "client.ts");
1792
+ const content = generateClientFile(strapiVersion, apiPrefix);
1161
1793
  await writeFile(filePath, await formatCode(content));
1162
1794
  generatedFiles.push(filePath);
1163
1795
  return generatedFiles;
1164
1796
  }
1165
- function generateClientFile(strapiVersion) {
1797
+ function generateClientFile(strapiVersion, apiPrefix) {
1166
1798
  const isV4 = strapiVersion === "v4";
1167
1799
  if (isV4) {
1168
- return generateV4ClientFile();
1800
+ return generateV4ClientFile(apiPrefix);
1169
1801
  }
1170
- return generateV5ClientFile();
1802
+ return generateV5ClientFile(apiPrefix);
1171
1803
  }
1172
- function generateV5ClientFile() {
1804
+ function generateV5ClientFile(apiPrefix) {
1805
+ const normalizedPrefix = apiPrefix.startsWith("/") ? apiPrefix : "/" + apiPrefix;
1173
1806
  return `/**
1174
1807
  * Strapi Client (v5)
1175
1808
  * Generated by strapi2front
@@ -1180,9 +1813,11 @@ import Strapi from 'strapi-sdk-js';
1180
1813
  // Initialize the Strapi client
1181
1814
  const strapiUrl = import.meta.env.STRAPI_URL || process.env.STRAPI_URL || 'http://localhost:1337';
1182
1815
  const strapiToken = import.meta.env.STRAPI_TOKEN || process.env.STRAPI_TOKEN;
1816
+ const strapiApiPrefix = import.meta.env.STRAPI_API_PREFIX || process.env.STRAPI_API_PREFIX || '${normalizedPrefix}';
1183
1817
 
1184
1818
  export const strapi = new Strapi({
1185
1819
  url: strapiUrl,
1820
+ prefix: strapiApiPrefix,
1186
1821
  axiosOptions: {
1187
1822
  headers: strapiToken ? {
1188
1823
  Authorization: \`Bearer \${strapiToken}\`,
@@ -1267,7 +1902,8 @@ export function single<T>(singularName: string) {
1267
1902
  }
1268
1903
  `;
1269
1904
  }
1270
- function generateV4ClientFile() {
1905
+ function generateV4ClientFile(apiPrefix) {
1906
+ const normalizedPrefix = apiPrefix.startsWith("/") ? apiPrefix : "/" + apiPrefix;
1271
1907
  return `/**
1272
1908
  * Strapi Client (v4)
1273
1909
  * Generated by strapi2front
@@ -1278,9 +1914,11 @@ import Strapi from 'strapi-sdk-js';
1278
1914
  // Initialize the Strapi client
1279
1915
  const strapiUrl = import.meta.env.STRAPI_URL || process.env.STRAPI_URL || 'http://localhost:1337';
1280
1916
  const strapiToken = import.meta.env.STRAPI_TOKEN || process.env.STRAPI_TOKEN;
1917
+ const strapiApiPrefix = import.meta.env.STRAPI_API_PREFIX || process.env.STRAPI_API_PREFIX || '${normalizedPrefix}';
1281
1918
 
1282
1919
  export const strapi = new Strapi({
1283
1920
  url: strapiUrl,
1921
+ prefix: strapiApiPrefix,
1284
1922
  axiosOptions: {
1285
1923
  headers: strapiToken ? {
1286
1924
  Authorization: \`Bearer \${strapiToken}\`,
@@ -1420,7 +2058,7 @@ async function generateLocales(locales, options) {
1420
2058
  const { outputDir } = options;
1421
2059
  const generatedFiles = [];
1422
2060
  await ensureDir(outputDir);
1423
- const filePath = path7.join(outputDir, "locales.ts");
2061
+ const filePath = path9.join(outputDir, "locales.ts");
1424
2062
  const content = generateLocalesFile(locales);
1425
2063
  await writeFile(filePath, await formatCode(content));
1426
2064
  generatedFiles.push(filePath);
@@ -1468,198 +2106,1191 @@ export type Locale = ${localeCodes};
1468
2106
  */
1469
2107
  export const defaultLocale: Locale = '${defaultLocale}';
1470
2108
 
1471
- /**
1472
- * Locale display names
1473
- */
1474
- export const localeNames: Record<Locale, string> = {
1475
- ${localeNames}
1476
- };
2109
+ /**
2110
+ * Locale display names
2111
+ */
2112
+ export const localeNames: Record<Locale, string> = {
2113
+ ${localeNames}
2114
+ };
2115
+
2116
+ /**
2117
+ * Check if a string is a valid locale
2118
+ */
2119
+ export function isValidLocale(code: string): code is Locale {
2120
+ return locales.includes(code as Locale);
2121
+ }
2122
+
2123
+ /**
2124
+ * Get locale name by code
2125
+ */
2126
+ export function getLocaleName(code: Locale): string {
2127
+ return localeNames[code] || code;
2128
+ }
2129
+ `;
2130
+ }
2131
+ async function generateByFeature(schema, locales, options) {
2132
+ const {
2133
+ outputDir,
2134
+ features,
2135
+ blocksRendererInstalled = false,
2136
+ strapiVersion = "v5",
2137
+ apiPrefix = "/api",
2138
+ outputFormat = "typescript"
2139
+ } = options;
2140
+ const generatedFiles = [];
2141
+ const ext = outputFormat === "jsdoc" ? "js" : "ts";
2142
+ await ensureDir(path9.join(outputDir, "collections"));
2143
+ await ensureDir(path9.join(outputDir, "singles"));
2144
+ await ensureDir(path9.join(outputDir, "components"));
2145
+ await ensureDir(path9.join(outputDir, "shared"));
2146
+ const sharedDir = path9.join(outputDir, "shared");
2147
+ const utilsPath = path9.join(sharedDir, `utils.${ext}`);
2148
+ const utilsContent = outputFormat === "jsdoc" ? generateUtilityTypesJSDoc(blocksRendererInstalled, strapiVersion) : generateUtilityTypes3(blocksRendererInstalled, strapiVersion);
2149
+ await writeFile(utilsPath, await formatCode(utilsContent));
2150
+ generatedFiles.push(utilsPath);
2151
+ const clientPath = path9.join(sharedDir, `client.${ext}`);
2152
+ const clientContent = outputFormat === "jsdoc" ? generateClientJSDoc(strapiVersion, apiPrefix) : generateClient2(strapiVersion, apiPrefix);
2153
+ await writeFile(clientPath, await formatCode(clientContent));
2154
+ generatedFiles.push(clientPath);
2155
+ const localesPath = path9.join(sharedDir, `locales.${ext}`);
2156
+ const localesContent = outputFormat === "jsdoc" ? generateLocalesFileJSDoc(locales) : generateLocalesFile2(locales);
2157
+ await writeFile(localesPath, await formatCode(localesContent));
2158
+ generatedFiles.push(localesPath);
2159
+ for (const collection of schema.collections) {
2160
+ const featureDir = path9.join(outputDir, "collections", toKebabCase(collection.singularName));
2161
+ await ensureDir(featureDir);
2162
+ if (features.types) {
2163
+ const typesPath = path9.join(featureDir, `types.${ext}`);
2164
+ const content = outputFormat === "jsdoc" ? generateCollectionTypesJSDoc(collection, schema) : generateCollectionTypes(collection, schema);
2165
+ await writeFile(typesPath, await formatCode(content));
2166
+ generatedFiles.push(typesPath);
2167
+ }
2168
+ if (features.services) {
2169
+ const servicePath = path9.join(featureDir, `service.${ext}`);
2170
+ const content = outputFormat === "jsdoc" ? generateCollectionServiceJSDoc(collection, strapiVersion) : generateCollectionService3(collection, strapiVersion);
2171
+ await writeFile(servicePath, await formatCode(content));
2172
+ generatedFiles.push(servicePath);
2173
+ }
2174
+ if (features.actions) {
2175
+ const actionsPath = path9.join(featureDir, `actions.${ext}`);
2176
+ await writeFile(actionsPath, await formatCode(generateCollectionActions2(collection, strapiVersion)));
2177
+ generatedFiles.push(actionsPath);
2178
+ }
2179
+ }
2180
+ for (const single of schema.singles) {
2181
+ const featureDir = path9.join(outputDir, "singles", toKebabCase(single.singularName));
2182
+ await ensureDir(featureDir);
2183
+ if (features.types) {
2184
+ const typesPath = path9.join(featureDir, `types.${ext}`);
2185
+ const content = outputFormat === "jsdoc" ? generateSingleTypesJSDoc(single, schema) : generateSingleTypes(single, schema);
2186
+ await writeFile(typesPath, await formatCode(content));
2187
+ generatedFiles.push(typesPath);
2188
+ }
2189
+ if (features.services) {
2190
+ const servicePath = path9.join(featureDir, `service.${ext}`);
2191
+ const content = outputFormat === "jsdoc" ? generateSingleServiceJSDoc(single, strapiVersion) : generateSingleService3(single, strapiVersion);
2192
+ await writeFile(servicePath, await formatCode(content));
2193
+ generatedFiles.push(servicePath);
2194
+ }
2195
+ }
2196
+ for (const component of schema.components) {
2197
+ const componentPath = path9.join(outputDir, "components", `${toKebabCase(component.name)}.${ext}`);
2198
+ const content = outputFormat === "jsdoc" ? generateComponentTypesJSDoc(component, schema) : generateComponentTypes(component, schema);
2199
+ await writeFile(componentPath, await formatCode(content));
2200
+ generatedFiles.push(componentPath);
2201
+ }
2202
+ return generatedFiles;
2203
+ }
2204
+ function generateUtilityTypes3(blocksRendererInstalled, strapiVersion) {
2205
+ const isV4 = strapiVersion === "v4";
2206
+ const blocksContentType = isV4 ? `/**
2207
+ * Rich text content (Strapi v4)
2208
+ * Can be markdown string or custom JSON structure
2209
+ */
2210
+ export type RichTextContent = string;` : blocksRendererInstalled ? `/**
2211
+ * Blocks content type (Strapi v5 rich text)
2212
+ * Re-exported from @strapi/blocks-react-renderer
2213
+ */
2214
+ export type { BlocksContent } from '@strapi/blocks-react-renderer';` : `/**
2215
+ * Blocks content type (Strapi v5 rich text)
2216
+ *
2217
+ * For full type support and rendering, install:
2218
+ * npm install @strapi/blocks-react-renderer
2219
+ *
2220
+ * Then re-run: npx strapi2front sync
2221
+ */
2222
+ export type BlocksContent = unknown[];`;
2223
+ const baseEntity = isV4 ? `export interface StrapiBaseEntity {
2224
+ id: number;
2225
+ createdAt: string;
2226
+ updatedAt: string;
2227
+ publishedAt: string | null;
2228
+ }` : `export interface StrapiBaseEntity {
2229
+ id: number;
2230
+ documentId: string;
2231
+ createdAt: string;
2232
+ updatedAt: string;
2233
+ publishedAt: string | null;
2234
+ }`;
2235
+ const mediaType = isV4 ? `export interface StrapiMedia {
2236
+ id: number;
2237
+ name: string;
2238
+ alternativeText: string | null;
2239
+ caption: string | null;
2240
+ width: number;
2241
+ height: number;
2242
+ formats: {
2243
+ thumbnail?: StrapiMediaFormat;
2244
+ small?: StrapiMediaFormat;
2245
+ medium?: StrapiMediaFormat;
2246
+ large?: StrapiMediaFormat;
2247
+ } | null;
2248
+ hash: string;
2249
+ ext: string;
2250
+ mime: string;
2251
+ size: number;
2252
+ url: string;
2253
+ previewUrl: string | null;
2254
+ provider: string;
2255
+ createdAt: string;
2256
+ updatedAt: string;
2257
+ }` : `export interface StrapiMedia {
2258
+ id: number;
2259
+ documentId: string;
2260
+ name: string;
2261
+ alternativeText: string | null;
2262
+ caption: string | null;
2263
+ width: number;
2264
+ height: number;
2265
+ formats: {
2266
+ thumbnail?: StrapiMediaFormat;
2267
+ small?: StrapiMediaFormat;
2268
+ medium?: StrapiMediaFormat;
2269
+ large?: StrapiMediaFormat;
2270
+ } | null;
2271
+ hash: string;
2272
+ ext: string;
2273
+ mime: string;
2274
+ size: number;
2275
+ url: string;
2276
+ previewUrl: string | null;
2277
+ provider: string;
2278
+ createdAt: string;
2279
+ updatedAt: string;
2280
+ }`;
2281
+ const v4RawResponseTypes = isV4 ? `
2282
+
2283
+ /**
2284
+ * Strapi v4 raw API response (with nested attributes)
2285
+ */
2286
+ export interface StrapiV4RawItem<T> {
2287
+ id: number;
2288
+ attributes: Omit<T, 'id'>;
2289
+ }
2290
+
2291
+ export interface StrapiV4RawResponse<T> {
2292
+ data: StrapiV4RawItem<T>;
2293
+ meta: Record<string, unknown>;
2294
+ }
2295
+
2296
+ export interface StrapiV4RawListResponse<T> {
2297
+ data: StrapiV4RawItem<T>[];
2298
+ meta: {
2299
+ pagination: StrapiPagination;
2300
+ };
2301
+ }
2302
+
2303
+ /**
2304
+ * Flatten Strapi v4 response item
2305
+ */
2306
+ export function flattenV4Response<T>(item: StrapiV4RawItem<T>): T {
2307
+ return { id: item.id, ...item.attributes } as T;
2308
+ }
2309
+
2310
+ /**
2311
+ * Flatten Strapi v4 list response
2312
+ */
2313
+ export function flattenV4ListResponse<T>(items: StrapiV4RawItem<T>[]): T[] {
2314
+ return items.map(item => flattenV4Response<T>(item));
2315
+ }` : "";
2316
+ return `/**
2317
+ * Strapi utility types
2318
+ * Generated by strapi2front
2319
+ * Strapi version: ${strapiVersion}
2320
+ */
2321
+
2322
+ ${mediaType}
2323
+
2324
+ export interface StrapiMediaFormat {
2325
+ name: string;
2326
+ hash: string;
2327
+ ext: string;
2328
+ mime: string;
2329
+ width: number;
2330
+ height: number;
2331
+ size: number;
2332
+ url: string;
2333
+ }
2334
+
2335
+ export interface StrapiPagination {
2336
+ page: number;
2337
+ pageSize: number;
2338
+ pageCount: number;
2339
+ total: number;
2340
+ }
2341
+
2342
+ export interface StrapiResponse<T> {
2343
+ data: T;
2344
+ meta: {
2345
+ pagination?: StrapiPagination;
2346
+ };
2347
+ }
2348
+
2349
+ export interface StrapiListResponse<T> {
2350
+ data: T[];
2351
+ meta: {
2352
+ pagination: StrapiPagination;
2353
+ };
2354
+ }
2355
+
2356
+ ${baseEntity}
2357
+ ${v4RawResponseTypes}
2358
+ ${blocksContentType}
2359
+ `;
2360
+ }
2361
+ function generateClient2(strapiVersion, apiPrefix = "/api") {
2362
+ const isV4 = strapiVersion === "v4";
2363
+ const normalizedPrefix = apiPrefix.startsWith("/") ? apiPrefix : "/" + apiPrefix;
2364
+ if (isV4) {
2365
+ return `/**
2366
+ * Strapi Client (v4)
2367
+ * Generated by strapi2front
2368
+ */
2369
+
2370
+ import Strapi from 'strapi-sdk-js';
2371
+ import type { StrapiPagination } from './utils';
2372
+
2373
+ // Initialize the Strapi client
2374
+ const strapiUrl = import.meta.env.STRAPI_URL || process.env.STRAPI_URL || 'http://localhost:1337';
2375
+ const strapiToken = import.meta.env.STRAPI_TOKEN || process.env.STRAPI_TOKEN;
2376
+ const strapiApiPrefix = import.meta.env.STRAPI_API_PREFIX || process.env.STRAPI_API_PREFIX || '${normalizedPrefix}';
2377
+
2378
+ export const strapi = new Strapi({
2379
+ url: strapiUrl,
2380
+ prefix: strapiApiPrefix,
2381
+ axiosOptions: {
2382
+ headers: strapiToken ? {
2383
+ Authorization: \`Bearer \${strapiToken}\`,
2384
+ } : {},
2385
+ },
2386
+ });
2387
+
2388
+ // Default pagination for fallback
2389
+ const defaultPagination: StrapiPagination = {
2390
+ page: 1,
2391
+ pageSize: 25,
2392
+ pageCount: 1,
2393
+ total: 0,
2394
+ };
2395
+
2396
+ // Strapi v4 raw response types (with nested attributes)
2397
+ interface StrapiV4RawItem<T> {
2398
+ id: number;
2399
+ attributes: Omit<T, 'id'>;
2400
+ }
2401
+
2402
+ interface StrapiV4RawListResponse<T> {
2403
+ data: StrapiV4RawItem<T>[];
2404
+ meta: {
2405
+ pagination?: StrapiPagination;
2406
+ };
2407
+ }
2408
+
2409
+ interface StrapiV4RawSingleResponse<T> {
2410
+ data: StrapiV4RawItem<T>;
2411
+ meta?: Record<string, unknown>;
2412
+ }
2413
+
2414
+ /**
2415
+ * Flatten a Strapi v4 response item (merges id with attributes)
2416
+ */
2417
+ function flattenItem<T>(item: StrapiV4RawItem<T>): T {
2418
+ return { id: item.id, ...item.attributes } as T;
2419
+ }
2420
+
2421
+ /**
2422
+ * Recursively flatten nested relations in Strapi v4 response
2423
+ */
2424
+ function flattenRelations<T>(data: T): T {
2425
+ if (data === null || data === undefined) return data;
2426
+ if (Array.isArray(data)) {
2427
+ return data.map(item => flattenRelations(item)) as unknown as T;
2428
+ }
2429
+ if (typeof data === 'object') {
2430
+ const result: Record<string, unknown> = {};
2431
+ for (const [key, value] of Object.entries(data as Record<string, unknown>)) {
2432
+ // Check if this is a Strapi v4 relation response { data: { id, attributes } }
2433
+ if (value && typeof value === 'object' && 'data' in value) {
2434
+ const relationData = (value as { data: unknown }).data;
2435
+ if (relationData === null) {
2436
+ result[key] = null;
2437
+ } else if (Array.isArray(relationData)) {
2438
+ // To-many relation
2439
+ result[key] = relationData.map((item: StrapiV4RawItem<unknown>) =>
2440
+ flattenRelations(flattenItem(item))
2441
+ );
2442
+ } else if (typeof relationData === 'object' && 'id' in relationData && 'attributes' in relationData) {
2443
+ // To-one relation
2444
+ result[key] = flattenRelations(flattenItem(relationData as StrapiV4RawItem<unknown>));
2445
+ } else {
2446
+ result[key] = flattenRelations(value);
2447
+ }
2448
+ } else {
2449
+ result[key] = flattenRelations(value);
2450
+ }
2451
+ }
2452
+ return result as T;
2453
+ }
2454
+ return data;
2455
+ }
2456
+
2457
+ // Helper to get typed collection
2458
+ export function collection<T>(pluralName: string) {
2459
+ return {
2460
+ async find(params?: Record<string, unknown>): Promise<{ data: T[]; meta: { pagination: StrapiPagination } }> {
2461
+ const response = await strapi.find(pluralName, params) as unknown as StrapiV4RawListResponse<T>;
2462
+ const flattenedData = Array.isArray(response.data)
2463
+ ? response.data.map(item => flattenRelations(flattenItem<T>(item)))
2464
+ : [];
2465
+ return {
2466
+ data: flattenedData,
2467
+ meta: {
2468
+ pagination: response.meta?.pagination || defaultPagination,
2469
+ },
2470
+ };
2471
+ },
2472
+ async findOne(id: number | string, params?: Record<string, unknown>): Promise<{ data: T }> {
2473
+ const response = await strapi.findOne(pluralName, String(id), params) as unknown as StrapiV4RawSingleResponse<T>;
2474
+ return { data: flattenRelations(flattenItem<T>(response.data)) };
2475
+ },
2476
+ async create(data: { data: Partial<T> }): Promise<{ data: T }> {
2477
+ const response = await strapi.create(pluralName, data.data as Record<string, unknown>) as unknown as StrapiV4RawSingleResponse<T>;
2478
+ return { data: flattenRelations(flattenItem<T>(response.data)) };
2479
+ },
2480
+ async update(id: number | string, data: { data: Partial<T> }): Promise<{ data: T }> {
2481
+ const response = await strapi.update(pluralName, String(id), data.data as Record<string, unknown>) as unknown as StrapiV4RawSingleResponse<T>;
2482
+ return { data: flattenRelations(flattenItem<T>(response.data)) };
2483
+ },
2484
+ async delete(id: number | string): Promise<void> {
2485
+ await strapi.delete(pluralName, String(id));
2486
+ },
2487
+ };
2488
+ }
2489
+
2490
+ // Helper to get typed single type
2491
+ export function single<T>(singularName: string) {
2492
+ return {
2493
+ async find(params?: Record<string, unknown>): Promise<{ data: T }> {
2494
+ const response = await strapi.find(singularName, params) as unknown as StrapiV4RawSingleResponse<T>;
2495
+ return { data: flattenRelations(flattenItem<T>(response.data)) };
2496
+ },
2497
+ async update(data: { data: Partial<T> }): Promise<{ data: T }> {
2498
+ const response = await strapi.update(singularName, 1 as unknown as string, data.data as Record<string, unknown>) as unknown as StrapiV4RawSingleResponse<T>;
2499
+ return { data: flattenRelations(flattenItem<T>(response.data)) };
2500
+ },
2501
+ async delete(): Promise<void> {
2502
+ await strapi.delete(singularName, 1 as unknown as string);
2503
+ },
2504
+ };
2505
+ }
2506
+ `;
2507
+ }
2508
+ return `/**
2509
+ * Strapi Client (v5)
2510
+ * Generated by strapi2front
2511
+ */
2512
+
2513
+ import Strapi from 'strapi-sdk-js';
2514
+ import type { StrapiPagination } from './utils';
2515
+
2516
+ // Initialize the Strapi client
2517
+ const strapiUrl = import.meta.env.STRAPI_URL || process.env.STRAPI_URL || 'http://localhost:1337';
2518
+ const strapiToken = import.meta.env.STRAPI_TOKEN || process.env.STRAPI_TOKEN;
2519
+ const strapiApiPrefix = import.meta.env.STRAPI_API_PREFIX || process.env.STRAPI_API_PREFIX || '${normalizedPrefix}';
2520
+
2521
+ export const strapi = new Strapi({
2522
+ url: strapiUrl,
2523
+ prefix: strapiApiPrefix,
2524
+ axiosOptions: {
2525
+ headers: strapiToken ? {
2526
+ Authorization: \`Bearer \${strapiToken}\`,
2527
+ } : {},
2528
+ },
2529
+ });
2530
+
2531
+ // Default pagination for fallback
2532
+ const defaultPagination: StrapiPagination = {
2533
+ page: 1,
2534
+ pageSize: 25,
2535
+ pageCount: 1,
2536
+ total: 0,
2537
+ };
2538
+
2539
+ // Response types from strapi-sdk-js
2540
+ interface StrapiListResponse<T> {
2541
+ data: T[];
2542
+ meta: {
2543
+ pagination?: StrapiPagination;
2544
+ };
2545
+ }
2546
+
2547
+ interface StrapiSingleResponse<T> {
2548
+ data: T;
2549
+ meta?: Record<string, unknown>;
2550
+ }
2551
+
2552
+ // Helper to get typed collection
2553
+ export function collection<T>(pluralName: string) {
2554
+ return {
2555
+ async find(params?: Record<string, unknown>): Promise<{ data: T[]; meta: { pagination: StrapiPagination } }> {
2556
+ const response = await strapi.find(pluralName, params) as unknown as StrapiListResponse<T>;
2557
+ return {
2558
+ data: Array.isArray(response.data) ? response.data : [],
2559
+ meta: {
2560
+ pagination: response.meta?.pagination || defaultPagination,
2561
+ },
2562
+ };
2563
+ },
2564
+ async findOne(documentId: string, params?: Record<string, unknown>): Promise<{ data: T }> {
2565
+ const response = await strapi.findOne(pluralName, documentId, params) as unknown as StrapiSingleResponse<T>;
2566
+ return { data: response.data };
2567
+ },
2568
+ async create(data: { data: Partial<T> }): Promise<{ data: T }> {
2569
+ const response = await strapi.create(pluralName, data.data as Record<string, unknown>) as unknown as StrapiSingleResponse<T>;
2570
+ return { data: response.data };
2571
+ },
2572
+ async update(documentId: string, data: { data: Partial<T> }): Promise<{ data: T }> {
2573
+ const response = await strapi.update(pluralName, documentId, data.data as Record<string, unknown>) as unknown as StrapiSingleResponse<T>;
2574
+ return { data: response.data };
2575
+ },
2576
+ async delete(documentId: string): Promise<void> {
2577
+ await strapi.delete(pluralName, documentId);
2578
+ },
2579
+ };
2580
+ }
2581
+
2582
+ // Helper to get typed single type
2583
+ export function single<T>(singularName: string) {
2584
+ return {
2585
+ async find(params?: Record<string, unknown>): Promise<{ data: T }> {
2586
+ const response = await strapi.find(singularName, params) as unknown as StrapiSingleResponse<T>;
2587
+ return { data: response.data };
2588
+ },
2589
+ async update(data: { data: Partial<T> }): Promise<{ data: T }> {
2590
+ const response = await strapi.update(singularName, 1 as unknown as string, data.data as Record<string, unknown>) as unknown as StrapiSingleResponse<T>;
2591
+ return { data: response.data };
2592
+ },
2593
+ async delete(): Promise<void> {
2594
+ await strapi.delete(singularName, 1 as unknown as string);
2595
+ },
2596
+ };
2597
+ }
2598
+ `;
2599
+ }
2600
+ function generateLocalesFile2(locales) {
2601
+ if (locales.length === 0) {
2602
+ return `/**
2603
+ * Strapi locales
2604
+ * Generated by strapi2front
2605
+ * Note: i18n is not enabled in Strapi
2606
+ */
2607
+
2608
+ export const locales = [] as const;
2609
+ export type Locale = string;
2610
+ export const defaultLocale: Locale = 'en';
2611
+ export const localeNames: Record<string, string> = {};
2612
+
2613
+ export function isValidLocale(_code: string): _code is Locale {
2614
+ return true;
2615
+ }
2616
+
2617
+ export function getLocaleName(code: string): string {
2618
+ return code;
2619
+ }
2620
+ `;
2621
+ }
2622
+ const localeCodes = locales.map((l) => l.code);
2623
+ const defaultLocale = locales.find((l) => l.isDefault)?.code || locales[0]?.code || "en";
2624
+ return `/**
2625
+ * Strapi locales
2626
+ * Generated by strapi2front
2627
+ */
2628
+
2629
+ export const locales = [${localeCodes.map((c) => `'${c}'`).join(", ")}] as const;
2630
+
2631
+ export type Locale = typeof locales[number];
2632
+
2633
+ export const defaultLocale: Locale = '${defaultLocale}';
2634
+
2635
+ export const localeNames: Record<Locale, string> = {
2636
+ ${locales.map((l) => ` '${l.code}': '${l.name}'`).join(",\n")}
2637
+ };
2638
+
2639
+ export function isValidLocale(code: string): code is Locale {
2640
+ return locales.includes(code as Locale);
2641
+ }
2642
+
2643
+ export function getLocaleName(code: Locale): string {
2644
+ return localeNames[code] || code;
2645
+ }
2646
+ `;
2647
+ }
2648
+ function generateCollectionTypes(collection, schema) {
2649
+ const typeName = toPascalCase(collection.singularName);
2650
+ const attributes = generateAttributes2(collection.attributes);
2651
+ const imports = generateTypeImports(collection.attributes, schema, "collection");
2652
+ return `/**
2653
+ * ${collection.displayName}
2654
+ * ${collection.description || ""}
2655
+ * Generated by strapi2front
2656
+ */
2657
+
2658
+ ${imports}
2659
+
2660
+ export interface ${typeName} extends StrapiBaseEntity {
2661
+ ${attributes}
2662
+ }
2663
+
2664
+ export interface ${typeName}Filters {
2665
+ id?: number | { $eq?: number; $ne?: number; $in?: number[]; $notIn?: number[] };
2666
+ documentId?: string | { $eq?: string; $ne?: string };
2667
+ createdAt?: string | { $eq?: string; $gt?: string; $gte?: string; $lt?: string; $lte?: string };
2668
+ updatedAt?: string | { $eq?: string; $gt?: string; $gte?: string; $lt?: string; $lte?: string };
2669
+ publishedAt?: string | null | { $eq?: string; $ne?: string; $null?: boolean };
2670
+ $and?: ${typeName}Filters[];
2671
+ $or?: ${typeName}Filters[];
2672
+ $not?: ${typeName}Filters;
2673
+ }
2674
+ `;
2675
+ }
2676
+ function generateSingleTypes(single, schema) {
2677
+ const typeName = toPascalCase(single.singularName);
2678
+ const attributes = generateAttributes2(single.attributes);
2679
+ const imports = generateTypeImports(single.attributes, schema, "single");
2680
+ return `/**
2681
+ * ${single.displayName}
2682
+ * ${single.description || ""}
2683
+ * Generated by strapi2front
2684
+ */
2685
+
2686
+ ${imports}
2687
+
2688
+ export interface ${typeName} extends StrapiBaseEntity {
2689
+ ${attributes}
2690
+ }
2691
+ `;
2692
+ }
2693
+ function generateComponentTypes(component, schema) {
2694
+ const typeName = toPascalCase(component.name);
2695
+ const attributes = generateAttributes2(component.attributes);
2696
+ const imports = generateTypeImports(component.attributes, schema, "component");
2697
+ return `/**
2698
+ * ${component.displayName} component
2699
+ * Category: ${component.category}
2700
+ * ${component.description || ""}
2701
+ * Generated by strapi2front
2702
+ */
2703
+
2704
+ ${imports}
2705
+
2706
+ export interface ${typeName} {
2707
+ id: number;
2708
+ ${attributes}
2709
+ }
2710
+ `;
2711
+ }
2712
+ function generateTypeImports(attributes, schema, context) {
2713
+ const utilsImports = [];
2714
+ const relationImports = /* @__PURE__ */ new Map();
2715
+ const componentImports = /* @__PURE__ */ new Map();
2716
+ const attributesStr = JSON.stringify(attributes);
2717
+ if (context !== "component") {
2718
+ utilsImports.push("StrapiBaseEntity");
2719
+ }
2720
+ if (attributesStr.includes('"type":"media"')) {
2721
+ utilsImports.push("StrapiMedia");
2722
+ }
2723
+ if (attributesStr.includes('"type":"blocks"')) {
2724
+ utilsImports.push("BlocksContent");
2725
+ }
2726
+ const relativePrefix = context === "component" ? ".." : "../..";
2727
+ for (const attr of Object.values(attributes)) {
2728
+ if (attr.type === "relation" && "target" in attr && attr.target) {
2729
+ const targetName = attr.target.split(".").pop() || "";
2730
+ if (targetName) {
2731
+ const typeName = toPascalCase(targetName);
2732
+ const fileName = toKebabCase(targetName);
2733
+ const isCollection = schema.collections.some((c) => c.singularName === targetName);
2734
+ const isSingle = schema.singles.some((s) => s.singularName === targetName);
2735
+ if (isCollection) {
2736
+ relationImports.set(typeName, `${relativePrefix}/collections/${fileName}/types`);
2737
+ } else if (isSingle) {
2738
+ relationImports.set(typeName, `${relativePrefix}/singles/${fileName}/types`);
2739
+ }
2740
+ }
2741
+ }
2742
+ if (attr.type === "component" && "component" in attr && attr.component) {
2743
+ const componentName = attr.component.split(".").pop() || "";
2744
+ if (componentName) {
2745
+ const typeName = toPascalCase(componentName);
2746
+ const fileName = toKebabCase(componentName);
2747
+ if (context === "component") {
2748
+ componentImports.set(typeName, `./${fileName}`);
2749
+ } else {
2750
+ componentImports.set(typeName, `../../components/${fileName}`);
2751
+ }
2752
+ }
2753
+ }
2754
+ if (attr.type === "dynamiczone" && "components" in attr && attr.components) {
2755
+ for (const comp of attr.components) {
2756
+ const componentName = comp.split(".").pop() || "";
2757
+ if (componentName) {
2758
+ const typeName = toPascalCase(componentName);
2759
+ const fileName = toKebabCase(componentName);
2760
+ if (context === "component") {
2761
+ componentImports.set(typeName, `./${fileName}`);
2762
+ } else {
2763
+ componentImports.set(typeName, `../../components/${fileName}`);
2764
+ }
2765
+ }
2766
+ }
2767
+ }
2768
+ }
2769
+ const lines = [];
2770
+ if (utilsImports.length > 0) {
2771
+ const utilsPath = context === "component" ? "../shared/utils" : "../../shared/utils";
2772
+ lines.push(`import type { ${utilsImports.join(", ")} } from '${utilsPath}';`);
2773
+ }
2774
+ for (const [typeName, importPath] of relationImports) {
2775
+ lines.push(`import type { ${typeName} } from '${importPath}';`);
2776
+ }
2777
+ for (const [typeName, importPath] of componentImports) {
2778
+ lines.push(`import type { ${typeName} } from '${importPath}';`);
2779
+ }
2780
+ return lines.join("\n");
2781
+ }
2782
+ function generateAttributes2(attributes) {
2783
+ const lines = [];
2784
+ for (const [name, attr] of Object.entries(attributes)) {
2785
+ const tsType = attributeToTsType(attr);
2786
+ const optional = attr.required ? "" : "?";
2787
+ lines.push(` ${name}${optional}: ${tsType};`);
2788
+ }
2789
+ return lines.join("\n");
2790
+ }
2791
+ function attributeToTsType(attr) {
2792
+ switch (attr.type) {
2793
+ case "string":
2794
+ case "text":
2795
+ case "richtext":
2796
+ case "email":
2797
+ case "password":
2798
+ case "uid":
2799
+ return "string";
2800
+ case "blocks":
2801
+ return "BlocksContent";
2802
+ case "integer":
2803
+ case "biginteger":
2804
+ case "float":
2805
+ case "decimal":
2806
+ return "number";
2807
+ case "boolean":
2808
+ return "boolean";
2809
+ case "date":
2810
+ case "time":
2811
+ case "datetime":
2812
+ case "timestamp":
2813
+ return "string";
2814
+ case "json":
2815
+ return "unknown";
2816
+ case "enumeration":
2817
+ if ("enum" in attr && attr.enum) {
2818
+ return attr.enum.map((v) => `'${v}'`).join(" | ");
2819
+ }
2820
+ return "string";
2821
+ case "media":
2822
+ if ("multiple" in attr && attr.multiple) {
2823
+ return "StrapiMedia[]";
2824
+ }
2825
+ return "StrapiMedia | null";
2826
+ case "relation":
2827
+ if ("target" in attr && attr.target) {
2828
+ const targetName = toPascalCase(attr.target.split(".").pop() || "unknown");
2829
+ const isMany = attr.relation === "oneToMany" || attr.relation === "manyToMany";
2830
+ return isMany ? `${targetName}[]` : `${targetName} | null`;
2831
+ }
2832
+ return "unknown";
2833
+ case "component":
2834
+ if ("component" in attr && attr.component) {
2835
+ const componentName = toPascalCase(attr.component.split(".").pop() || "unknown");
2836
+ if ("repeatable" in attr && attr.repeatable) {
2837
+ return `${componentName}[]`;
2838
+ }
2839
+ return `${componentName} | null`;
2840
+ }
2841
+ return "unknown";
2842
+ case "dynamiczone":
2843
+ if ("components" in attr && attr.components) {
2844
+ const types = attr.components.map((c) => toPascalCase(c.split(".").pop() || "unknown"));
2845
+ return `(${types.join(" | ")})[]`;
2846
+ }
2847
+ return "unknown[]";
2848
+ default:
2849
+ return "unknown";
2850
+ }
2851
+ }
2852
+ function generateCollectionService3(collection, strapiVersion) {
2853
+ const typeName = toPascalCase(collection.singularName);
2854
+ const serviceName = toCamelCase(collection.singularName) + "Service";
2855
+ const endpoint = collection.pluralName;
2856
+ const hasSlug = "slug" in collection.attributes;
2857
+ const { localized, draftAndPublish } = collection;
2858
+ const isV4 = strapiVersion === "v4";
2859
+ const idParam = isV4 ? "id: number" : "documentId: string";
2860
+ const idName = isV4 ? "id" : "documentId";
2861
+ const omitFields = isV4 ? "'id' | 'createdAt' | 'updatedAt' | 'publishedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt' | 'publishedAt'";
2862
+ const omitFieldsUpdate = isV4 ? "'id' | 'createdAt' | 'updatedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt'";
2863
+ const imports = [
2864
+ `import { collection } from '../../shared/client';`,
2865
+ `import type { ${typeName}, ${typeName}Filters } from './types';`,
2866
+ `import type { StrapiPagination } from '../../shared/utils';`
2867
+ ];
2868
+ if (localized) {
2869
+ imports.push(`import type { Locale } from '../../shared/locales';`);
2870
+ }
2871
+ const paginationFields = `
2872
+ /** Page number (1-indexed) - use with pageSize */
2873
+ page?: number;
2874
+ /** Number of items per page (default: 25) - use with page */
2875
+ pageSize?: number;
2876
+ /** Offset to start from (0-indexed) - use with limit */
2877
+ start?: number;
2878
+ /** Maximum number of items to return - use with start */
2879
+ limit?: number;`;
2880
+ let findManyOptionsFields = ` filters?: ${typeName}Filters;
2881
+ pagination?: {${paginationFields}
2882
+ };
2883
+ sort?: string | string[];
2884
+ populate?: string | string[] | Record<string, unknown>;`;
2885
+ let findOneOptionsFields = ` populate?: string | string[] | Record<string, unknown>;`;
2886
+ if (localized) {
2887
+ findManyOptionsFields += `
2888
+ locale?: Locale;`;
2889
+ findOneOptionsFields += `
2890
+ locale?: Locale;`;
2891
+ }
2892
+ if (draftAndPublish) {
2893
+ findManyOptionsFields += `
2894
+ status?: 'draft' | 'published';`;
2895
+ findOneOptionsFields += `
2896
+ status?: 'draft' | 'published';`;
2897
+ }
2898
+ let findParams = ` filters: options.filters,
2899
+ pagination: options.pagination,
2900
+ sort: options.sort,
2901
+ populate: options.populate,`;
2902
+ let findOneParams = ` populate: options.populate,`;
2903
+ if (localized) {
2904
+ findParams += `
2905
+ locale: options.locale,`;
2906
+ findOneParams += `
2907
+ locale: options.locale,`;
2908
+ }
2909
+ if (draftAndPublish) {
2910
+ findParams += `
2911
+ status: options.status,`;
2912
+ findOneParams += `
2913
+ status: options.status,`;
2914
+ }
2915
+ return `/**
2916
+ * ${collection.displayName} Service
2917
+ * ${collection.description || ""}
2918
+ * Generated by strapi2front
2919
+ * Strapi version: ${strapiVersion}
2920
+ */
2921
+
2922
+ ${imports.join("\n")}
2923
+
2924
+ export interface FindManyOptions {
2925
+ ${findManyOptionsFields}
2926
+ }
2927
+
2928
+ export interface FindOneOptions {
2929
+ ${findOneOptionsFields}
2930
+ }
2931
+
2932
+ // Create typed collection helper
2933
+ const ${toCamelCase(collection.singularName)}Collection = collection<${typeName}>('${endpoint}');
2934
+
2935
+ export const ${serviceName} = {
2936
+ async findMany(options: FindManyOptions = {}): Promise<{ data: ${typeName}[]; pagination: StrapiPagination }> {
2937
+ const response = await ${toCamelCase(collection.singularName)}Collection.find({
2938
+ ${findParams}
2939
+ });
2940
+
2941
+ return {
2942
+ data: response.data,
2943
+ pagination: response.meta.pagination,
2944
+ };
2945
+ },
2946
+
2947
+ async findAll(options: Omit<FindManyOptions, 'pagination'> = {}): Promise<${typeName}[]> {
2948
+ const allItems: ${typeName}[] = [];
2949
+ let page = 1;
2950
+ let hasMore = true;
2951
+
2952
+ while (hasMore) {
2953
+ const { data, pagination } = await this.findMany({
2954
+ ...options,
2955
+ pagination: { page, pageSize: 100 },
2956
+ });
2957
+
2958
+ allItems.push(...data);
2959
+ hasMore = page < pagination.pageCount;
2960
+ page++;
2961
+ }
2962
+
2963
+ return allItems;
2964
+ },
2965
+
2966
+ async findOne(${idParam}, options: FindOneOptions = {}): Promise<${typeName} | null> {
2967
+ try {
2968
+ const response = await ${toCamelCase(collection.singularName)}Collection.findOne(${idName}, {
2969
+ ${findOneParams}
2970
+ });
2971
+
2972
+ return response.data;
2973
+ } catch (error) {
2974
+ if (error instanceof Error && error.message.includes('404')) {
2975
+ return null;
2976
+ }
2977
+ throw error;
2978
+ }
2979
+ },
2980
+ ${hasSlug ? `
2981
+ async findBySlug(slug: string, options: FindOneOptions = {}): Promise<${typeName} | null> {
2982
+ const { data } = await this.findMany({
2983
+ filters: { slug: { $eq: slug } } as ${typeName}Filters,
2984
+ pagination: { pageSize: 1 },
2985
+ populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
2986
+ });
2987
+
2988
+ return data[0] || null;
2989
+ },
2990
+ ` : ""}
2991
+ async create(data: Partial<Omit<${typeName}, ${omitFields}>>): Promise<${typeName}> {
2992
+ const response = await ${toCamelCase(collection.singularName)}Collection.create({ data });
2993
+ return response.data;
2994
+ },
2995
+
2996
+ async update(${idParam}, data: Partial<Omit<${typeName}, ${omitFieldsUpdate}>>): Promise<${typeName}> {
2997
+ const response = await ${toCamelCase(collection.singularName)}Collection.update(${idName}, { data });
2998
+ return response.data;
2999
+ },
3000
+
3001
+ async delete(${idParam}): Promise<void> {
3002
+ await ${toCamelCase(collection.singularName)}Collection.delete(${idName});
3003
+ },
3004
+
3005
+ async count(filters?: ${typeName}Filters): Promise<number> {
3006
+ const { pagination } = await this.findMany({
3007
+ filters,
3008
+ pagination: { pageSize: 1 },
3009
+ });
3010
+
3011
+ return pagination.total;
3012
+ },
3013
+ };
3014
+ `;
3015
+ }
3016
+ function generateSingleService3(single, strapiVersion) {
3017
+ const typeName = toPascalCase(single.singularName);
3018
+ const serviceName = toCamelCase(single.singularName) + "Service";
3019
+ const endpoint = single.singularName;
3020
+ const { localized, draftAndPublish } = single;
3021
+ const isV4 = strapiVersion === "v4";
3022
+ const omitFields = isV4 ? "'id' | 'createdAt' | 'updatedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt'";
3023
+ const imports = [
3024
+ `import { single } from '../../shared/client';`,
3025
+ `import type { ${typeName} } from './types';`
3026
+ ];
3027
+ if (localized) {
3028
+ imports.push(`import type { Locale } from '../../shared/locales';`);
3029
+ }
3030
+ let findOptionsFields = ` populate?: string | string[] | Record<string, unknown>;`;
3031
+ if (localized) {
3032
+ findOptionsFields += `
3033
+ locale?: Locale;`;
3034
+ }
3035
+ if (draftAndPublish) {
3036
+ findOptionsFields += `
3037
+ status?: 'draft' | 'published';`;
3038
+ }
3039
+ let findParams = ` populate: options.populate,`;
3040
+ if (localized) {
3041
+ findParams += `
3042
+ locale: options.locale,`;
3043
+ }
3044
+ if (draftAndPublish) {
3045
+ findParams += `
3046
+ status: options.status,`;
3047
+ }
3048
+ return `/**
3049
+ * ${single.displayName} Service (Single Type)
3050
+ * ${single.description || ""}
3051
+ * Generated by strapi2front
3052
+ * Strapi version: ${strapiVersion}
3053
+ */
3054
+
3055
+ ${imports.join("\n")}
3056
+
3057
+ export interface FindOptions {
3058
+ ${findOptionsFields}
3059
+ }
3060
+
3061
+ // Create typed single helper
3062
+ const ${toCamelCase(single.singularName)}Single = single<${typeName}>('${endpoint}');
3063
+
3064
+ export const ${serviceName} = {
3065
+ async find(options: FindOptions = {}): Promise<${typeName} | null> {
3066
+ try {
3067
+ const response = await ${toCamelCase(single.singularName)}Single.find({
3068
+ ${findParams}
3069
+ });
3070
+
3071
+ return response.data;
3072
+ } catch (error) {
3073
+ if (error instanceof Error && error.message.includes('404')) {
3074
+ return null;
3075
+ }
3076
+ throw error;
3077
+ }
3078
+ },
3079
+
3080
+ async update(data: Partial<Omit<${typeName}, ${omitFields}>>): Promise<${typeName}> {
3081
+ const response = await ${toCamelCase(single.singularName)}Single.update({ data });
3082
+ return response.data;
3083
+ },
3084
+
3085
+ async delete(): Promise<void> {
3086
+ await ${toCamelCase(single.singularName)}Single.delete();
3087
+ },
3088
+ };
3089
+ `;
3090
+ }
3091
+ function generateCollectionActions2(collection, strapiVersion) {
3092
+ const typeName = toPascalCase(collection.singularName);
3093
+ const serviceName = toCamelCase(collection.singularName) + "Service";
3094
+ const actionPrefix = toCamelCase(collection.singularName);
3095
+ const isV4 = strapiVersion === "v4";
3096
+ const idInputSchema = isV4 ? "z.number().int().positive()" : "z.string()";
3097
+ const idParamName = isV4 ? "id" : "documentId";
3098
+ return `/**
3099
+ * ${collection.displayName} Astro Actions
3100
+ * ${collection.description || ""}
3101
+ * Generated by strapi2front
3102
+ * Strapi version: ${strapiVersion}
3103
+ */
3104
+
3105
+ import { defineAction } from 'astro:actions';
3106
+ import { z } from 'astro:schema';
3107
+ import { ${serviceName} } from './service';
3108
+ import type { ${typeName} } from './types';
3109
+
3110
+ export const ${actionPrefix}Actions = {
3111
+ getMany: defineAction({
3112
+ input: z.object({
3113
+ page: z.number().optional(),
3114
+ pageSize: z.number().optional(),
3115
+ sort: z.string().optional(),
3116
+ }).optional(),
3117
+ handler: async (input) => {
3118
+ const { data, pagination } = await ${serviceName}.findMany({
3119
+ pagination: input ? { page: input.page, pageSize: input.pageSize } : undefined,
3120
+ sort: input?.sort,
3121
+ });
3122
+ return { data, pagination };
3123
+ },
3124
+ }),
3125
+
3126
+ getOne: defineAction({
3127
+ input: z.object({
3128
+ ${idParamName}: ${idInputSchema},
3129
+ }),
3130
+ handler: async (input) => {
3131
+ const data = await ${serviceName}.findOne(input.${idParamName});
3132
+ return { data };
3133
+ },
3134
+ }),
3135
+
3136
+ create: defineAction({
3137
+ input: z.object({
3138
+ data: z.record(z.unknown()),
3139
+ }),
3140
+ handler: async (input) => {
3141
+ const data = await ${serviceName}.create(input.data as Partial<${typeName}>);
3142
+ return { data };
3143
+ },
3144
+ }),
1477
3145
 
1478
- /**
1479
- * Check if a string is a valid locale
1480
- */
1481
- export function isValidLocale(code: string): code is Locale {
1482
- return locales.includes(code as Locale);
1483
- }
3146
+ update: defineAction({
3147
+ input: z.object({
3148
+ ${idParamName}: ${idInputSchema},
3149
+ data: z.record(z.unknown()),
3150
+ }),
3151
+ handler: async (input) => {
3152
+ const data = await ${serviceName}.update(input.${idParamName}, input.data as Partial<${typeName}>);
3153
+ return { data };
3154
+ },
3155
+ }),
1484
3156
 
1485
- /**
1486
- * Get locale name by code
1487
- */
1488
- export function getLocaleName(code: Locale): string {
1489
- return localeNames[code] || code;
1490
- }
3157
+ delete: defineAction({
3158
+ input: z.object({
3159
+ ${idParamName}: ${idInputSchema},
3160
+ }),
3161
+ handler: async (input) => {
3162
+ await ${serviceName}.delete(input.${idParamName});
3163
+ return { success: true };
3164
+ },
3165
+ }),
3166
+ };
1491
3167
  `;
1492
3168
  }
1493
- async function generateByFeature(schema, locales, options) {
1494
- const { outputDir, features, blocksRendererInstalled = false, strapiVersion = "v5" } = options;
1495
- const generatedFiles = [];
1496
- await ensureDir(path7.join(outputDir, "collections"));
1497
- await ensureDir(path7.join(outputDir, "singles"));
1498
- await ensureDir(path7.join(outputDir, "components"));
1499
- await ensureDir(path7.join(outputDir, "shared"));
1500
- const sharedDir = path7.join(outputDir, "shared");
1501
- const utilsPath = path7.join(sharedDir, "utils.ts");
1502
- await writeFile(utilsPath, await formatCode(generateUtilityTypes2(blocksRendererInstalled, strapiVersion)));
1503
- generatedFiles.push(utilsPath);
1504
- const clientPath = path7.join(sharedDir, "client.ts");
1505
- await writeFile(clientPath, await formatCode(generateClient2(strapiVersion)));
1506
- generatedFiles.push(clientPath);
1507
- const localesPath = path7.join(sharedDir, "locales.ts");
1508
- await writeFile(localesPath, await formatCode(generateLocalesFile2(locales)));
1509
- generatedFiles.push(localesPath);
1510
- for (const collection of schema.collections) {
1511
- const featureDir = path7.join(outputDir, "collections", toKebabCase(collection.singularName));
1512
- await ensureDir(featureDir);
1513
- if (features.types) {
1514
- const typesPath = path7.join(featureDir, "types.ts");
1515
- await writeFile(typesPath, await formatCode(generateCollectionTypes(collection, schema)));
1516
- generatedFiles.push(typesPath);
1517
- }
1518
- if (features.services) {
1519
- const servicePath = path7.join(featureDir, "service.ts");
1520
- await writeFile(servicePath, await formatCode(generateCollectionService2(collection, strapiVersion)));
1521
- generatedFiles.push(servicePath);
1522
- }
1523
- if (features.actions) {
1524
- const actionsPath = path7.join(featureDir, "actions.ts");
1525
- await writeFile(actionsPath, await formatCode(generateCollectionActions2(collection, strapiVersion)));
1526
- generatedFiles.push(actionsPath);
1527
- }
1528
- }
1529
- for (const single of schema.singles) {
1530
- const featureDir = path7.join(outputDir, "singles", toKebabCase(single.singularName));
1531
- await ensureDir(featureDir);
1532
- if (features.types) {
1533
- const typesPath = path7.join(featureDir, "types.ts");
1534
- await writeFile(typesPath, await formatCode(generateSingleTypes(single, schema)));
1535
- generatedFiles.push(typesPath);
1536
- }
1537
- if (features.services) {
1538
- const servicePath = path7.join(featureDir, "service.ts");
1539
- await writeFile(servicePath, await formatCode(generateSingleService2(single, strapiVersion)));
1540
- generatedFiles.push(servicePath);
1541
- }
1542
- }
1543
- for (const component of schema.components) {
1544
- const componentPath = path7.join(outputDir, "components", `${toKebabCase(component.name)}.ts`);
1545
- await writeFile(componentPath, await formatCode(generateComponentTypes(component, schema)));
1546
- generatedFiles.push(componentPath);
1547
- }
1548
- return generatedFiles;
1549
- }
1550
- function generateUtilityTypes2(blocksRendererInstalled, strapiVersion) {
3169
+ function generateUtilityTypesJSDoc(blocksRendererInstalled, strapiVersion) {
1551
3170
  const isV4 = strapiVersion === "v4";
1552
3171
  const blocksContentType = isV4 ? `/**
1553
3172
  * Rich text content (Strapi v4)
1554
3173
  * Can be markdown string or custom JSON structure
1555
- */
1556
- export type RichTextContent = string;` : blocksRendererInstalled ? `/**
1557
- * Blocks content type (Strapi v5 rich text)
1558
- * Re-exported from @strapi/blocks-react-renderer
1559
- */
1560
- export type { BlocksContent } from '@strapi/blocks-react-renderer';` : `/**
3174
+ * @typedef {string} RichTextContent
3175
+ */` : blocksRendererInstalled ? `// BlocksContent - import from '@strapi/blocks-react-renderer' for full type support` : `/**
1561
3176
  * Blocks content type (Strapi v5 rich text)
1562
3177
  *
1563
3178
  * For full type support and rendering, install:
1564
3179
  * npm install @strapi/blocks-react-renderer
1565
3180
  *
1566
3181
  * Then re-run: npx strapi2front sync
1567
- */
1568
- export type BlocksContent = unknown[];`;
1569
- const baseEntity = isV4 ? `export interface StrapiBaseEntity {
1570
- id: number;
1571
- createdAt: string;
1572
- updatedAt: string;
1573
- publishedAt: string | null;
1574
- }` : `export interface StrapiBaseEntity {
1575
- id: number;
1576
- documentId: string;
1577
- createdAt: string;
1578
- updatedAt: string;
1579
- publishedAt: string | null;
1580
- }`;
1581
- const mediaType = isV4 ? `export interface StrapiMedia {
1582
- id: number;
1583
- name: string;
1584
- alternativeText: string | null;
1585
- caption: string | null;
1586
- width: number;
1587
- height: number;
1588
- formats: {
1589
- thumbnail?: StrapiMediaFormat;
1590
- small?: StrapiMediaFormat;
1591
- medium?: StrapiMediaFormat;
1592
- large?: StrapiMediaFormat;
1593
- } | null;
1594
- hash: string;
1595
- ext: string;
1596
- mime: string;
1597
- size: number;
1598
- url: string;
1599
- previewUrl: string | null;
1600
- provider: string;
1601
- createdAt: string;
1602
- updatedAt: string;
1603
- }` : `export interface StrapiMedia {
1604
- id: number;
1605
- documentId: string;
1606
- name: string;
1607
- alternativeText: string | null;
1608
- caption: string | null;
1609
- width: number;
1610
- height: number;
1611
- formats: {
1612
- thumbnail?: StrapiMediaFormat;
1613
- small?: StrapiMediaFormat;
1614
- medium?: StrapiMediaFormat;
1615
- large?: StrapiMediaFormat;
1616
- } | null;
1617
- hash: string;
1618
- ext: string;
1619
- mime: string;
1620
- size: number;
1621
- url: string;
1622
- previewUrl: string | null;
1623
- provider: string;
1624
- createdAt: string;
1625
- updatedAt: string;
1626
- }`;
3182
+ * @typedef {Array<Object>} BlocksContent
3183
+ */`;
3184
+ const baseEntity = isV4 ? `/**
3185
+ * @typedef {Object} StrapiBaseEntity
3186
+ * @property {number} id
3187
+ * @property {string} createdAt
3188
+ * @property {string} updatedAt
3189
+ * @property {string|null} publishedAt
3190
+ */` : `/**
3191
+ * @typedef {Object} StrapiBaseEntity
3192
+ * @property {number} id
3193
+ * @property {string} documentId
3194
+ * @property {string} createdAt
3195
+ * @property {string} updatedAt
3196
+ * @property {string|null} publishedAt
3197
+ */`;
3198
+ const mediaType = isV4 ? `/**
3199
+ * @typedef {Object} StrapiMedia
3200
+ * @property {number} id
3201
+ * @property {string} name
3202
+ * @property {string|null} alternativeText
3203
+ * @property {string|null} caption
3204
+ * @property {number} width
3205
+ * @property {number} height
3206
+ * @property {Object|null} formats
3207
+ * @property {StrapiMediaFormat} [formats.thumbnail]
3208
+ * @property {StrapiMediaFormat} [formats.small]
3209
+ * @property {StrapiMediaFormat} [formats.medium]
3210
+ * @property {StrapiMediaFormat} [formats.large]
3211
+ * @property {string} hash
3212
+ * @property {string} ext
3213
+ * @property {string} mime
3214
+ * @property {number} size
3215
+ * @property {string} url
3216
+ * @property {string|null} previewUrl
3217
+ * @property {string} provider
3218
+ * @property {string} createdAt
3219
+ * @property {string} updatedAt
3220
+ */` : `/**
3221
+ * @typedef {Object} StrapiMedia
3222
+ * @property {number} id
3223
+ * @property {string} documentId
3224
+ * @property {string} name
3225
+ * @property {string|null} alternativeText
3226
+ * @property {string|null} caption
3227
+ * @property {number} width
3228
+ * @property {number} height
3229
+ * @property {Object|null} formats
3230
+ * @property {StrapiMediaFormat} [formats.thumbnail]
3231
+ * @property {StrapiMediaFormat} [formats.small]
3232
+ * @property {StrapiMediaFormat} [formats.medium]
3233
+ * @property {StrapiMediaFormat} [formats.large]
3234
+ * @property {string} hash
3235
+ * @property {string} ext
3236
+ * @property {string} mime
3237
+ * @property {number} size
3238
+ * @property {string} url
3239
+ * @property {string|null} previewUrl
3240
+ * @property {string} provider
3241
+ * @property {string} createdAt
3242
+ * @property {string} updatedAt
3243
+ */`;
1627
3244
  const v4RawResponseTypes = isV4 ? `
1628
3245
 
1629
3246
  /**
1630
- * Strapi v4 raw API response (with nested attributes)
3247
+ * Strapi v4 raw API response item (with nested attributes)
3248
+ * @template T
3249
+ * @typedef {Object} StrapiV4RawItem
3250
+ * @property {number} id
3251
+ * @property {Omit<T, 'id'>} attributes
1631
3252
  */
1632
- export interface StrapiV4RawItem<T> {
1633
- id: number;
1634
- attributes: Omit<T, 'id'>;
1635
- }
1636
3253
 
1637
- export interface StrapiV4RawResponse<T> {
1638
- data: StrapiV4RawItem<T>;
1639
- meta: Record<string, unknown>;
1640
- }
3254
+ /**
3255
+ * Strapi v4 raw API response
3256
+ * @template T
3257
+ * @typedef {Object} StrapiV4RawResponse
3258
+ * @property {StrapiV4RawItem<T>} data
3259
+ * @property {Object} meta
3260
+ */
1641
3261
 
1642
- export interface StrapiV4RawListResponse<T> {
1643
- data: StrapiV4RawItem<T>[];
1644
- meta: {
1645
- pagination: StrapiPagination;
1646
- };
1647
- }
3262
+ /**
3263
+ * Strapi v4 raw list response
3264
+ * @template T
3265
+ * @typedef {Object} StrapiV4RawListResponse
3266
+ * @property {StrapiV4RawItem<T>[]} data
3267
+ * @property {Object} meta
3268
+ * @property {StrapiPagination} meta.pagination
3269
+ */
1648
3270
 
1649
3271
  /**
1650
3272
  * Flatten Strapi v4 response item
3273
+ * @template T
3274
+ * @param {StrapiV4RawItem<T>} item
3275
+ * @returns {T}
1651
3276
  */
1652
- export function flattenV4Response<T>(item: StrapiV4RawItem<T>): T {
1653
- return { id: item.id, ...item.attributes } as T;
3277
+ function flattenV4Response(item) {
3278
+ return { id: item.id, ...item.attributes };
1654
3279
  }
1655
3280
 
1656
3281
  /**
1657
3282
  * Flatten Strapi v4 list response
3283
+ * @template T
3284
+ * @param {StrapiV4RawItem<T>[]} items
3285
+ * @returns {T[]}
1658
3286
  */
1659
- export function flattenV4ListResponse<T>(items: StrapiV4RawItem<T>[]): T[] {
1660
- return items.map(item => flattenV4Response<T>(item));
1661
- }` : "";
1662
- return `/**
3287
+ function flattenV4ListResponse(items) {
3288
+ return items.map(item => flattenV4Response(item));
3289
+ }
3290
+
3291
+ module.exports = { flattenV4Response, flattenV4ListResponse };` : "";
3292
+ return `// @ts-check
3293
+ /**
1663
3294
  * Strapi utility types
1664
3295
  * Generated by strapi2front
1665
3296
  * Strapi version: ${strapiVersion}
@@ -1667,60 +3298,69 @@ export function flattenV4ListResponse<T>(items: StrapiV4RawItem<T>[]): T[] {
1667
3298
 
1668
3299
  ${mediaType}
1669
3300
 
1670
- export interface StrapiMediaFormat {
1671
- name: string;
1672
- hash: string;
1673
- ext: string;
1674
- mime: string;
1675
- width: number;
1676
- height: number;
1677
- size: number;
1678
- url: string;
1679
- }
3301
+ /**
3302
+ * @typedef {Object} StrapiMediaFormat
3303
+ * @property {string} name
3304
+ * @property {string} hash
3305
+ * @property {string} ext
3306
+ * @property {string} mime
3307
+ * @property {number} width
3308
+ * @property {number} height
3309
+ * @property {number} size
3310
+ * @property {string} url
3311
+ */
1680
3312
 
1681
- export interface StrapiPagination {
1682
- page: number;
1683
- pageSize: number;
1684
- pageCount: number;
1685
- total: number;
1686
- }
3313
+ /**
3314
+ * @typedef {Object} StrapiPagination
3315
+ * @property {number} page
3316
+ * @property {number} pageSize
3317
+ * @property {number} pageCount
3318
+ * @property {number} total
3319
+ */
1687
3320
 
1688
- export interface StrapiResponse<T> {
1689
- data: T;
1690
- meta: {
1691
- pagination?: StrapiPagination;
1692
- };
1693
- }
3321
+ /**
3322
+ * @template T
3323
+ * @typedef {Object} StrapiResponse
3324
+ * @property {T} data
3325
+ * @property {Object} meta
3326
+ * @property {StrapiPagination} [meta.pagination]
3327
+ */
1694
3328
 
1695
- export interface StrapiListResponse<T> {
1696
- data: T[];
1697
- meta: {
1698
- pagination: StrapiPagination;
1699
- };
1700
- }
3329
+ /**
3330
+ * @template T
3331
+ * @typedef {Object} StrapiListResponse
3332
+ * @property {T[]} data
3333
+ * @property {Object} meta
3334
+ * @property {StrapiPagination} meta.pagination
3335
+ */
1701
3336
 
1702
3337
  ${baseEntity}
1703
3338
  ${v4RawResponseTypes}
1704
3339
  ${blocksContentType}
3340
+
3341
+ module.exports = {};
1705
3342
  `;
1706
3343
  }
1707
- function generateClient2(strapiVersion) {
3344
+ function generateClientJSDoc(strapiVersion, apiPrefix = "/api") {
1708
3345
  const isV4 = strapiVersion === "v4";
3346
+ const normalizedPrefix = apiPrefix.startsWith("/") ? apiPrefix : "/" + apiPrefix;
1709
3347
  if (isV4) {
1710
- return `/**
3348
+ return `// @ts-check
3349
+ /**
1711
3350
  * Strapi Client (v4)
1712
3351
  * Generated by strapi2front
1713
3352
  */
1714
3353
 
1715
- import Strapi from 'strapi-sdk-js';
1716
- import type { StrapiPagination } from './utils';
3354
+ const Strapi = require('strapi-sdk-js').default;
1717
3355
 
1718
3356
  // Initialize the Strapi client
1719
- const strapiUrl = import.meta.env.STRAPI_URL || process.env.STRAPI_URL || 'http://localhost:1337';
1720
- const strapiToken = import.meta.env.STRAPI_TOKEN || process.env.STRAPI_TOKEN;
3357
+ const strapiUrl = process.env.STRAPI_URL || 'http://localhost:1337';
3358
+ const strapiToken = process.env.STRAPI_TOKEN;
3359
+ const strapiApiPrefix = process.env.STRAPI_API_PREFIX || '${normalizedPrefix}';
1721
3360
 
1722
- export const strapi = new Strapi({
3361
+ const strapi = new Strapi({
1723
3362
  url: strapiUrl,
3363
+ prefix: strapiApiPrefix,
1724
3364
  axiosOptions: {
1725
3365
  headers: strapiToken ? {
1726
3366
  Authorization: \`Bearer \${strapiToken}\`,
@@ -1728,63 +3368,49 @@ export const strapi = new Strapi({
1728
3368
  },
1729
3369
  });
1730
3370
 
1731
- // Default pagination for fallback
1732
- const defaultPagination: StrapiPagination = {
3371
+ /** @type {import('./utils').StrapiPagination} */
3372
+ const defaultPagination = {
1733
3373
  page: 1,
1734
3374
  pageSize: 25,
1735
3375
  pageCount: 1,
1736
3376
  total: 0,
1737
3377
  };
1738
3378
 
1739
- // Strapi v4 raw response types (with nested attributes)
1740
- interface StrapiV4RawItem<T> {
1741
- id: number;
1742
- attributes: Omit<T, 'id'>;
1743
- }
1744
-
1745
- interface StrapiV4RawListResponse<T> {
1746
- data: StrapiV4RawItem<T>[];
1747
- meta: {
1748
- pagination?: StrapiPagination;
1749
- };
1750
- }
1751
-
1752
- interface StrapiV4RawSingleResponse<T> {
1753
- data: StrapiV4RawItem<T>;
1754
- meta?: Record<string, unknown>;
1755
- }
1756
-
1757
3379
  /**
1758
3380
  * Flatten a Strapi v4 response item (merges id with attributes)
3381
+ * @template T
3382
+ * @param {{ id: number, attributes: Omit<T, 'id'> }} item
3383
+ * @returns {T}
1759
3384
  */
1760
- function flattenItem<T>(item: StrapiV4RawItem<T>): T {
1761
- return { id: item.id, ...item.attributes } as T;
3385
+ function flattenItem(item) {
3386
+ return { id: item.id, ...item.attributes };
1762
3387
  }
1763
3388
 
1764
3389
  /**
1765
3390
  * Recursively flatten nested relations in Strapi v4 response
3391
+ * @template T
3392
+ * @param {T} data
3393
+ * @returns {T}
1766
3394
  */
1767
- function flattenRelations<T>(data: T): T {
3395
+ function flattenRelations(data) {
1768
3396
  if (data === null || data === undefined) return data;
1769
3397
  if (Array.isArray(data)) {
1770
- return data.map(item => flattenRelations(item)) as unknown as T;
3398
+ return /** @type {T} */ (data.map(item => flattenRelations(item)));
1771
3399
  }
1772
3400
  if (typeof data === 'object') {
1773
- const result: Record<string, unknown> = {};
1774
- for (const [key, value] of Object.entries(data as Record<string, unknown>)) {
1775
- // Check if this is a Strapi v4 relation response { data: { id, attributes } }
3401
+ /** @type {Record<string, unknown>} */
3402
+ const result = {};
3403
+ for (const [key, value] of Object.entries(data)) {
1776
3404
  if (value && typeof value === 'object' && 'data' in value) {
1777
- const relationData = (value as { data: unknown }).data;
3405
+ const relationData = /** @type {{ data: unknown }} */ (value).data;
1778
3406
  if (relationData === null) {
1779
3407
  result[key] = null;
1780
3408
  } else if (Array.isArray(relationData)) {
1781
- // To-many relation
1782
- result[key] = relationData.map((item: StrapiV4RawItem<unknown>) =>
3409
+ result[key] = relationData.map((item) =>
1783
3410
  flattenRelations(flattenItem(item))
1784
3411
  );
1785
3412
  } else if (typeof relationData === 'object' && 'id' in relationData && 'attributes' in relationData) {
1786
- // To-one relation
1787
- result[key] = flattenRelations(flattenItem(relationData as StrapiV4RawItem<unknown>));
3413
+ result[key] = flattenRelations(flattenItem(/** @type {{ id: number, attributes: object }} */ (relationData)));
1788
3414
  } else {
1789
3415
  result[key] = flattenRelations(value);
1790
3416
  }
@@ -1792,76 +3418,150 @@ function flattenRelations<T>(data: T): T {
1792
3418
  result[key] = flattenRelations(value);
1793
3419
  }
1794
3420
  }
1795
- return result as T;
3421
+ return /** @type {T} */ (result);
1796
3422
  }
1797
3423
  return data;
1798
3424
  }
1799
3425
 
1800
- // Helper to get typed collection
1801
- export function collection<T>(pluralName: string) {
3426
+ /**
3427
+ * Helper to get typed collection
3428
+ * @template T
3429
+ * @param {string} pluralName
3430
+ */
3431
+ function collection(pluralName) {
1802
3432
  return {
1803
- async find(params?: Record<string, unknown>): Promise<{ data: T[]; meta: { pagination: StrapiPagination } }> {
1804
- const response = await strapi.find(pluralName, params) as unknown as StrapiV4RawListResponse<T>;
1805
- const flattenedData = Array.isArray(response.data)
1806
- ? response.data.map(item => flattenRelations(flattenItem<T>(item)))
3433
+ /**
3434
+ * @param {Record<string, unknown>} [params]
3435
+ * @returns {Promise<{ data: T[], meta: { pagination: import('./utils').StrapiPagination } }>}
3436
+ */
3437
+ async find(params) {
3438
+ const response = await strapi.find(pluralName, params);
3439
+ /** @type {any} */
3440
+ const rawData = response.data;
3441
+ /** @type {T[]} */
3442
+ const data = Array.isArray(rawData)
3443
+ ? rawData.map(item => flattenRelations(flattenItem(item)))
1807
3444
  : [];
1808
- return {
1809
- data: flattenedData,
1810
- meta: {
1811
- pagination: response.meta?.pagination || defaultPagination,
1812
- },
3445
+ /** @type {any} */
3446
+ const rawMeta = response.meta;
3447
+ /** @type {any} */
3448
+ const rawPag = rawMeta?.pagination;
3449
+ /** @type {import('./utils').StrapiPagination} */
3450
+ const pagination = {
3451
+ page: rawPag?.page ?? defaultPagination.page,
3452
+ pageSize: rawPag?.pageSize ?? defaultPagination.pageSize,
3453
+ pageCount: rawPag?.pageCount ?? defaultPagination.pageCount,
3454
+ total: rawPag?.total ?? defaultPagination.total,
1813
3455
  };
3456
+ return { data, meta: { pagination } };
1814
3457
  },
1815
- async findOne(id: number | string, params?: Record<string, unknown>): Promise<{ data: T }> {
1816
- const response = await strapi.findOne(pluralName, String(id), params) as unknown as StrapiV4RawSingleResponse<T>;
1817
- return { data: flattenRelations(flattenItem<T>(response.data)) };
3458
+ /**
3459
+ * @param {number|string} id
3460
+ * @param {Record<string, unknown>} [params]
3461
+ * @returns {Promise<{ data: T }>}
3462
+ */
3463
+ async findOne(id, params) {
3464
+ const response = await strapi.findOne(pluralName, String(id), params);
3465
+ /** @type {any} */
3466
+ const rawData = response.data;
3467
+ /** @type {T} */
3468
+ const data = flattenRelations(flattenItem(rawData));
3469
+ return { data };
1818
3470
  },
1819
- async create(data: { data: Partial<T> }): Promise<{ data: T }> {
1820
- const response = await strapi.create(pluralName, data.data as Record<string, unknown>) as unknown as StrapiV4RawSingleResponse<T>;
1821
- return { data: flattenRelations(flattenItem<T>(response.data)) };
3471
+ /**
3472
+ * @param {{ data: Partial<T> }} data
3473
+ * @returns {Promise<{ data: T }>}
3474
+ */
3475
+ async create(data) {
3476
+ const response = await strapi.create(pluralName, data.data);
3477
+ /** @type {any} */
3478
+ const rawData = response.data;
3479
+ /** @type {T} */
3480
+ const result = flattenRelations(flattenItem(rawData));
3481
+ return { data: result };
1822
3482
  },
1823
- async update(id: number | string, data: { data: Partial<T> }): Promise<{ data: T }> {
1824
- const response = await strapi.update(pluralName, String(id), data.data as Record<string, unknown>) as unknown as StrapiV4RawSingleResponse<T>;
1825
- return { data: flattenRelations(flattenItem<T>(response.data)) };
3483
+ /**
3484
+ * @param {number|string} id
3485
+ * @param {{ data: Partial<T> }} data
3486
+ * @returns {Promise<{ data: T }>}
3487
+ */
3488
+ async update(id, data) {
3489
+ const response = await strapi.update(pluralName, String(id), data.data);
3490
+ /** @type {any} */
3491
+ const rawData = response.data;
3492
+ /** @type {T} */
3493
+ const result = flattenRelations(flattenItem(rawData));
3494
+ return { data: result };
1826
3495
  },
1827
- async delete(id: number | string): Promise<void> {
3496
+ /**
3497
+ * @param {number|string} id
3498
+ * @returns {Promise<void>}
3499
+ */
3500
+ async delete(id) {
1828
3501
  await strapi.delete(pluralName, String(id));
1829
3502
  },
1830
3503
  };
1831
3504
  }
1832
3505
 
1833
- // Helper to get typed single type
1834
- export function single<T>(singularName: string) {
3506
+ /**
3507
+ * Helper to get typed single type
3508
+ * @template T
3509
+ * @param {string} singularName
3510
+ */
3511
+ function single(singularName) {
1835
3512
  return {
1836
- async find(params?: Record<string, unknown>): Promise<{ data: T }> {
1837
- const response = await strapi.find(singularName, params) as unknown as StrapiV4RawSingleResponse<T>;
1838
- return { data: flattenRelations(flattenItem<T>(response.data)) };
3513
+ /**
3514
+ * @param {Record<string, unknown>} [params]
3515
+ * @returns {Promise<{ data: T }>}
3516
+ */
3517
+ async find(params) {
3518
+ const response = await strapi.find(singularName, params);
3519
+ /** @type {any} */
3520
+ const rawData = response.data;
3521
+ /** @type {T} */
3522
+ const data = flattenRelations(flattenItem(rawData));
3523
+ return { data };
1839
3524
  },
1840
- async update(data: { data: Partial<T> }): Promise<{ data: T }> {
1841
- const response = await strapi.update(singularName, 1 as unknown as string, data.data as Record<string, unknown>) as unknown as StrapiV4RawSingleResponse<T>;
1842
- return { data: flattenRelations(flattenItem<T>(response.data)) };
3525
+ /**
3526
+ * @param {{ data: Partial<T> }} data
3527
+ * @returns {Promise<{ data: T }>}
3528
+ */
3529
+ async update(data) {
3530
+ const response = await strapi.update(singularName, String(1), data.data);
3531
+ /** @type {any} */
3532
+ const rawData = response.data;
3533
+ /** @type {T} */
3534
+ const result = flattenRelations(flattenItem(rawData));
3535
+ return { data: result };
1843
3536
  },
1844
- async delete(): Promise<void> {
1845
- await strapi.delete(singularName, 1 as unknown as string);
3537
+ /**
3538
+ * @returns {Promise<void>}
3539
+ */
3540
+ async delete() {
3541
+ await strapi.delete(singularName, String(1));
1846
3542
  },
1847
3543
  };
1848
3544
  }
3545
+
3546
+ module.exports = { strapi, collection, single };
1849
3547
  `;
1850
3548
  }
1851
- return `/**
3549
+ return `// @ts-check
3550
+ /**
1852
3551
  * Strapi Client (v5)
1853
3552
  * Generated by strapi2front
1854
3553
  */
1855
3554
 
1856
- import Strapi from 'strapi-sdk-js';
1857
- import type { StrapiPagination } from './utils';
3555
+ const Strapi = require('strapi-sdk-js').default;
1858
3556
 
1859
3557
  // Initialize the Strapi client
1860
- const strapiUrl = import.meta.env.STRAPI_URL || process.env.STRAPI_URL || 'http://localhost:1337';
1861
- const strapiToken = import.meta.env.STRAPI_TOKEN || process.env.STRAPI_TOKEN;
3558
+ const strapiUrl = process.env.STRAPI_URL || 'http://localhost:1337';
3559
+ const strapiToken = process.env.STRAPI_TOKEN;
3560
+ const strapiApiPrefix = process.env.STRAPI_API_PREFIX || '${normalizedPrefix}';
1862
3561
 
1863
- export const strapi = new Strapi({
3562
+ const strapi = new Strapi({
1864
3563
  url: strapiUrl,
3564
+ prefix: strapiApiPrefix,
1865
3565
  axiosOptions: {
1866
3566
  headers: strapiToken ? {
1867
3567
  Authorization: \`Bearer \${strapiToken}\`,
@@ -1869,267 +3569,304 @@ export const strapi = new Strapi({
1869
3569
  },
1870
3570
  });
1871
3571
 
1872
- // Default pagination for fallback
1873
- const defaultPagination: StrapiPagination = {
3572
+ /** @type {import('./utils').StrapiPagination} */
3573
+ const defaultPagination = {
1874
3574
  page: 1,
1875
3575
  pageSize: 25,
1876
3576
  pageCount: 1,
1877
3577
  total: 0,
1878
3578
  };
1879
3579
 
1880
- // Response types from strapi-sdk-js
1881
- interface StrapiListResponse<T> {
1882
- data: T[];
1883
- meta: {
1884
- pagination?: StrapiPagination;
1885
- };
1886
- }
1887
-
1888
- interface StrapiSingleResponse<T> {
1889
- data: T;
1890
- meta?: Record<string, unknown>;
1891
- }
1892
-
1893
- // Helper to get typed collection
1894
- export function collection<T>(pluralName: string) {
3580
+ /**
3581
+ * Helper to get typed collection
3582
+ * @template T
3583
+ * @param {string} pluralName
3584
+ */
3585
+ function collection(pluralName) {
1895
3586
  return {
1896
- async find(params?: Record<string, unknown>): Promise<{ data: T[]; meta: { pagination: StrapiPagination } }> {
1897
- const response = await strapi.find(pluralName, params) as unknown as StrapiListResponse<T>;
1898
- return {
1899
- data: Array.isArray(response.data) ? response.data : [],
1900
- meta: {
1901
- pagination: response.meta?.pagination || defaultPagination,
1902
- },
3587
+ /**
3588
+ * @param {Record<string, unknown>} [params]
3589
+ * @returns {Promise<{ data: T[], meta: { pagination: import('./utils').StrapiPagination } }>}
3590
+ */
3591
+ async find(params) {
3592
+ const response = await strapi.find(pluralName, params);
3593
+ /** @type {any} */
3594
+ const rawMeta = response.meta;
3595
+ /** @type {any} */
3596
+ const rawPag = rawMeta?.pagination;
3597
+ /** @type {import('./utils').StrapiPagination} */
3598
+ const pagination = {
3599
+ page: rawPag?.page ?? defaultPagination.page,
3600
+ pageSize: rawPag?.pageSize ?? defaultPagination.pageSize,
3601
+ pageCount: rawPag?.pageCount ?? defaultPagination.pageCount,
3602
+ total: rawPag?.total ?? defaultPagination.total,
1903
3603
  };
3604
+ /** @type {any} */
3605
+ const rawData = response.data;
3606
+ /** @type {T[]} */
3607
+ const data = Array.isArray(rawData) ? rawData : [];
3608
+ return { data, meta: { pagination } };
1904
3609
  },
1905
- async findOne(documentId: string, params?: Record<string, unknown>): Promise<{ data: T }> {
1906
- const response = await strapi.findOne(pluralName, documentId, params) as unknown as StrapiSingleResponse<T>;
1907
- return { data: response.data };
3610
+ /**
3611
+ * @param {string} documentId
3612
+ * @param {Record<string, unknown>} [params]
3613
+ * @returns {Promise<{ data: T }>}
3614
+ */
3615
+ async findOne(documentId, params) {
3616
+ const response = await strapi.findOne(pluralName, documentId, params);
3617
+ /** @type {any} */
3618
+ const rawData = response.data;
3619
+ /** @type {T} */
3620
+ const data = rawData;
3621
+ return { data };
1908
3622
  },
1909
- async create(data: { data: Partial<T> }): Promise<{ data: T }> {
1910
- const response = await strapi.create(pluralName, data.data as Record<string, unknown>) as unknown as StrapiSingleResponse<T>;
1911
- return { data: response.data };
3623
+ /**
3624
+ * @param {{ data: Partial<T> }} data
3625
+ * @returns {Promise<{ data: T }>}
3626
+ */
3627
+ async create(data) {
3628
+ const response = await strapi.create(pluralName, data.data);
3629
+ /** @type {any} */
3630
+ const rawData = response.data;
3631
+ /** @type {T} */
3632
+ const result = rawData;
3633
+ return { data: result };
1912
3634
  },
1913
- async update(documentId: string, data: { data: Partial<T> }): Promise<{ data: T }> {
1914
- const response = await strapi.update(pluralName, documentId, data.data as Record<string, unknown>) as unknown as StrapiSingleResponse<T>;
1915
- return { data: response.data };
3635
+ /**
3636
+ * @param {string} documentId
3637
+ * @param {{ data: Partial<T> }} data
3638
+ * @returns {Promise<{ data: T }>}
3639
+ */
3640
+ async update(documentId, data) {
3641
+ const response = await strapi.update(pluralName, documentId, data.data);
3642
+ /** @type {any} */
3643
+ const rawData = response.data;
3644
+ /** @type {T} */
3645
+ const result = rawData;
3646
+ return { data: result };
1916
3647
  },
1917
- async delete(documentId: string): Promise<void> {
3648
+ /**
3649
+ * @param {string} documentId
3650
+ * @returns {Promise<void>}
3651
+ */
3652
+ async delete(documentId) {
1918
3653
  await strapi.delete(pluralName, documentId);
1919
3654
  },
1920
3655
  };
1921
3656
  }
1922
3657
 
1923
- // Helper to get typed single type
1924
- export function single<T>(singularName: string) {
3658
+ /**
3659
+ * Helper to get typed single type
3660
+ * @template T
3661
+ * @param {string} singularName
3662
+ */
3663
+ function single(singularName) {
1925
3664
  return {
1926
- async find(params?: Record<string, unknown>): Promise<{ data: T }> {
1927
- const response = await strapi.find(singularName, params) as unknown as StrapiSingleResponse<T>;
1928
- return { data: response.data };
3665
+ /**
3666
+ * @param {Record<string, unknown>} [params]
3667
+ * @returns {Promise<{ data: T }>}
3668
+ */
3669
+ async find(params) {
3670
+ const response = await strapi.find(singularName, params);
3671
+ /** @type {any} */
3672
+ const rawData = response.data;
3673
+ /** @type {T} */
3674
+ const data = rawData;
3675
+ return { data };
1929
3676
  },
1930
- async update(data: { data: Partial<T> }): Promise<{ data: T }> {
1931
- const response = await strapi.update(singularName, 1 as unknown as string, data.data as Record<string, unknown>) as unknown as StrapiSingleResponse<T>;
1932
- return { data: response.data };
3677
+ /**
3678
+ * @param {{ data: Partial<T> }} data
3679
+ * @returns {Promise<{ data: T }>}
3680
+ */
3681
+ async update(data) {
3682
+ const response = await strapi.update(singularName, String(1), data.data);
3683
+ /** @type {any} */
3684
+ const rawData = response.data;
3685
+ /** @type {T} */
3686
+ const result = rawData;
3687
+ return { data: result };
1933
3688
  },
1934
- async delete(): Promise<void> {
1935
- await strapi.delete(singularName, 1 as unknown as string);
3689
+ /**
3690
+ * @returns {Promise<void>}
3691
+ */
3692
+ async delete() {
3693
+ await strapi.delete(singularName, String(1));
1936
3694
  },
1937
3695
  };
1938
3696
  }
3697
+
3698
+ module.exports = { strapi, collection, single };
1939
3699
  `;
1940
3700
  }
1941
- function generateLocalesFile2(locales) {
3701
+ function generateLocalesFileJSDoc(locales) {
1942
3702
  if (locales.length === 0) {
1943
- return `/**
3703
+ return `// @ts-check
3704
+ /**
1944
3705
  * Strapi locales
1945
3706
  * Generated by strapi2front
1946
3707
  * Note: i18n is not enabled in Strapi
1947
3708
  */
1948
3709
 
1949
- export const locales = [] as const;
1950
- export type Locale = string;
1951
- export const defaultLocale: Locale = 'en';
1952
- export const localeNames: Record<string, string> = {};
3710
+ /** @type {readonly string[]} */
3711
+ const locales = [];
1953
3712
 
1954
- export function isValidLocale(_code: string): _code is Locale {
3713
+ /** @typedef {string} Locale */
3714
+
3715
+ /** @type {string} */
3716
+ const defaultLocale = 'en';
3717
+
3718
+ /** @type {Record<string, string>} */
3719
+ const localeNames = {};
3720
+
3721
+ /**
3722
+ * @param {string} _code
3723
+ * @returns {boolean}
3724
+ */
3725
+ function isValidLocale(_code) {
1955
3726
  return true;
1956
3727
  }
1957
3728
 
1958
- export function getLocaleName(code: string): string {
3729
+ /**
3730
+ * @param {string} code
3731
+ * @returns {string}
3732
+ */
3733
+ function getLocaleName(code) {
1959
3734
  return code;
1960
3735
  }
3736
+
3737
+ module.exports = { locales, defaultLocale, localeNames, isValidLocale, getLocaleName };
1961
3738
  `;
1962
3739
  }
1963
3740
  const localeCodes = locales.map((l) => l.code);
1964
3741
  const defaultLocale = locales.find((l) => l.isDefault)?.code || locales[0]?.code || "en";
1965
- return `/**
3742
+ return `// @ts-check
3743
+ /**
1966
3744
  * Strapi locales
1967
3745
  * Generated by strapi2front
1968
3746
  */
1969
3747
 
1970
- export const locales = [${localeCodes.map((c) => `'${c}'`).join(", ")}] as const;
3748
+ /** @type {readonly [${localeCodes.map((c) => `'${c}'`).join(", ")}]} */
3749
+ const locales = [${localeCodes.map((c) => `'${c}'`).join(", ")}];
1971
3750
 
1972
- export type Locale = typeof locales[number];
3751
+ /** @typedef {${localeCodes.map((c) => `'${c}'`).join(" | ")}} Locale */
1973
3752
 
1974
- export const defaultLocale: Locale = '${defaultLocale}';
3753
+ /** @type {Locale} */
3754
+ const defaultLocale = '${defaultLocale}';
1975
3755
 
1976
- export const localeNames: Record<Locale, string> = {
3756
+ /** @type {Record<Locale, string>} */
3757
+ const localeNames = {
1977
3758
  ${locales.map((l) => ` '${l.code}': '${l.name}'`).join(",\n")}
1978
3759
  };
1979
3760
 
1980
- export function isValidLocale(code: string): code is Locale {
1981
- return locales.includes(code as Locale);
3761
+ /**
3762
+ * @param {string} code
3763
+ * @returns {boolean}
3764
+ */
3765
+ function isValidLocale(code) {
3766
+ /** @type {readonly string[]} */
3767
+ const localeList = locales;
3768
+ return localeList.includes(code);
1982
3769
  }
1983
3770
 
1984
- export function getLocaleName(code: Locale): string {
3771
+ /**
3772
+ * @param {Locale} code
3773
+ * @returns {string}
3774
+ */
3775
+ function getLocaleName(code) {
1985
3776
  return localeNames[code] || code;
1986
3777
  }
3778
+
3779
+ module.exports = { locales, defaultLocale, localeNames, isValidLocale, getLocaleName };
1987
3780
  `;
1988
3781
  }
1989
- function generateCollectionTypes(collection, schema) {
3782
+ function generateCollectionTypesJSDoc(collection, schema) {
1990
3783
  const typeName = toPascalCase(collection.singularName);
1991
- const attributes = generateAttributes2(collection.attributes);
1992
- const imports = generateTypeImports(collection.attributes, schema, "collection");
1993
- return `/**
3784
+ const attributes = generateJSDocAttributes(collection.attributes, schema, "collection");
3785
+ return `// @ts-check
3786
+ /**
1994
3787
  * ${collection.displayName}
1995
3788
  * ${collection.description || ""}
1996
3789
  * Generated by strapi2front
1997
3790
  */
1998
3791
 
1999
- ${imports}
3792
+ /**
3793
+ * @typedef {import('../../shared/utils').StrapiBaseEntity & ${typeName}Attributes} ${typeName}
3794
+ */
2000
3795
 
2001
- export interface ${typeName} extends StrapiBaseEntity {
3796
+ /**
3797
+ * @typedef {Object} ${typeName}Attributes
2002
3798
  ${attributes}
2003
- }
3799
+ */
2004
3800
 
2005
- export interface ${typeName}Filters {
2006
- id?: number | { $eq?: number; $ne?: number; $in?: number[]; $notIn?: number[] };
2007
- documentId?: string | { $eq?: string; $ne?: string };
2008
- createdAt?: string | { $eq?: string; $gt?: string; $gte?: string; $lt?: string; $lte?: string };
2009
- updatedAt?: string | { $eq?: string; $gt?: string; $gte?: string; $lt?: string; $lte?: string };
2010
- publishedAt?: string | null | { $eq?: string; $ne?: string; $null?: boolean };
2011
- $and?: ${typeName}Filters[];
2012
- $or?: ${typeName}Filters[];
2013
- $not?: ${typeName}Filters;
2014
- }
3801
+ /**
3802
+ * @typedef {Object} ${typeName}Filters
3803
+ * @property {number|{$eq?: number, $ne?: number, $in?: number[], $notIn?: number[]}} [id]
3804
+ * @property {string|{$eq?: string, $ne?: string}} [documentId]
3805
+ * @property {string|{$eq?: string, $gt?: string, $gte?: string, $lt?: string, $lte?: string}} [createdAt]
3806
+ * @property {string|{$eq?: string, $gt?: string, $gte?: string, $lt?: string, $lte?: string}} [updatedAt]
3807
+ * @property {string|null|{$eq?: string, $ne?: string, $null?: boolean}} [publishedAt]
3808
+ * @property {${typeName}Filters[]} [$and]
3809
+ * @property {${typeName}Filters[]} [$or]
3810
+ * @property {${typeName}Filters} [$not]
3811
+ */
3812
+
3813
+ module.exports = {};
2015
3814
  `;
2016
3815
  }
2017
- function generateSingleTypes(single, schema) {
3816
+ function generateSingleTypesJSDoc(single, schema) {
2018
3817
  const typeName = toPascalCase(single.singularName);
2019
- const attributes = generateAttributes2(single.attributes);
2020
- const imports = generateTypeImports(single.attributes, schema, "single");
2021
- return `/**
3818
+ const attributes = generateJSDocAttributes(single.attributes, schema, "single");
3819
+ return `// @ts-check
3820
+ /**
2022
3821
  * ${single.displayName}
2023
3822
  * ${single.description || ""}
2024
3823
  * Generated by strapi2front
2025
3824
  */
2026
3825
 
2027
- ${imports}
3826
+ /**
3827
+ * @typedef {import('../../shared/utils').StrapiBaseEntity & ${typeName}Attributes} ${typeName}
3828
+ */
2028
3829
 
2029
- export interface ${typeName} extends StrapiBaseEntity {
3830
+ /**
3831
+ * @typedef {Object} ${typeName}Attributes
2030
3832
  ${attributes}
2031
- }
3833
+ */
3834
+
3835
+ module.exports = {};
2032
3836
  `;
2033
3837
  }
2034
- function generateComponentTypes(component, schema) {
3838
+ function generateComponentTypesJSDoc(component, schema) {
2035
3839
  const typeName = toPascalCase(component.name);
2036
- const attributes = generateAttributes2(component.attributes);
2037
- const imports = generateTypeImports(component.attributes, schema, "component");
2038
- return `/**
3840
+ const attributes = generateJSDocAttributes(component.attributes, schema, "component");
3841
+ return `// @ts-check
3842
+ /**
2039
3843
  * ${component.displayName} component
2040
3844
  * Category: ${component.category}
2041
3845
  * ${component.description || ""}
2042
3846
  * Generated by strapi2front
2043
3847
  */
2044
3848
 
2045
- ${imports}
2046
-
2047
- export interface ${typeName} {
2048
- id: number;
3849
+ /**
3850
+ * @typedef {Object} ${typeName}
3851
+ * @property {number} id
2049
3852
  ${attributes}
2050
- }
3853
+ */
3854
+
3855
+ module.exports = {};
2051
3856
  `;
2052
3857
  }
2053
- function generateTypeImports(attributes, schema, context) {
2054
- const utilsImports = [];
2055
- const relationImports = /* @__PURE__ */ new Map();
2056
- const componentImports = /* @__PURE__ */ new Map();
2057
- const attributesStr = JSON.stringify(attributes);
2058
- if (context !== "component") {
2059
- utilsImports.push("StrapiBaseEntity");
2060
- }
2061
- if (attributesStr.includes('"type":"media"')) {
2062
- utilsImports.push("StrapiMedia");
2063
- }
2064
- if (attributesStr.includes('"type":"blocks"')) {
2065
- utilsImports.push("BlocksContent");
2066
- }
2067
- const relativePrefix = context === "component" ? ".." : "../..";
2068
- for (const attr of Object.values(attributes)) {
2069
- if (attr.type === "relation" && "target" in attr && attr.target) {
2070
- const targetName = attr.target.split(".").pop() || "";
2071
- if (targetName) {
2072
- const typeName = toPascalCase(targetName);
2073
- const fileName = toKebabCase(targetName);
2074
- const isCollection = schema.collections.some((c) => c.singularName === targetName);
2075
- const isSingle = schema.singles.some((s) => s.singularName === targetName);
2076
- if (isCollection) {
2077
- relationImports.set(typeName, `${relativePrefix}/collections/${fileName}/types`);
2078
- } else if (isSingle) {
2079
- relationImports.set(typeName, `${relativePrefix}/singles/${fileName}/types`);
2080
- }
2081
- }
2082
- }
2083
- if (attr.type === "component" && "component" in attr && attr.component) {
2084
- const componentName = attr.component.split(".").pop() || "";
2085
- if (componentName) {
2086
- const typeName = toPascalCase(componentName);
2087
- const fileName = toKebabCase(componentName);
2088
- if (context === "component") {
2089
- componentImports.set(typeName, `./${fileName}`);
2090
- } else {
2091
- componentImports.set(typeName, `../../components/${fileName}`);
2092
- }
2093
- }
2094
- }
2095
- if (attr.type === "dynamiczone" && "components" in attr && attr.components) {
2096
- for (const comp of attr.components) {
2097
- const componentName = comp.split(".").pop() || "";
2098
- if (componentName) {
2099
- const typeName = toPascalCase(componentName);
2100
- const fileName = toKebabCase(componentName);
2101
- if (context === "component") {
2102
- componentImports.set(typeName, `./${fileName}`);
2103
- } else {
2104
- componentImports.set(typeName, `../../components/${fileName}`);
2105
- }
2106
- }
2107
- }
2108
- }
2109
- }
2110
- const lines = [];
2111
- if (utilsImports.length > 0) {
2112
- const utilsPath = context === "component" ? "../shared/utils" : "../../shared/utils";
2113
- lines.push(`import type { ${utilsImports.join(", ")} } from '${utilsPath}';`);
2114
- }
2115
- for (const [typeName, importPath] of relationImports) {
2116
- lines.push(`import type { ${typeName} } from '${importPath}';`);
2117
- }
2118
- for (const [typeName, importPath] of componentImports) {
2119
- lines.push(`import type { ${typeName} } from '${importPath}';`);
2120
- }
2121
- return lines.join("\n");
2122
- }
2123
- function generateAttributes2(attributes) {
3858
+ function generateJSDocAttributes(attributes, schema, context) {
2124
3859
  const lines = [];
3860
+ const relativePrefix = context === "component" ? ".." : "../..";
2125
3861
  for (const [name, attr] of Object.entries(attributes)) {
2126
- const tsType = attributeToTsType2(attr);
2127
- const optional = attr.required ? "" : "?";
2128
- lines.push(` ${name}${optional}: ${tsType};`);
3862
+ const jsType = attributeToJSDocType(attr, schema, relativePrefix);
3863
+ const optional = attr.required ? "" : "[";
3864
+ const optionalEnd = attr.required ? "" : "]";
3865
+ lines.push(` * @property {${jsType}} ${optional}${name}${optionalEnd}`);
2129
3866
  }
2130
3867
  return lines.join("\n");
2131
3868
  }
2132
- function attributeToTsType2(attr) {
3869
+ function attributeToJSDocType(attr, schema, relativePrefix) {
2133
3870
  switch (attr.type) {
2134
3871
  case "string":
2135
3872
  case "text":
@@ -2139,7 +3876,7 @@ function attributeToTsType2(attr) {
2139
3876
  case "uid":
2140
3877
  return "string";
2141
3878
  case "blocks":
2142
- return "BlocksContent";
3879
+ return "Array<Object>";
2143
3880
  case "integer":
2144
3881
  case "biginteger":
2145
3882
  case "float":
@@ -2153,130 +3890,116 @@ function attributeToTsType2(attr) {
2153
3890
  case "timestamp":
2154
3891
  return "string";
2155
3892
  case "json":
2156
- return "unknown";
3893
+ return "Object";
2157
3894
  case "enumeration":
2158
3895
  if ("enum" in attr && attr.enum) {
2159
- return attr.enum.map((v) => `'${v}'`).join(" | ");
3896
+ return attr.enum.map((v) => `'${v}'`).join("|");
2160
3897
  }
2161
3898
  return "string";
2162
3899
  case "media":
2163
3900
  if ("multiple" in attr && attr.multiple) {
2164
- return "StrapiMedia[]";
3901
+ return `import("${relativePrefix}/shared/utils").StrapiMedia[]`;
2165
3902
  }
2166
- return "StrapiMedia | null";
3903
+ return `import("${relativePrefix}/shared/utils").StrapiMedia|null`;
2167
3904
  case "relation":
2168
3905
  if ("target" in attr && attr.target) {
2169
- const targetName = toPascalCase(attr.target.split(".").pop() || "unknown");
3906
+ const targetName = attr.target.split(".").pop() || "unknown";
3907
+ const typeName = toPascalCase(targetName);
3908
+ const fileName = toKebabCase(targetName);
2170
3909
  const isMany = attr.relation === "oneToMany" || attr.relation === "manyToMany";
2171
- return isMany ? `${targetName}[]` : `${targetName} | null`;
3910
+ const isCollection = schema.collections.some((c) => c.singularName === targetName);
3911
+ const isSingle = schema.singles.some((s) => s.singularName === targetName);
3912
+ let importPath;
3913
+ if (isCollection) {
3914
+ importPath = `${relativePrefix}/collections/${fileName}/types`;
3915
+ } else if (isSingle) {
3916
+ importPath = `${relativePrefix}/singles/${fileName}/types`;
3917
+ } else {
3918
+ importPath = `${relativePrefix}/collections/${fileName}/types`;
3919
+ }
3920
+ const importType = `import("${importPath}").${typeName}`;
3921
+ return isMany ? `${importType}[]` : `${importType}|null`;
2172
3922
  }
2173
- return "unknown";
3923
+ return "Object";
2174
3924
  case "component":
2175
3925
  if ("component" in attr && attr.component) {
2176
- const componentName = toPascalCase(attr.component.split(".").pop() || "unknown");
3926
+ const componentName = attr.component.split(".").pop() || "unknown";
3927
+ const typeName = toPascalCase(componentName);
3928
+ const fileName = toKebabCase(componentName);
3929
+ const importPath = `${relativePrefix}/components/${fileName}`;
3930
+ const importType = `import("${importPath}").${typeName}`;
2177
3931
  if ("repeatable" in attr && attr.repeatable) {
2178
- return `${componentName}[]`;
3932
+ return `${importType}[]`;
2179
3933
  }
2180
- return `${componentName} | null`;
3934
+ return `${importType}|null`;
2181
3935
  }
2182
- return "unknown";
3936
+ return "Object";
2183
3937
  case "dynamiczone":
2184
3938
  if ("components" in attr && attr.components) {
2185
- const types = attr.components.map((c) => toPascalCase(c.split(".").pop() || "unknown"));
2186
- return `(${types.join(" | ")})[]`;
3939
+ const types = attr.components.map((comp) => {
3940
+ const componentName = comp.split(".").pop() || "unknown";
3941
+ const typeName = toPascalCase(componentName);
3942
+ const fileName = toKebabCase(componentName);
3943
+ const importPath = `${relativePrefix}/components/${fileName}`;
3944
+ return `import("${importPath}").${typeName}`;
3945
+ });
3946
+ return `(${types.join("|")})[]`;
2187
3947
  }
2188
- return "unknown[]";
3948
+ return "Object[]";
2189
3949
  default:
2190
- return "unknown";
3950
+ return "Object";
2191
3951
  }
2192
3952
  }
2193
- function generateCollectionService2(collection, strapiVersion) {
3953
+ function generateCollectionServiceJSDoc(collection, strapiVersion) {
2194
3954
  const typeName = toPascalCase(collection.singularName);
2195
3955
  const serviceName = toCamelCase(collection.singularName) + "Service";
2196
3956
  const endpoint = collection.pluralName;
2197
3957
  const hasSlug = "slug" in collection.attributes;
2198
3958
  const { localized, draftAndPublish } = collection;
2199
3959
  const isV4 = strapiVersion === "v4";
2200
- const idParam = isV4 ? "id: number" : "documentId: string";
2201
- const idName = isV4 ? "id" : "documentId";
2202
- const omitFields = isV4 ? "'id' | 'createdAt' | 'updatedAt' | 'publishedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt' | 'publishedAt'";
2203
- const omitFieldsUpdate = isV4 ? "'id' | 'createdAt' | 'updatedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt'";
2204
- const imports = [
2205
- `import { collection } from '../../shared/client';`,
2206
- `import type { ${typeName}, ${typeName}Filters } from './types';`,
2207
- `import type { StrapiPagination } from '../../shared/utils';`
2208
- ];
2209
- if (localized) {
2210
- imports.push(`import type { Locale } from '../../shared/locales';`);
2211
- }
2212
- const paginationFields = `
2213
- /** Page number (1-indexed) - use with pageSize */
2214
- page?: number;
2215
- /** Number of items per page (default: 25) - use with page */
2216
- pageSize?: number;
2217
- /** Offset to start from (0-indexed) - use with limit */
2218
- start?: number;
2219
- /** Maximum number of items to return - use with start */
2220
- limit?: number;`;
2221
- let findManyOptionsFields = ` filters?: ${typeName}Filters;
2222
- pagination?: {${paginationFields}
2223
- };
2224
- sort?: string | string[];
2225
- populate?: string | string[] | Record<string, unknown>;`;
2226
- let findOneOptionsFields = ` populate?: string | string[] | Record<string, unknown>;`;
2227
- if (localized) {
2228
- findManyOptionsFields += `
2229
- locale?: Locale;`;
2230
- findOneOptionsFields += `
2231
- locale?: Locale;`;
2232
- }
2233
- if (draftAndPublish) {
2234
- findManyOptionsFields += `
2235
- status?: 'draft' | 'published';`;
2236
- findOneOptionsFields += `
2237
- status?: 'draft' | 'published';`;
2238
- }
2239
- let findParams = ` filters: options.filters,
2240
- pagination: options.pagination,
2241
- sort: options.sort,
2242
- populate: options.populate,`;
2243
- let findOneParams = ` populate: options.populate,`;
2244
- if (localized) {
2245
- findParams += `
2246
- locale: options.locale,`;
2247
- findOneParams += `
2248
- locale: options.locale,`;
2249
- }
2250
- if (draftAndPublish) {
2251
- findParams += `
2252
- status: options.status,`;
2253
- findOneParams += `
2254
- status: options.status,`;
2255
- }
2256
- return `/**
3960
+ const idParam = isV4 ? "id" : "documentId";
3961
+ const idType = isV4 ? "number" : "string";
3962
+ return `// @ts-check
3963
+ /**
2257
3964
  * ${collection.displayName} Service
2258
3965
  * ${collection.description || ""}
2259
3966
  * Generated by strapi2front
2260
3967
  * Strapi version: ${strapiVersion}
2261
3968
  */
2262
3969
 
2263
- ${imports.join("\n")}
3970
+ const { collection } = require('../../shared/client');
2264
3971
 
2265
- export interface FindManyOptions {
2266
- ${findManyOptionsFields}
2267
- }
3972
+ /**
3973
+ * @typedef {Object} FindManyOptions
3974
+ * @property {import('./types').${typeName}Filters} [filters]
3975
+ * @property {Object} [pagination]
3976
+ * @property {number} [pagination.page]
3977
+ * @property {number} [pagination.pageSize]
3978
+ * @property {number} [pagination.start]
3979
+ * @property {number} [pagination.limit]
3980
+ * @property {string|string[]} [sort]
3981
+ * @property {string|string[]|Record<string, unknown>} [populate]${localized ? "\n * @property {string} [locale]" : ""}${draftAndPublish ? "\n * @property {'draft'|'published'} [status]" : ""}
3982
+ */
2268
3983
 
2269
- export interface FindOneOptions {
2270
- ${findOneOptionsFields}
2271
- }
3984
+ /**
3985
+ * @typedef {Object} FindOneOptions
3986
+ * @property {string|string[]|Record<string, unknown>} [populate]${localized ? "\n * @property {string} [locale]" : ""}${draftAndPublish ? "\n * @property {'draft'|'published'} [status]" : ""}
3987
+ */
2272
3988
 
2273
- // Create typed collection helper
2274
- const ${toCamelCase(collection.singularName)}Collection = collection<${typeName}>('${endpoint}');
3989
+ /** @type {ReturnType<typeof collection<import('./types').${typeName}>>} */
3990
+ const ${toCamelCase(collection.singularName)}Collection = collection('${endpoint}');
2275
3991
 
2276
- export const ${serviceName} = {
2277
- async findMany(options: FindManyOptions = {}): Promise<{ data: ${typeName}[]; pagination: StrapiPagination }> {
3992
+ const ${serviceName} = {
3993
+ /**
3994
+ * @param {FindManyOptions} [options]
3995
+ * @returns {Promise<{ data: import('./types').${typeName}[], pagination: import('../../shared/utils').StrapiPagination }>}
3996
+ */
3997
+ async findMany(options = {}) {
2278
3998
  const response = await ${toCamelCase(collection.singularName)}Collection.find({
2279
- ${findParams}
3999
+ filters: options.filters,
4000
+ pagination: options.pagination,
4001
+ sort: options.sort,
4002
+ populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
2280
4003
  });
2281
4004
 
2282
4005
  return {
@@ -2285,8 +4008,13 @@ ${findParams}
2285
4008
  };
2286
4009
  },
2287
4010
 
2288
- async findAll(options: Omit<FindManyOptions, 'pagination'> = {}): Promise<${typeName}[]> {
2289
- const allItems: ${typeName}[] = [];
4011
+ /**
4012
+ * @param {Omit<FindManyOptions, 'pagination'>} [options]
4013
+ * @returns {Promise<import('./types').${typeName}[]>}
4014
+ */
4015
+ async findAll(options = {}) {
4016
+ /** @type {import('./types').${typeName}[]} */
4017
+ const allItems = [];
2290
4018
  let page = 1;
2291
4019
  let hasMore = true;
2292
4020
 
@@ -2304,10 +4032,15 @@ ${findParams}
2304
4032
  return allItems;
2305
4033
  },
2306
4034
 
2307
- async findOne(${idParam}, options: FindOneOptions = {}): Promise<${typeName} | null> {
4035
+ /**
4036
+ * @param {${idType}} ${idParam}
4037
+ * @param {FindOneOptions} [options]
4038
+ * @returns {Promise<import('./types').${typeName}|null>}
4039
+ */
4040
+ async findOne(${idParam}, options = {}) {
2308
4041
  try {
2309
- const response = await ${toCamelCase(collection.singularName)}Collection.findOne(${idName}, {
2310
- ${findOneParams}
4042
+ const response = await ${toCamelCase(collection.singularName)}Collection.findOne(${idParam}, {
4043
+ populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
2311
4044
  });
2312
4045
 
2313
4046
  return response.data;
@@ -2319,9 +4052,14 @@ ${findOneParams}
2319
4052
  }
2320
4053
  },
2321
4054
  ${hasSlug ? `
2322
- async findBySlug(slug: string, options: FindOneOptions = {}): Promise<${typeName} | null> {
4055
+ /**
4056
+ * @param {string} slug
4057
+ * @param {FindOneOptions} [options]
4058
+ * @returns {Promise<import('./types').${typeName}|null>}
4059
+ */
4060
+ async findBySlug(slug, options = {}) {
2323
4061
  const { data } = await this.findMany({
2324
- filters: { slug: { $eq: slug } } as ${typeName}Filters,
4062
+ filters: /** @type {import('./types').${typeName}Filters} */ ({ slug: { $eq: slug } }),
2325
4063
  pagination: { pageSize: 1 },
2326
4064
  populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
2327
4065
  });
@@ -2329,21 +4067,38 @@ ${hasSlug ? `
2329
4067
  return data[0] || null;
2330
4068
  },
2331
4069
  ` : ""}
2332
- async create(data: Partial<Omit<${typeName}, ${omitFields}>>): Promise<${typeName}> {
4070
+ /**
4071
+ * @param {Partial<import('./types').${typeName}>} data
4072
+ * @returns {Promise<import('./types').${typeName}>}
4073
+ */
4074
+ async create(data) {
2333
4075
  const response = await ${toCamelCase(collection.singularName)}Collection.create({ data });
2334
4076
  return response.data;
2335
4077
  },
2336
4078
 
2337
- async update(${idParam}, data: Partial<Omit<${typeName}, ${omitFieldsUpdate}>>): Promise<${typeName}> {
2338
- const response = await ${toCamelCase(collection.singularName)}Collection.update(${idName}, { data });
4079
+ /**
4080
+ * @param {${idType}} ${idParam}
4081
+ * @param {Partial<import('./types').${typeName}>} data
4082
+ * @returns {Promise<import('./types').${typeName}>}
4083
+ */
4084
+ async update(${idParam}, data) {
4085
+ const response = await ${toCamelCase(collection.singularName)}Collection.update(${idParam}, { data });
2339
4086
  return response.data;
2340
4087
  },
2341
4088
 
2342
- async delete(${idParam}): Promise<void> {
2343
- await ${toCamelCase(collection.singularName)}Collection.delete(${idName});
4089
+ /**
4090
+ * @param {${idType}} ${idParam}
4091
+ * @returns {Promise<void>}
4092
+ */
4093
+ async delete(${idParam}) {
4094
+ await ${toCamelCase(collection.singularName)}Collection.delete(${idParam});
2344
4095
  },
2345
4096
 
2346
- async count(filters?: ${typeName}Filters): Promise<number> {
4097
+ /**
4098
+ * @param {import('./types').${typeName}Filters} [filters]
4099
+ * @returns {Promise<number>}
4100
+ */
4101
+ async count(filters) {
2347
4102
  const { pagination } = await this.findMany({
2348
4103
  filters,
2349
4104
  pagination: { pageSize: 1 },
@@ -2352,61 +4107,42 @@ ${hasSlug ? `
2352
4107
  return pagination.total;
2353
4108
  },
2354
4109
  };
4110
+
4111
+ module.exports = { ${serviceName} };
2355
4112
  `;
2356
4113
  }
2357
- function generateSingleService2(single, strapiVersion) {
4114
+ function generateSingleServiceJSDoc(single, strapiVersion) {
2358
4115
  const typeName = toPascalCase(single.singularName);
2359
4116
  const serviceName = toCamelCase(single.singularName) + "Service";
2360
4117
  const endpoint = single.singularName;
2361
4118
  const { localized, draftAndPublish } = single;
2362
- const isV4 = strapiVersion === "v4";
2363
- const omitFields = isV4 ? "'id' | 'createdAt' | 'updatedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt'";
2364
- const imports = [
2365
- `import { single } from '../../shared/client';`,
2366
- `import type { ${typeName} } from './types';`
2367
- ];
2368
- if (localized) {
2369
- imports.push(`import type { Locale } from '../../shared/locales';`);
2370
- }
2371
- let findOptionsFields = ` populate?: string | string[] | Record<string, unknown>;`;
2372
- if (localized) {
2373
- findOptionsFields += `
2374
- locale?: Locale;`;
2375
- }
2376
- if (draftAndPublish) {
2377
- findOptionsFields += `
2378
- status?: 'draft' | 'published';`;
2379
- }
2380
- let findParams = ` populate: options.populate,`;
2381
- if (localized) {
2382
- findParams += `
2383
- locale: options.locale,`;
2384
- }
2385
- if (draftAndPublish) {
2386
- findParams += `
2387
- status: options.status,`;
2388
- }
2389
- return `/**
4119
+ return `// @ts-check
4120
+ /**
2390
4121
  * ${single.displayName} Service (Single Type)
2391
4122
  * ${single.description || ""}
2392
4123
  * Generated by strapi2front
2393
4124
  * Strapi version: ${strapiVersion}
2394
4125
  */
2395
4126
 
2396
- ${imports.join("\n")}
4127
+ const { single } = require('../../shared/client');
2397
4128
 
2398
- export interface FindOptions {
2399
- ${findOptionsFields}
2400
- }
4129
+ /**
4130
+ * @typedef {Object} FindOptions
4131
+ * @property {string|string[]|Record<string, unknown>} [populate]${localized ? "\n * @property {string} [locale]" : ""}${draftAndPublish ? "\n * @property {'draft'|'published'} [status]" : ""}
4132
+ */
2401
4133
 
2402
- // Create typed single helper
2403
- const ${toCamelCase(single.singularName)}Single = single<${typeName}>('${endpoint}');
4134
+ /** @type {ReturnType<typeof single<import('./types').${typeName}>>} */
4135
+ const ${toCamelCase(single.singularName)}Single = single('${endpoint}');
2404
4136
 
2405
- export const ${serviceName} = {
2406
- async find(options: FindOptions = {}): Promise<${typeName} | null> {
4137
+ const ${serviceName} = {
4138
+ /**
4139
+ * @param {FindOptions} [options]
4140
+ * @returns {Promise<import('./types').${typeName}|null>}
4141
+ */
4142
+ async find(options = {}) {
2407
4143
  try {
2408
4144
  const response = await ${toCamelCase(single.singularName)}Single.find({
2409
- ${findParams}
4145
+ populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
2410
4146
  });
2411
4147
 
2412
4148
  return response.data;
@@ -2418,96 +4154,60 @@ ${findParams}
2418
4154
  }
2419
4155
  },
2420
4156
 
2421
- async update(data: Partial<Omit<${typeName}, ${omitFields}>>): Promise<${typeName}> {
4157
+ /**
4158
+ * @param {Partial<import('./types').${typeName}>} data
4159
+ * @returns {Promise<import('./types').${typeName}>}
4160
+ */
4161
+ async update(data) {
2422
4162
  const response = await ${toCamelCase(single.singularName)}Single.update({ data });
2423
4163
  return response.data;
2424
4164
  },
2425
4165
 
2426
- async delete(): Promise<void> {
4166
+ /**
4167
+ * @returns {Promise<void>}
4168
+ */
4169
+ async delete() {
2427
4170
  await ${toCamelCase(single.singularName)}Single.delete();
2428
4171
  },
2429
4172
  };
4173
+
4174
+ module.exports = { ${serviceName} };
2430
4175
  `;
2431
4176
  }
2432
- function generateCollectionActions2(collection, strapiVersion) {
2433
- const typeName = toPascalCase(collection.singularName);
2434
- const serviceName = toCamelCase(collection.singularName) + "Service";
2435
- const actionPrefix = toCamelCase(collection.singularName);
2436
- const isV4 = strapiVersion === "v4";
2437
- const idInputSchema = isV4 ? "z.number().int().positive()" : "z.string()";
2438
- const idParamName = isV4 ? "id" : "documentId";
2439
- return `/**
2440
- * ${collection.displayName} Astro Actions
2441
- * ${collection.description || ""}
2442
- * Generated by strapi2front
2443
- * Strapi version: ${strapiVersion}
2444
- */
2445
-
2446
- import { defineAction } from 'astro:actions';
2447
- import { z } from 'astro:schema';
2448
- import { ${serviceName} } from './service';
2449
- import type { ${typeName} } from './types';
2450
-
2451
- export const ${actionPrefix}Actions = {
2452
- getMany: defineAction({
2453
- input: z.object({
2454
- page: z.number().optional(),
2455
- pageSize: z.number().optional(),
2456
- sort: z.string().optional(),
2457
- }).optional(),
2458
- handler: async (input) => {
2459
- const { data, pagination } = await ${serviceName}.findMany({
2460
- pagination: input ? { page: input.page, pageSize: input.pageSize } : undefined,
2461
- sort: input?.sort,
2462
- });
2463
- return { data, pagination };
2464
- },
2465
- }),
2466
-
2467
- getOne: defineAction({
2468
- input: z.object({
2469
- ${idParamName}: ${idInputSchema},
2470
- }),
2471
- handler: async (input) => {
2472
- const data = await ${serviceName}.findOne(input.${idParamName});
2473
- return { data };
2474
- },
2475
- }),
2476
4177
 
2477
- create: defineAction({
2478
- input: z.object({
2479
- data: z.record(z.unknown()),
2480
- }),
2481
- handler: async (input) => {
2482
- const data = await ${serviceName}.create(input.data as Partial<${typeName}>);
2483
- return { data };
2484
- },
2485
- }),
4178
+ // src/frameworks/nextjs/actions.ts
4179
+ async function generateNextJsActions(_schema, _options) {
4180
+ throw new Error(
4181
+ "Next.js Server Actions generator is not yet implemented. Coming soon! For now, you can use the generated services directly."
4182
+ );
4183
+ }
4184
+ function isNextJsActionsSupported(nextVersion) {
4185
+ if (!nextVersion) return false;
4186
+ const match = nextVersion.replace(/^[\^~]/, "").match(/^(\d+)/);
4187
+ const majorVersion = match ? parseInt(match[1], 10) : null;
4188
+ return majorVersion !== null && majorVersion >= 14;
4189
+ }
2486
4190
 
2487
- update: defineAction({
2488
- input: z.object({
2489
- ${idParamName}: ${idInputSchema},
2490
- data: z.record(z.unknown()),
2491
- }),
2492
- handler: async (input) => {
2493
- const data = await ${serviceName}.update(input.${idParamName}, input.data as Partial<${typeName}>);
2494
- return { data };
2495
- },
2496
- }),
4191
+ // src/frameworks/nuxt/server-routes.ts
4192
+ async function generateNuxtServerRoutes(_schema, _options) {
4193
+ throw new Error(
4194
+ "Nuxt Server Routes generator is not yet implemented. Coming soon! For now, you can use the generated services directly."
4195
+ );
4196
+ }
4197
+ function isNuxtServerRoutesSupported(nuxtVersion) {
4198
+ if (!nuxtVersion) return false;
4199
+ const match = nuxtVersion.replace(/^[\^~]/, "").match(/^(\d+)/);
4200
+ const majorVersion = match ? parseInt(match[1], 10) : null;
4201
+ return majorVersion !== null && majorVersion >= 3;
4202
+ }
2497
4203
 
2498
- delete: defineAction({
2499
- input: z.object({
2500
- ${idParamName}: ${idInputSchema},
2501
- }),
2502
- handler: async (input) => {
2503
- await ${serviceName}.delete(input.${idParamName});
2504
- return { success: true };
2505
- },
2506
- }),
4204
+ // src/frameworks/index.ts
4205
+ var frameworkSupport = {
4206
+ astro: { status: "stable", minVersion: "4.0.0" },
4207
+ nextjs: { status: "coming-soon", minVersion: "14.0.0" },
4208
+ nuxt: { status: "coming-soon", minVersion: "3.0.0" }
2507
4209
  };
2508
- `;
2509
- }
2510
4210
 
2511
- export { deleteFile, ensureDir, fileExists, formatCode, formatJson, generateActions, generateByFeature, generateClient, generateLocales, generateServices, generateTypes, listFiles, pluralize, readFile, toCamelCase, toKebabCase, toPascalCase, writeFile };
4211
+ export { deleteFile, ensureDir, fileExists, formatCode, formatJson, frameworkSupport, generateActions, generateAstroActions, generateByFeature, generateClient, generateJSDocServices, generateJSDocTypes, generateLocales, generateNextJsActions, generateNuxtServerRoutes, generateServices, generateTypeScriptTypes, generateTypes, isAstroActionsSupported, isNextJsActionsSupported, isNuxtServerRoutesSupported, listFiles, pluralize, readFile, toCamelCase, toKebabCase, toPascalCase, writeFile };
2512
4212
  //# sourceMappingURL=index.js.map
2513
4213
  //# sourceMappingURL=index.js.map