@strapi2front/generators 0.1.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 ADDED
@@ -0,0 +1,2512 @@
1
+ import path7 from 'path';
2
+ import * as prettier from 'prettier';
3
+ import fs from 'fs/promises';
4
+
5
+ // src/types/generator.ts
6
+ async function formatCode(code) {
7
+ try {
8
+ return await prettier.format(code, {
9
+ parser: "typescript",
10
+ semi: true,
11
+ singleQuote: true,
12
+ trailingComma: "es5",
13
+ printWidth: 100,
14
+ tabWidth: 2,
15
+ useTabs: false
16
+ });
17
+ } catch {
18
+ return code;
19
+ }
20
+ }
21
+ async function formatJson(code) {
22
+ try {
23
+ return await prettier.format(code, {
24
+ parser: "json",
25
+ tabWidth: 2
26
+ });
27
+ } catch {
28
+ return code;
29
+ }
30
+ }
31
+ async function ensureDir(dirPath) {
32
+ try {
33
+ await fs.mkdir(dirPath, { recursive: true });
34
+ } catch (error) {
35
+ if (error.code !== "EEXIST") {
36
+ throw error;
37
+ }
38
+ }
39
+ }
40
+ async function writeFile(filePath, content) {
41
+ const dir = path7.dirname(filePath);
42
+ await ensureDir(dir);
43
+ await fs.writeFile(filePath, content, "utf-8");
44
+ }
45
+ async function readFile(filePath) {
46
+ return await fs.readFile(filePath, "utf-8");
47
+ }
48
+ async function fileExists(filePath) {
49
+ try {
50
+ await fs.access(filePath);
51
+ return true;
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+ async function deleteFile(filePath) {
57
+ try {
58
+ await fs.unlink(filePath);
59
+ } catch (error) {
60
+ if (error.code !== "ENOENT") {
61
+ throw error;
62
+ }
63
+ }
64
+ }
65
+ async function listFiles(dirPath, extension) {
66
+ try {
67
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
68
+ let files = entries.filter((entry) => entry.isFile()).map((entry) => path7.join(dirPath, entry.name));
69
+ if (extension) {
70
+ files = files.filter((file) => file.endsWith(extension));
71
+ }
72
+ return files;
73
+ } catch {
74
+ return [];
75
+ }
76
+ }
77
+
78
+ // src/utils/naming.ts
79
+ function toPascalCase(str) {
80
+ return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
81
+ }
82
+ function toCamelCase(str) {
83
+ const pascal = toPascalCase(str);
84
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
85
+ }
86
+ function toKebabCase(str) {
87
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
88
+ }
89
+ function pluralize(word) {
90
+ if (word.endsWith("s") || word.endsWith("x") || word.endsWith("ch") || word.endsWith("sh")) {
91
+ return word + "es";
92
+ }
93
+ if (word.endsWith("y") && !["a", "e", "i", "o", "u"].includes(word.charAt(word.length - 2))) {
94
+ return word.slice(0, -1) + "ies";
95
+ }
96
+ return word + "s";
97
+ }
98
+
99
+ // src/types/generator.ts
100
+ async function generateTypes(schema, options) {
101
+ const { outputDir } = options;
102
+ const generatedFiles = [];
103
+ await ensureDir(path7.join(outputDir, "collections"));
104
+ await ensureDir(path7.join(outputDir, "components"));
105
+ const utilsPath = path7.join(outputDir, "utils.ts");
106
+ const strapiVersion = options.strapiVersion ?? "v5";
107
+ await writeFile(utilsPath, await formatCode(generateUtilityTypes(options.blocksRendererInstalled ?? false, strapiVersion)));
108
+ generatedFiles.push(utilsPath);
109
+ for (const collection of schema.collections) {
110
+ const fileName = `${toKebabCase(collection.singularName)}.ts`;
111
+ const filePath = path7.join(outputDir, "collections", fileName);
112
+ const content = generateCollectionType(collection, schema.components);
113
+ await writeFile(filePath, await formatCode(content));
114
+ generatedFiles.push(filePath);
115
+ }
116
+ for (const single of schema.singles) {
117
+ const fileName = `${toKebabCase(single.singularName)}.ts`;
118
+ const filePath = path7.join(outputDir, "collections", fileName);
119
+ const content = generateSingleType(single, schema.components);
120
+ await writeFile(filePath, await formatCode(content));
121
+ generatedFiles.push(filePath);
122
+ }
123
+ for (const component of schema.components) {
124
+ const fileName = `${toKebabCase(component.name)}.ts`;
125
+ const filePath = path7.join(outputDir, "components", fileName);
126
+ const content = generateComponentType(component);
127
+ await writeFile(filePath, await formatCode(content));
128
+ generatedFiles.push(filePath);
129
+ }
130
+ return generatedFiles;
131
+ }
132
+ function generateUtilityTypes(blocksRendererInstalled, strapiVersion) {
133
+ const isV4 = strapiVersion === "v4";
134
+ const blocksContentType = isV4 ? `/**
135
+ * Rich text content (Strapi v4)
136
+ * Can be markdown string or custom JSON structure
137
+ */
138
+ export type RichTextContent = string;` : blocksRendererInstalled ? `/**
139
+ * Blocks content type (Strapi v5 rich text)
140
+ * Re-exported from @strapi/blocks-react-renderer
141
+ */
142
+ export type { BlocksContent } from '@strapi/blocks-react-renderer';` : `/**
143
+ * Blocks content type (Strapi v5 rich text)
144
+ *
145
+ * For full type support and rendering, install:
146
+ * npm install @strapi/blocks-react-renderer
147
+ *
148
+ * Then re-run: npx strapi-integrate sync
149
+ */
150
+ export type BlocksContent = unknown[];`;
151
+ const baseEntity = isV4 ? `/**
152
+ * Base entity fields (Strapi v4)
153
+ */
154
+ export interface StrapiBaseEntity {
155
+ id: number;
156
+ createdAt: string;
157
+ updatedAt: string;
158
+ publishedAt: string | null;
159
+ }` : `/**
160
+ * Base entity fields (Strapi v5)
161
+ */
162
+ export interface StrapiBaseEntity {
163
+ id: number;
164
+ documentId: string;
165
+ createdAt: string;
166
+ updatedAt: string;
167
+ publishedAt: string | null;
168
+ }`;
169
+ const mediaType = isV4 ? `/**
170
+ * Strapi media object (v4)
171
+ */
172
+ export interface StrapiMedia {
173
+ id: number;
174
+ name: string;
175
+ alternativeText: string | null;
176
+ caption: string | null;
177
+ width: number;
178
+ height: number;
179
+ formats: {
180
+ thumbnail?: StrapiMediaFormat;
181
+ small?: StrapiMediaFormat;
182
+ medium?: StrapiMediaFormat;
183
+ large?: StrapiMediaFormat;
184
+ } | null;
185
+ hash: string;
186
+ ext: string;
187
+ mime: string;
188
+ size: number;
189
+ url: string;
190
+ previewUrl: string | null;
191
+ provider: string;
192
+ createdAt: string;
193
+ updatedAt: string;
194
+ }` : `/**
195
+ * Strapi media object (v5)
196
+ */
197
+ export interface StrapiMedia {
198
+ id: number;
199
+ documentId: string;
200
+ name: string;
201
+ alternativeText: string | null;
202
+ caption: string | null;
203
+ width: number;
204
+ height: number;
205
+ formats: {
206
+ thumbnail?: StrapiMediaFormat;
207
+ small?: StrapiMediaFormat;
208
+ medium?: StrapiMediaFormat;
209
+ large?: StrapiMediaFormat;
210
+ } | null;
211
+ hash: string;
212
+ ext: string;
213
+ mime: string;
214
+ size: number;
215
+ url: string;
216
+ previewUrl: string | null;
217
+ provider: string;
218
+ createdAt: string;
219
+ updatedAt: string;
220
+ }`;
221
+ const rawResponseTypes = isV4 ? `
222
+ /**
223
+ * Strapi v4 raw API response (with nested attributes)
224
+ */
225
+ export interface StrapiV4RawItem<T> {
226
+ id: number;
227
+ attributes: Omit<T, 'id'>;
228
+ }
229
+
230
+ export interface StrapiV4RawResponse<T> {
231
+ data: StrapiV4RawItem<T>;
232
+ meta: Record<string, unknown>;
233
+ }
234
+
235
+ export interface StrapiV4RawListResponse<T> {
236
+ data: StrapiV4RawItem<T>[];
237
+ meta: {
238
+ pagination: StrapiPagination;
239
+ };
240
+ }
241
+
242
+ /**
243
+ * Flatten Strapi v4 response item
244
+ */
245
+ export function flattenV4Response<T>(item: StrapiV4RawItem<T>): T {
246
+ return { id: item.id, ...item.attributes } as T;
247
+ }
248
+
249
+ /**
250
+ * Flatten Strapi v4 list response
251
+ */
252
+ export function flattenV4ListResponse<T>(items: StrapiV4RawItem<T>[]): T[] {
253
+ return items.map(item => flattenV4Response<T>(item));
254
+ }` : "";
255
+ return `/**
256
+ * Strapi utility types
257
+ * Generated by strapi-integrate
258
+ * Strapi version: ${strapiVersion}
259
+ */
260
+
261
+ ${mediaType}
262
+
263
+ export interface StrapiMediaFormat {
264
+ name: string;
265
+ hash: string;
266
+ ext: string;
267
+ mime: string;
268
+ width: number;
269
+ height: number;
270
+ size: number;
271
+ url: string;
272
+ }
273
+
274
+ /**
275
+ * Strapi pagination
276
+ */
277
+ export interface StrapiPagination {
278
+ page: number;
279
+ pageSize: number;
280
+ pageCount: number;
281
+ total: number;
282
+ }
283
+
284
+ /**
285
+ * Strapi response wrapper
286
+ */
287
+ export interface StrapiResponse<T> {
288
+ data: T;
289
+ meta: {
290
+ pagination?: StrapiPagination;
291
+ };
292
+ }
293
+
294
+ /**
295
+ * Strapi list response
296
+ */
297
+ export interface StrapiListResponse<T> {
298
+ data: T[];
299
+ meta: {
300
+ pagination: StrapiPagination;
301
+ };
302
+ }
303
+
304
+ ${baseEntity}
305
+ ${rawResponseTypes}
306
+ ${blocksContentType}
307
+ `;
308
+ }
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
+ function generateDependencyImports(relations, components) {
338
+ const imports = [];
339
+ for (const relation of relations) {
340
+ const typeName = toPascalCase(relation);
341
+ const fileName = toKebabCase(relation);
342
+ imports.push(`import type { ${typeName} } from './${fileName}';`);
343
+ }
344
+ for (const component of components) {
345
+ const typeName = toPascalCase(component);
346
+ const fileName = toKebabCase(component);
347
+ imports.push(`import type { ${typeName} } from '../components/${fileName}';`);
348
+ }
349
+ return imports.join("\n");
350
+ }
351
+ function generateCollectionType(collection, components) {
352
+ const typeName = toPascalCase(collection.singularName);
353
+ const attributes = generateAttributes(collection.attributes);
354
+ const { relations, components: componentDeps } = extractDependencies(collection.attributes, typeName);
355
+ const dependencyImports = generateDependencyImports(relations, componentDeps);
356
+ const utilsImports = ["StrapiBaseEntity"];
357
+ const attributesStr = JSON.stringify(collection.attributes);
358
+ if (attributesStr.includes('"type":"media"')) {
359
+ utilsImports.push("StrapiMedia");
360
+ }
361
+ if (attributesStr.includes('"type":"blocks"')) {
362
+ utilsImports.push("BlocksContent");
363
+ }
364
+ return `/**
365
+ * ${collection.displayName}
366
+ * ${collection.description || ""}
367
+ * Generated by strapi-integrate
368
+ */
369
+
370
+ import type { ${utilsImports.join(", ")} } from '../utils';
371
+ ${dependencyImports ? dependencyImports + "\n" : ""}
372
+ export interface ${typeName} extends StrapiBaseEntity {
373
+ ${attributes}
374
+ }
375
+
376
+ export interface ${typeName}Filters {
377
+ id?: number | { $eq?: number; $ne?: number; $in?: number[]; $notIn?: number[] };
378
+ documentId?: string | { $eq?: string; $ne?: string };
379
+ createdAt?: string | { $eq?: string; $gt?: string; $gte?: string; $lt?: string; $lte?: string };
380
+ updatedAt?: string | { $eq?: string; $gt?: string; $gte?: string; $lt?: string; $lte?: string };
381
+ publishedAt?: string | null | { $eq?: string; $ne?: string; $null?: boolean };
382
+ $and?: ${typeName}Filters[];
383
+ $or?: ${typeName}Filters[];
384
+ $not?: ${typeName}Filters;
385
+ }
386
+
387
+ export interface ${typeName}Sort {
388
+ field: keyof ${typeName};
389
+ order: 'asc' | 'desc';
390
+ }
391
+ `;
392
+ }
393
+ function generateSingleType(single, components) {
394
+ const typeName = toPascalCase(single.singularName);
395
+ const attributes = generateAttributes(single.attributes);
396
+ const { relations, components: componentDeps } = extractDependencies(single.attributes, typeName);
397
+ const dependencyImports = generateDependencyImports(relations, componentDeps);
398
+ const utilsImports = ["StrapiBaseEntity"];
399
+ const attributesStr = JSON.stringify(single.attributes);
400
+ if (attributesStr.includes('"type":"media"')) {
401
+ utilsImports.push("StrapiMedia");
402
+ }
403
+ if (attributesStr.includes('"type":"blocks"')) {
404
+ utilsImports.push("BlocksContent");
405
+ }
406
+ return `/**
407
+ * ${single.displayName}
408
+ * ${single.description || ""}
409
+ * Generated by strapi-integrate
410
+ */
411
+
412
+ import type { ${utilsImports.join(", ")} } from '../utils';
413
+ ${dependencyImports ? dependencyImports + "\n" : ""}
414
+ export interface ${typeName} extends StrapiBaseEntity {
415
+ ${attributes}
416
+ }
417
+ `;
418
+ }
419
+ function generateComponentType(component) {
420
+ const typeName = toPascalCase(component.name);
421
+ const attributes = generateAttributes(component.attributes);
422
+ const { relations, components: componentDeps } = extractDependencies(component.attributes, typeName);
423
+ const imports = [];
424
+ for (const relation of relations) {
425
+ const relTypeName = toPascalCase(relation);
426
+ const fileName = toKebabCase(relation);
427
+ imports.push(`import type { ${relTypeName} } from '../collections/${fileName}';`);
428
+ }
429
+ for (const comp of componentDeps) {
430
+ const compTypeName = toPascalCase(comp);
431
+ const fileName = toKebabCase(comp);
432
+ if (compTypeName !== typeName) {
433
+ imports.push(`import type { ${compTypeName} } from './${fileName}';`);
434
+ }
435
+ }
436
+ const utilsImports = [];
437
+ const attributesStr = JSON.stringify(component.attributes);
438
+ if (attributesStr.includes('"type":"media"')) {
439
+ utilsImports.push("StrapiMedia");
440
+ }
441
+ if (attributesStr.includes('"type":"blocks"')) {
442
+ utilsImports.push("BlocksContent");
443
+ }
444
+ const utilsImportLine = utilsImports.length > 0 ? `import type { ${utilsImports.join(", ")} } from '../utils';
445
+ ` : "";
446
+ const dependencyImports = imports.length > 0 ? imports.join("\n") + "\n" : "";
447
+ return `/**
448
+ * ${component.displayName} component
449
+ * Category: ${component.category}
450
+ * ${component.description || ""}
451
+ * Generated by strapi-integrate
452
+ */
453
+
454
+ ${utilsImportLine}${dependencyImports}
455
+ export interface ${typeName} {
456
+ id: number;
457
+ ${attributes}
458
+ }
459
+ `;
460
+ }
461
+ function generateAttributes(attributes, components) {
462
+ const lines = [];
463
+ for (const [name, attr] of Object.entries(attributes)) {
464
+ const tsType = attributeToTsType(attr);
465
+ const optional = attr.required ? "" : "?";
466
+ const comment = getAttributeComment(attr);
467
+ if (comment) {
468
+ lines.push(` /** ${comment} */`);
469
+ }
470
+ lines.push(` ${name}${optional}: ${tsType};`);
471
+ }
472
+ return lines.join("\n");
473
+ }
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;
556
+ const generatedFiles = [];
557
+ await ensureDir(outputDir);
558
+ 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));
563
+ generatedFiles.push(filePath);
564
+ }
565
+ 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));
570
+ generatedFiles.push(filePath);
571
+ }
572
+ return generatedFiles;
573
+ }
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;
579
+ 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';`);
613
+ }
614
+ const findOneOptionsFields = [
615
+ ` populate?: string | string[] | Record<string, unknown>;`
616
+ ];
617
+ if (localized) {
618
+ findOneOptionsFields.push(` locale?: Locale;`);
619
+ }
620
+ if (draftAndPublish) {
621
+ findOneOptionsFields.push(` status?: 'draft' | 'published';`);
622
+ }
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,`);
631
+ }
632
+ if (draftAndPublish) {
633
+ findParams.push(` status: options.status,`);
634
+ }
635
+ const findOneParams = [
636
+ ` populate: options.populate,`
637
+ ];
638
+ if (localized) {
639
+ findOneParams.push(` locale: options.locale,`);
640
+ }
641
+ if (draftAndPublish) {
642
+ findOneParams.push(` status: options.status,`);
643
+ }
644
+ const findBySlugParams = [
645
+ ` populate: options.populate,`
646
+ ];
647
+ if (localized) {
648
+ findBySlugParams.push(` locale: options.locale,`);
649
+ }
650
+ if (draftAndPublish) {
651
+ findBySlugParams.push(` status: options.status,`);
652
+ }
653
+ return `/**
654
+ * ${collection.displayName} Service
655
+ * ${collection.description || ""}
656
+ * Generated by strapi-integrate
657
+ * Strapi version: ${strapiVersion}
658
+ */
659
+
660
+ ${imports.join("\n")}
661
+
662
+ export interface FindManyOptions {
663
+ ${findManyOptionsFields.join("\n")}
664
+ }
665
+
666
+ export interface FindOneOptions {
667
+ ${findOneOptionsFields.join("\n")}
668
+ }
669
+
670
+ // Create typed collection helper
671
+ const ${toCamelCase(collection.singularName)}Collection = collection<${typeName}>('${endpoint}');
672
+
673
+ export const ${serviceName} = {
674
+ /**
675
+ * Find multiple ${collection.pluralName}
676
+ */
677
+ async findMany(options: FindManyOptions = {}): Promise<{ data: ${typeName}[]; pagination: StrapiPagination }> {
678
+ const response = await ${toCamelCase(collection.singularName)}Collection.find({
679
+ ${findParams.join("\n")}
680
+ });
681
+
682
+ return {
683
+ data: response.data,
684
+ pagination: response.meta.pagination,
685
+ };
686
+ },
687
+
688
+ /**
689
+ * Find all ${collection.pluralName} (handles pagination automatically)
690
+ */
691
+ async findAll(options: Omit<FindManyOptions, 'pagination'> = {}): Promise<${typeName}[]> {
692
+ const allItems: ${typeName}[] = [];
693
+ let page = 1;
694
+ let hasMore = true;
695
+
696
+ while (hasMore) {
697
+ const { data, pagination } = await this.findMany({
698
+ ...options,
699
+ pagination: { page, pageSize: 100 },
700
+ });
701
+
702
+ allItems.push(...data);
703
+ hasMore = page < pagination.pageCount;
704
+ page++;
705
+ }
706
+
707
+ return allItems;
708
+ },
709
+
710
+ /**
711
+ * Find one ${collection.singularName} by ${idName}
712
+ */
713
+ async findOne(${idParam}, options: FindOneOptions = {}): Promise<${typeName} | null> {
714
+ try {
715
+ const response = await ${toCamelCase(collection.singularName)}Collection.findOne(${idName}, {
716
+ ${findOneParams.join("\n")}
717
+ });
718
+
719
+ return response.data;
720
+ } catch (error) {
721
+ // Return null if not found
722
+ if (error instanceof Error && error.message.includes('404')) {
723
+ return null;
724
+ }
725
+ throw error;
726
+ }
727
+ },
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
+ ` : ""}
742
+ /**
743
+ * Create a new ${collection.singularName}
744
+ */
745
+ async create(data: Partial<Omit<${typeName}, ${omitFields}>>): Promise<${typeName}> {
746
+ const response = await ${toCamelCase(collection.singularName)}Collection.create({ data });
747
+ return response.data;
748
+ },
749
+
750
+ /**
751
+ * Update a ${collection.singularName}
752
+ */
753
+ async update(${idParam}, data: Partial<Omit<${typeName}, ${omitFields}>>): Promise<${typeName}> {
754
+ const response = await ${toCamelCase(collection.singularName)}Collection.update(${idName}, { data });
755
+ return response.data;
756
+ },
757
+
758
+ /**
759
+ * Delete a ${collection.singularName}
760
+ */
761
+ async delete(${idParam}): Promise<void> {
762
+ await ${toCamelCase(collection.singularName)}Collection.delete(${idName});
763
+ },
764
+
765
+ /**
766
+ * Count ${collection.pluralName}
767
+ */
768
+ async count(filters?: ${typeName}Filters): Promise<number> {
769
+ const { pagination } = await this.findMany({
770
+ filters,
771
+ pagination: { pageSize: 1 },
772
+ });
773
+
774
+ return pagination.total;
775
+ },
776
+ };
777
+ `;
778
+ }
779
+ function generateSingleService(single, typesImportPath, strapiVersion) {
780
+ const typeName = toPascalCase(single.singularName);
781
+ const serviceName = toCamelCase(single.singularName) + "Service";
782
+ const fileName = toKebabCase(single.singularName);
783
+ const endpoint = single.singularName;
784
+ const isV4 = strapiVersion === "v4";
785
+ const omitFields = isV4 ? "'id' | 'createdAt' | 'updatedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt'";
786
+ 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
+ }
812
+ return `/**
813
+ * ${single.displayName} Service (Single Type)
814
+ * ${single.description || ""}
815
+ * Generated by strapi-integrate
816
+ * Strapi version: ${strapiVersion}
817
+ */
818
+
819
+ ${imports.join("\n")}
820
+
821
+ export interface FindOptions {
822
+ ${findOptionsFields.join("\n")}
823
+ }
824
+
825
+ // Create typed single helper
826
+ const ${toCamelCase(single.singularName)}Single = single<${typeName}>('${endpoint}');
827
+
828
+ export const ${serviceName} = {
829
+ /**
830
+ * Get ${single.displayName}
831
+ */
832
+ async find(options: FindOptions = {}): Promise<${typeName} | null> {
833
+ try {
834
+ const response = await ${toCamelCase(single.singularName)}Single.find({
835
+ ${findParams.join("\n")}
836
+ });
837
+
838
+ return response.data;
839
+ } catch (error) {
840
+ if (error instanceof Error && error.message.includes('404')) {
841
+ return null;
842
+ }
843
+ throw error;
844
+ }
845
+ },
846
+
847
+ /**
848
+ * Update ${single.displayName}
849
+ */
850
+ async update(data: Partial<Omit<${typeName}, ${omitFields}>>): Promise<${typeName}> {
851
+ const response = await ${toCamelCase(single.singularName)}Single.update({ data });
852
+ return response.data;
853
+ },
854
+
855
+ /**
856
+ * Delete ${single.displayName}
857
+ */
858
+ async delete(): Promise<void> {
859
+ await ${toCamelCase(single.singularName)}Single.delete();
860
+ },
861
+ };
862
+ `;
863
+ }
864
+ async function generateActions(schema, options) {
865
+ const { outputDir, servicesImportPath, strapiVersion = "v5" } = options;
866
+ const generatedFiles = [];
867
+ await ensureDir(outputDir);
868
+ 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);
872
+ await writeFile(filePath, await formatCode(content));
873
+ generatedFiles.push(filePath);
874
+ }
875
+ 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);
879
+ await writeFile(filePath, await formatCode(content));
880
+ generatedFiles.push(filePath);
881
+ }
882
+ return generatedFiles;
883
+ }
884
+ function generateCollectionActions(collection, servicesImportPath, strapiVersion) {
885
+ const serviceName = toCamelCase(collection.singularName) + "Service";
886
+ const actionsName = toCamelCase(collection.singularName);
887
+ const fileName = toKebabCase(collection.singularName);
888
+ 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";
892
+ const hasSlug = "slug" in collection.attributes;
893
+ return `/**
894
+ * ${collection.displayName} Actions
895
+ * ${collection.description || ""}
896
+ * Generated by strapi-integrate
897
+ * Strapi version: ${strapiVersion}
898
+ */
899
+
900
+ import { defineAction, ActionError } from 'astro:actions';
901
+ import { z } from 'astro:schema';
902
+ import { ${serviceName} } from '${servicesImportPath}/${fileName}.service';
903
+
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();
911
+
912
+ /**
913
+ * ${collection.displayName} actions
914
+ */
915
+ export const ${actionsName} = {
916
+ /**
917
+ * Get all ${collection.pluralName} with pagination
918
+ */
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
+ });
930
+
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
+ }),
940
+
941
+ /**
942
+ * Get a single ${collection.singularName} by ${idComment}
943
+ */
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 });
952
+
953
+ if (!result) {
954
+ throw new ActionError({
955
+ code: 'NOT_FOUND',
956
+ message: '${collection.displayName} not found',
957
+ });
958
+ }
959
+
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
+ });
967
+ }
968
+ },
969
+ }),
970
+ ${hasSlug ? `
971
+ /**
972
+ * Get a single ${collection.singularName} by slug
973
+ */
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
+ }
989
+
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
+ }),
1000
+ ` : ""}
1001
+ /**
1002
+ * Create a new ${collection.singularName}
1003
+ */
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
+ }),
1020
+
1021
+ /**
1022
+ * Update a ${collection.singularName}
1023
+ */
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
+ }),
1041
+
1042
+ /**
1043
+ * Delete a ${collection.singularName}
1044
+ */
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
+ }),
1061
+
1062
+ /**
1063
+ * Count ${collection.pluralName}
1064
+ */
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
+ }),
1081
+ };
1082
+ `;
1083
+ }
1084
+ function generateSingleActions(single, servicesImportPath) {
1085
+ const serviceName = toCamelCase(single.singularName) + "Service";
1086
+ const actionsName = toCamelCase(single.singularName);
1087
+ const fileName = toKebabCase(single.singularName);
1088
+ return `/**
1089
+ * ${single.displayName} Actions (Single Type)
1090
+ * ${single.description || ""}
1091
+ * Generated by strapi-integrate
1092
+ */
1093
+
1094
+ import { defineAction, ActionError } from 'astro:actions';
1095
+ import { z } from 'astro:schema';
1096
+ import { ${serviceName} } from '${servicesImportPath}/${fileName}.service';
1097
+
1098
+ /**
1099
+ * ${single.displayName} actions
1100
+ */
1101
+ export const ${actionsName} = {
1102
+ /**
1103
+ * Get ${single.displayName}
1104
+ */
1105
+ get: defineAction({
1106
+ input: z.object({
1107
+ populate: z.union([z.string(), z.array(z.string())]).optional(),
1108
+ }).optional(),
1109
+ handler: async (input) => {
1110
+ try {
1111
+ const result = await ${serviceName}.find({
1112
+ populate: input?.populate,
1113
+ });
1114
+
1115
+ if (!result) {
1116
+ throw new ActionError({
1117
+ code: 'NOT_FOUND',
1118
+ message: '${single.displayName} not found',
1119
+ });
1120
+ }
1121
+
1122
+ return result;
1123
+ } catch (error) {
1124
+ if (error instanceof ActionError) throw error;
1125
+ throw new ActionError({
1126
+ code: 'INTERNAL_SERVER_ERROR',
1127
+ message: error instanceof Error ? error.message : 'Failed to fetch ${single.singularName}',
1128
+ });
1129
+ }
1130
+ },
1131
+ }),
1132
+
1133
+ /**
1134
+ * Update ${single.displayName}
1135
+ */
1136
+ update: defineAction({
1137
+ input: z.object({
1138
+ data: z.record(z.unknown()),
1139
+ }),
1140
+ handler: async ({ data }) => {
1141
+ try {
1142
+ const result = await ${serviceName}.update(data);
1143
+ return result;
1144
+ } catch (error) {
1145
+ throw new ActionError({
1146
+ code: 'INTERNAL_SERVER_ERROR',
1147
+ message: error instanceof Error ? error.message : 'Failed to update ${single.singularName}',
1148
+ });
1149
+ }
1150
+ },
1151
+ }),
1152
+ };
1153
+ `;
1154
+ }
1155
+ async function generateClient(options) {
1156
+ const { outputDir, strapiVersion = "v5" } = options;
1157
+ const generatedFiles = [];
1158
+ await ensureDir(outputDir);
1159
+ const filePath = path7.join(outputDir, "client.ts");
1160
+ const content = generateClientFile(strapiVersion);
1161
+ await writeFile(filePath, await formatCode(content));
1162
+ generatedFiles.push(filePath);
1163
+ return generatedFiles;
1164
+ }
1165
+ function generateClientFile(strapiVersion) {
1166
+ const isV4 = strapiVersion === "v4";
1167
+ if (isV4) {
1168
+ return generateV4ClientFile();
1169
+ }
1170
+ return generateV5ClientFile();
1171
+ }
1172
+ function generateV5ClientFile() {
1173
+ return `/**
1174
+ * Strapi Client (v5)
1175
+ * Generated by strapi-integrate
1176
+ */
1177
+
1178
+ import Strapi from 'strapi-sdk-js';
1179
+
1180
+ // Initialize the Strapi client
1181
+ const strapiUrl = import.meta.env.STRAPI_URL || process.env.STRAPI_URL || 'http://localhost:1337';
1182
+ const strapiToken = import.meta.env.STRAPI_TOKEN || process.env.STRAPI_TOKEN;
1183
+
1184
+ export const strapi = new Strapi({
1185
+ url: strapiUrl,
1186
+ axiosOptions: {
1187
+ headers: strapiToken ? {
1188
+ Authorization: \`Bearer \${strapiToken}\`,
1189
+ } : {},
1190
+ },
1191
+ });
1192
+
1193
+ // Pagination type
1194
+ export interface StrapiPagination {
1195
+ page: number;
1196
+ pageSize: number;
1197
+ pageCount: number;
1198
+ total: number;
1199
+ }
1200
+
1201
+ // Default pagination for fallback
1202
+ const defaultPagination: StrapiPagination = {
1203
+ page: 1,
1204
+ pageSize: 25,
1205
+ pageCount: 1,
1206
+ total: 0,
1207
+ };
1208
+
1209
+ // Response types
1210
+ interface StrapiListResponse<T> {
1211
+ data: T[];
1212
+ meta: {
1213
+ pagination?: StrapiPagination;
1214
+ };
1215
+ }
1216
+
1217
+ interface StrapiSingleResponse<T> {
1218
+ data: T;
1219
+ meta?: Record<string, unknown>;
1220
+ }
1221
+
1222
+ // Helper to get typed collection
1223
+ export function collection<T>(pluralName: string) {
1224
+ return {
1225
+ async find(params?: Record<string, unknown>): Promise<{ data: T[]; meta: { pagination: StrapiPagination } }> {
1226
+ const response = await strapi.find(pluralName, params) as unknown as StrapiListResponse<T>;
1227
+ return {
1228
+ data: Array.isArray(response.data) ? response.data : [],
1229
+ meta: {
1230
+ pagination: response.meta?.pagination || defaultPagination,
1231
+ },
1232
+ };
1233
+ },
1234
+ async findOne(documentId: string, params?: Record<string, unknown>): Promise<{ data: T }> {
1235
+ const response = await strapi.findOne(pluralName, documentId, params) as unknown as StrapiSingleResponse<T>;
1236
+ return { data: response.data };
1237
+ },
1238
+ async create(data: { data: Partial<T> }): Promise<{ data: T }> {
1239
+ const response = await strapi.create(pluralName, data.data as Record<string, unknown>) as unknown as StrapiSingleResponse<T>;
1240
+ return { data: response.data };
1241
+ },
1242
+ async update(documentId: string, data: { data: Partial<T> }): Promise<{ data: T }> {
1243
+ const response = await strapi.update(pluralName, documentId, data.data as Record<string, unknown>) as unknown as StrapiSingleResponse<T>;
1244
+ return { data: response.data };
1245
+ },
1246
+ async delete(documentId: string): Promise<void> {
1247
+ await strapi.delete(pluralName, documentId);
1248
+ },
1249
+ };
1250
+ }
1251
+
1252
+ // Helper to get typed single type
1253
+ export function single<T>(singularName: string) {
1254
+ return {
1255
+ async find(params?: Record<string, unknown>): Promise<{ data: T }> {
1256
+ const response = await strapi.find(singularName, params) as unknown as StrapiSingleResponse<T>;
1257
+ return { data: response.data };
1258
+ },
1259
+ async update(data: { data: Partial<T> }): Promise<{ data: T }> {
1260
+ const response = await strapi.update(singularName, 1 as unknown as string, data.data as Record<string, unknown>) as unknown as StrapiSingleResponse<T>;
1261
+ return { data: response.data };
1262
+ },
1263
+ async delete(): Promise<void> {
1264
+ await strapi.delete(singularName, 1 as unknown as string);
1265
+ },
1266
+ };
1267
+ }
1268
+ `;
1269
+ }
1270
+ function generateV4ClientFile() {
1271
+ return `/**
1272
+ * Strapi Client (v4)
1273
+ * Generated by strapi-integrate
1274
+ */
1275
+
1276
+ import Strapi from 'strapi-sdk-js';
1277
+
1278
+ // Initialize the Strapi client
1279
+ const strapiUrl = import.meta.env.STRAPI_URL || process.env.STRAPI_URL || 'http://localhost:1337';
1280
+ const strapiToken = import.meta.env.STRAPI_TOKEN || process.env.STRAPI_TOKEN;
1281
+
1282
+ export const strapi = new Strapi({
1283
+ url: strapiUrl,
1284
+ axiosOptions: {
1285
+ headers: strapiToken ? {
1286
+ Authorization: \`Bearer \${strapiToken}\`,
1287
+ } : {},
1288
+ },
1289
+ });
1290
+
1291
+ // Pagination type
1292
+ export interface StrapiPagination {
1293
+ page: number;
1294
+ pageSize: number;
1295
+ pageCount: number;
1296
+ total: number;
1297
+ }
1298
+
1299
+ // Default pagination for fallback
1300
+ const defaultPagination: StrapiPagination = {
1301
+ page: 1,
1302
+ pageSize: 25,
1303
+ pageCount: 1,
1304
+ total: 0,
1305
+ };
1306
+
1307
+ // Strapi v4 raw response types (with nested attributes)
1308
+ interface StrapiV4RawItem<T> {
1309
+ id: number;
1310
+ attributes: Omit<T, 'id'>;
1311
+ }
1312
+
1313
+ interface StrapiV4RawListResponse<T> {
1314
+ data: StrapiV4RawItem<T>[];
1315
+ meta: {
1316
+ pagination?: StrapiPagination;
1317
+ };
1318
+ }
1319
+
1320
+ interface StrapiV4RawSingleResponse<T> {
1321
+ data: StrapiV4RawItem<T>;
1322
+ meta?: Record<string, unknown>;
1323
+ }
1324
+
1325
+ /**
1326
+ * Flatten a Strapi v4 response item (merges id with attributes)
1327
+ */
1328
+ function flattenItem<T>(item: StrapiV4RawItem<T>): T {
1329
+ return { id: item.id, ...item.attributes } as T;
1330
+ }
1331
+
1332
+ /**
1333
+ * Recursively flatten nested relations in Strapi v4 response
1334
+ */
1335
+ function flattenRelations<T>(data: T): T {
1336
+ if (data === null || data === undefined) return data;
1337
+ if (Array.isArray(data)) {
1338
+ return data.map(item => flattenRelations(item)) as unknown as T;
1339
+ }
1340
+ if (typeof data === 'object') {
1341
+ const result: Record<string, unknown> = {};
1342
+ for (const [key, value] of Object.entries(data as Record<string, unknown>)) {
1343
+ // Check if this is a Strapi v4 relation response { data: { id, attributes } }
1344
+ if (value && typeof value === 'object' && 'data' in value) {
1345
+ const relationData = (value as { data: unknown }).data;
1346
+ if (relationData === null) {
1347
+ result[key] = null;
1348
+ } else if (Array.isArray(relationData)) {
1349
+ // To-many relation
1350
+ result[key] = relationData.map((item: StrapiV4RawItem<unknown>) =>
1351
+ flattenRelations(flattenItem(item))
1352
+ );
1353
+ } else if (typeof relationData === 'object' && 'id' in relationData && 'attributes' in relationData) {
1354
+ // To-one relation
1355
+ result[key] = flattenRelations(flattenItem(relationData as StrapiV4RawItem<unknown>));
1356
+ } else {
1357
+ result[key] = flattenRelations(value);
1358
+ }
1359
+ } else {
1360
+ result[key] = flattenRelations(value);
1361
+ }
1362
+ }
1363
+ return result as T;
1364
+ }
1365
+ return data;
1366
+ }
1367
+
1368
+ // Helper to get typed collection
1369
+ export function collection<T>(pluralName: string) {
1370
+ return {
1371
+ async find(params?: Record<string, unknown>): Promise<{ data: T[]; meta: { pagination: StrapiPagination } }> {
1372
+ const response = await strapi.find(pluralName, params) as unknown as StrapiV4RawListResponse<T>;
1373
+ const flattenedData = Array.isArray(response.data)
1374
+ ? response.data.map(item => flattenRelations(flattenItem<T>(item)))
1375
+ : [];
1376
+ return {
1377
+ data: flattenedData,
1378
+ meta: {
1379
+ pagination: response.meta?.pagination || defaultPagination,
1380
+ },
1381
+ };
1382
+ },
1383
+ async findOne(id: number | string, params?: Record<string, unknown>): Promise<{ data: T }> {
1384
+ const response = await strapi.findOne(pluralName, String(id), params) as unknown as StrapiV4RawSingleResponse<T>;
1385
+ return { data: flattenRelations(flattenItem<T>(response.data)) };
1386
+ },
1387
+ async create(data: { data: Partial<T> }): Promise<{ data: T }> {
1388
+ const response = await strapi.create(pluralName, data.data as Record<string, unknown>) as unknown as StrapiV4RawSingleResponse<T>;
1389
+ return { data: flattenRelations(flattenItem<T>(response.data)) };
1390
+ },
1391
+ async update(id: number | string, data: { data: Partial<T> }): Promise<{ data: T }> {
1392
+ const response = await strapi.update(pluralName, String(id), data.data as Record<string, unknown>) as unknown as StrapiV4RawSingleResponse<T>;
1393
+ return { data: flattenRelations(flattenItem<T>(response.data)) };
1394
+ },
1395
+ async delete(id: number | string): Promise<void> {
1396
+ await strapi.delete(pluralName, String(id));
1397
+ },
1398
+ };
1399
+ }
1400
+
1401
+ // Helper to get typed single type
1402
+ export function single<T>(singularName: string) {
1403
+ return {
1404
+ async find(params?: Record<string, unknown>): Promise<{ data: T }> {
1405
+ const response = await strapi.find(singularName, params) as unknown as StrapiV4RawSingleResponse<T>;
1406
+ return { data: flattenRelations(flattenItem<T>(response.data)) };
1407
+ },
1408
+ async update(data: { data: Partial<T> }): Promise<{ data: T }> {
1409
+ const response = await strapi.update(singularName, 1 as unknown as string, data.data as Record<string, unknown>) as unknown as StrapiV4RawSingleResponse<T>;
1410
+ return { data: flattenRelations(flattenItem<T>(response.data)) };
1411
+ },
1412
+ async delete(): Promise<void> {
1413
+ await strapi.delete(singularName, 1 as unknown as string);
1414
+ },
1415
+ };
1416
+ }
1417
+ `;
1418
+ }
1419
+ async function generateLocales(locales, options) {
1420
+ const { outputDir } = options;
1421
+ const generatedFiles = [];
1422
+ await ensureDir(outputDir);
1423
+ const filePath = path7.join(outputDir, "locales.ts");
1424
+ const content = generateLocalesFile(locales);
1425
+ await writeFile(filePath, await formatCode(content));
1426
+ generatedFiles.push(filePath);
1427
+ return generatedFiles;
1428
+ }
1429
+ function generateLocalesFile(locales) {
1430
+ if (locales.length === 0) {
1431
+ return `/**
1432
+ * Strapi Locales
1433
+ * Generated by strapi-integrate
1434
+ *
1435
+ * Note: No locales found. i18n might not be enabled in Strapi.
1436
+ */
1437
+
1438
+ export const locales = [] as const;
1439
+
1440
+ export type Locale = string;
1441
+
1442
+ export const defaultLocale: Locale = 'en';
1443
+
1444
+ export const localeNames: Record<string, string> = {};
1445
+ `;
1446
+ }
1447
+ const defaultLocale = locales.find((l) => l.isDefault)?.code || locales[0]?.code || "en";
1448
+ const localeCodes = locales.map((l) => `'${l.code}'`).join(" | ");
1449
+ const localeArray = locales.map((l) => `'${l.code}'`).join(", ");
1450
+ const localeNames = locales.map((l) => ` '${l.code}': '${l.name}'`).join(",\n");
1451
+ return `/**
1452
+ * Strapi Locales
1453
+ * Generated by strapi-integrate
1454
+ */
1455
+
1456
+ /**
1457
+ * Available locale codes
1458
+ */
1459
+ export const locales = [${localeArray}] as const;
1460
+
1461
+ /**
1462
+ * Locale type - union of all available locales
1463
+ */
1464
+ export type Locale = ${localeCodes};
1465
+
1466
+ /**
1467
+ * Default locale
1468
+ */
1469
+ export const defaultLocale: Locale = '${defaultLocale}';
1470
+
1471
+ /**
1472
+ * Locale display names
1473
+ */
1474
+ export const localeNames: Record<Locale, string> = {
1475
+ ${localeNames}
1476
+ };
1477
+
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
+ }
1484
+
1485
+ /**
1486
+ * Get locale name by code
1487
+ */
1488
+ export function getLocaleName(code: Locale): string {
1489
+ return localeNames[code] || code;
1490
+ }
1491
+ `;
1492
+ }
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) {
1551
+ const isV4 = strapiVersion === "v4";
1552
+ const blocksContentType = isV4 ? `/**
1553
+ * Rich text content (Strapi v4)
1554
+ * 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';` : `/**
1561
+ * Blocks content type (Strapi v5 rich text)
1562
+ *
1563
+ * For full type support and rendering, install:
1564
+ * npm install @strapi/blocks-react-renderer
1565
+ *
1566
+ * Then re-run: npx strapi-integrate 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
+ }`;
1627
+ const v4RawResponseTypes = isV4 ? `
1628
+
1629
+ /**
1630
+ * Strapi v4 raw API response (with nested attributes)
1631
+ */
1632
+ export interface StrapiV4RawItem<T> {
1633
+ id: number;
1634
+ attributes: Omit<T, 'id'>;
1635
+ }
1636
+
1637
+ export interface StrapiV4RawResponse<T> {
1638
+ data: StrapiV4RawItem<T>;
1639
+ meta: Record<string, unknown>;
1640
+ }
1641
+
1642
+ export interface StrapiV4RawListResponse<T> {
1643
+ data: StrapiV4RawItem<T>[];
1644
+ meta: {
1645
+ pagination: StrapiPagination;
1646
+ };
1647
+ }
1648
+
1649
+ /**
1650
+ * Flatten Strapi v4 response item
1651
+ */
1652
+ export function flattenV4Response<T>(item: StrapiV4RawItem<T>): T {
1653
+ return { id: item.id, ...item.attributes } as T;
1654
+ }
1655
+
1656
+ /**
1657
+ * Flatten Strapi v4 list response
1658
+ */
1659
+ export function flattenV4ListResponse<T>(items: StrapiV4RawItem<T>[]): T[] {
1660
+ return items.map(item => flattenV4Response<T>(item));
1661
+ }` : "";
1662
+ return `/**
1663
+ * Strapi utility types
1664
+ * Generated by strapi-integrate
1665
+ * Strapi version: ${strapiVersion}
1666
+ */
1667
+
1668
+ ${mediaType}
1669
+
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
+ }
1680
+
1681
+ export interface StrapiPagination {
1682
+ page: number;
1683
+ pageSize: number;
1684
+ pageCount: number;
1685
+ total: number;
1686
+ }
1687
+
1688
+ export interface StrapiResponse<T> {
1689
+ data: T;
1690
+ meta: {
1691
+ pagination?: StrapiPagination;
1692
+ };
1693
+ }
1694
+
1695
+ export interface StrapiListResponse<T> {
1696
+ data: T[];
1697
+ meta: {
1698
+ pagination: StrapiPagination;
1699
+ };
1700
+ }
1701
+
1702
+ ${baseEntity}
1703
+ ${v4RawResponseTypes}
1704
+ ${blocksContentType}
1705
+ `;
1706
+ }
1707
+ function generateClient2(strapiVersion) {
1708
+ const isV4 = strapiVersion === "v4";
1709
+ if (isV4) {
1710
+ return `/**
1711
+ * Strapi Client (v4)
1712
+ * Generated by strapi-integrate
1713
+ */
1714
+
1715
+ import Strapi from 'strapi-sdk-js';
1716
+ import type { StrapiPagination } from './utils';
1717
+
1718
+ // 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;
1721
+
1722
+ export const strapi = new Strapi({
1723
+ url: strapiUrl,
1724
+ axiosOptions: {
1725
+ headers: strapiToken ? {
1726
+ Authorization: \`Bearer \${strapiToken}\`,
1727
+ } : {},
1728
+ },
1729
+ });
1730
+
1731
+ // Default pagination for fallback
1732
+ const defaultPagination: StrapiPagination = {
1733
+ page: 1,
1734
+ pageSize: 25,
1735
+ pageCount: 1,
1736
+ total: 0,
1737
+ };
1738
+
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
+ /**
1758
+ * Flatten a Strapi v4 response item (merges id with attributes)
1759
+ */
1760
+ function flattenItem<T>(item: StrapiV4RawItem<T>): T {
1761
+ return { id: item.id, ...item.attributes } as T;
1762
+ }
1763
+
1764
+ /**
1765
+ * Recursively flatten nested relations in Strapi v4 response
1766
+ */
1767
+ function flattenRelations<T>(data: T): T {
1768
+ if (data === null || data === undefined) return data;
1769
+ if (Array.isArray(data)) {
1770
+ return data.map(item => flattenRelations(item)) as unknown as T;
1771
+ }
1772
+ 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 } }
1776
+ if (value && typeof value === 'object' && 'data' in value) {
1777
+ const relationData = (value as { data: unknown }).data;
1778
+ if (relationData === null) {
1779
+ result[key] = null;
1780
+ } else if (Array.isArray(relationData)) {
1781
+ // To-many relation
1782
+ result[key] = relationData.map((item: StrapiV4RawItem<unknown>) =>
1783
+ flattenRelations(flattenItem(item))
1784
+ );
1785
+ } else if (typeof relationData === 'object' && 'id' in relationData && 'attributes' in relationData) {
1786
+ // To-one relation
1787
+ result[key] = flattenRelations(flattenItem(relationData as StrapiV4RawItem<unknown>));
1788
+ } else {
1789
+ result[key] = flattenRelations(value);
1790
+ }
1791
+ } else {
1792
+ result[key] = flattenRelations(value);
1793
+ }
1794
+ }
1795
+ return result as T;
1796
+ }
1797
+ return data;
1798
+ }
1799
+
1800
+ // Helper to get typed collection
1801
+ export function collection<T>(pluralName: string) {
1802
+ 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)))
1807
+ : [];
1808
+ return {
1809
+ data: flattenedData,
1810
+ meta: {
1811
+ pagination: response.meta?.pagination || defaultPagination,
1812
+ },
1813
+ };
1814
+ },
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)) };
1818
+ },
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)) };
1822
+ },
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)) };
1826
+ },
1827
+ async delete(id: number | string): Promise<void> {
1828
+ await strapi.delete(pluralName, String(id));
1829
+ },
1830
+ };
1831
+ }
1832
+
1833
+ // Helper to get typed single type
1834
+ export function single<T>(singularName: string) {
1835
+ 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)) };
1839
+ },
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)) };
1843
+ },
1844
+ async delete(): Promise<void> {
1845
+ await strapi.delete(singularName, 1 as unknown as string);
1846
+ },
1847
+ };
1848
+ }
1849
+ `;
1850
+ }
1851
+ return `/**
1852
+ * Strapi Client (v5)
1853
+ * Generated by strapi-integrate
1854
+ */
1855
+
1856
+ import Strapi from 'strapi-sdk-js';
1857
+ import type { StrapiPagination } from './utils';
1858
+
1859
+ // 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;
1862
+
1863
+ export const strapi = new Strapi({
1864
+ url: strapiUrl,
1865
+ axiosOptions: {
1866
+ headers: strapiToken ? {
1867
+ Authorization: \`Bearer \${strapiToken}\`,
1868
+ } : {},
1869
+ },
1870
+ });
1871
+
1872
+ // Default pagination for fallback
1873
+ const defaultPagination: StrapiPagination = {
1874
+ page: 1,
1875
+ pageSize: 25,
1876
+ pageCount: 1,
1877
+ total: 0,
1878
+ };
1879
+
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) {
1895
+ 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
+ },
1903
+ };
1904
+ },
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 };
1908
+ },
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 };
1912
+ },
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 };
1916
+ },
1917
+ async delete(documentId: string): Promise<void> {
1918
+ await strapi.delete(pluralName, documentId);
1919
+ },
1920
+ };
1921
+ }
1922
+
1923
+ // Helper to get typed single type
1924
+ export function single<T>(singularName: string) {
1925
+ 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 };
1929
+ },
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 };
1933
+ },
1934
+ async delete(): Promise<void> {
1935
+ await strapi.delete(singularName, 1 as unknown as string);
1936
+ },
1937
+ };
1938
+ }
1939
+ `;
1940
+ }
1941
+ function generateLocalesFile2(locales) {
1942
+ if (locales.length === 0) {
1943
+ return `/**
1944
+ * Strapi locales
1945
+ * Generated by strapi-integrate
1946
+ * Note: i18n is not enabled in Strapi
1947
+ */
1948
+
1949
+ export const locales = [] as const;
1950
+ export type Locale = string;
1951
+ export const defaultLocale: Locale = 'en';
1952
+ export const localeNames: Record<string, string> = {};
1953
+
1954
+ export function isValidLocale(_code: string): _code is Locale {
1955
+ return true;
1956
+ }
1957
+
1958
+ export function getLocaleName(code: string): string {
1959
+ return code;
1960
+ }
1961
+ `;
1962
+ }
1963
+ const localeCodes = locales.map((l) => l.code);
1964
+ const defaultLocale = locales.find((l) => l.isDefault)?.code || locales[0]?.code || "en";
1965
+ return `/**
1966
+ * Strapi locales
1967
+ * Generated by strapi-integrate
1968
+ */
1969
+
1970
+ export const locales = [${localeCodes.map((c) => `'${c}'`).join(", ")}] as const;
1971
+
1972
+ export type Locale = typeof locales[number];
1973
+
1974
+ export const defaultLocale: Locale = '${defaultLocale}';
1975
+
1976
+ export const localeNames: Record<Locale, string> = {
1977
+ ${locales.map((l) => ` '${l.code}': '${l.name}'`).join(",\n")}
1978
+ };
1979
+
1980
+ export function isValidLocale(code: string): code is Locale {
1981
+ return locales.includes(code as Locale);
1982
+ }
1983
+
1984
+ export function getLocaleName(code: Locale): string {
1985
+ return localeNames[code] || code;
1986
+ }
1987
+ `;
1988
+ }
1989
+ function generateCollectionTypes(collection, schema) {
1990
+ const typeName = toPascalCase(collection.singularName);
1991
+ const attributes = generateAttributes2(collection.attributes);
1992
+ const imports = generateTypeImports(collection.attributes, schema, "collection");
1993
+ return `/**
1994
+ * ${collection.displayName}
1995
+ * ${collection.description || ""}
1996
+ * Generated by strapi-integrate
1997
+ */
1998
+
1999
+ ${imports}
2000
+
2001
+ export interface ${typeName} extends StrapiBaseEntity {
2002
+ ${attributes}
2003
+ }
2004
+
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
+ }
2015
+ `;
2016
+ }
2017
+ function generateSingleTypes(single, schema) {
2018
+ const typeName = toPascalCase(single.singularName);
2019
+ const attributes = generateAttributes2(single.attributes);
2020
+ const imports = generateTypeImports(single.attributes, schema, "single");
2021
+ return `/**
2022
+ * ${single.displayName}
2023
+ * ${single.description || ""}
2024
+ * Generated by strapi-integrate
2025
+ */
2026
+
2027
+ ${imports}
2028
+
2029
+ export interface ${typeName} extends StrapiBaseEntity {
2030
+ ${attributes}
2031
+ }
2032
+ `;
2033
+ }
2034
+ function generateComponentTypes(component, schema) {
2035
+ const typeName = toPascalCase(component.name);
2036
+ const attributes = generateAttributes2(component.attributes);
2037
+ const imports = generateTypeImports(component.attributes, schema, "component");
2038
+ return `/**
2039
+ * ${component.displayName} component
2040
+ * Category: ${component.category}
2041
+ * ${component.description || ""}
2042
+ * Generated by strapi-integrate
2043
+ */
2044
+
2045
+ ${imports}
2046
+
2047
+ export interface ${typeName} {
2048
+ id: number;
2049
+ ${attributes}
2050
+ }
2051
+ `;
2052
+ }
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
+ for (const attr of Object.values(attributes)) {
2068
+ if (attr.type === "relation" && "target" in attr && attr.target) {
2069
+ const targetName = attr.target.split(".").pop() || "";
2070
+ if (targetName) {
2071
+ const typeName = toPascalCase(targetName);
2072
+ const fileName = toKebabCase(targetName);
2073
+ const isCollection = schema.collections.some((c) => c.singularName === targetName);
2074
+ const isSingle = schema.singles.some((s) => s.singularName === targetName);
2075
+ if (isCollection) {
2076
+ relationImports.set(typeName, `../../collections/${fileName}/types`);
2077
+ } else if (isSingle) {
2078
+ relationImports.set(typeName, `../../singles/${fileName}/types`);
2079
+ }
2080
+ }
2081
+ }
2082
+ if (attr.type === "component" && "component" in attr && attr.component) {
2083
+ const componentName = attr.component.split(".").pop() || "";
2084
+ if (componentName) {
2085
+ const typeName = toPascalCase(componentName);
2086
+ const fileName = toKebabCase(componentName);
2087
+ if (context === "component") {
2088
+ componentImports.set(typeName, `./${fileName}`);
2089
+ } else {
2090
+ componentImports.set(typeName, `../../components/${fileName}`);
2091
+ }
2092
+ }
2093
+ }
2094
+ if (attr.type === "dynamiczone" && "components" in attr && attr.components) {
2095
+ for (const comp of attr.components) {
2096
+ const componentName = comp.split(".").pop() || "";
2097
+ if (componentName) {
2098
+ const typeName = toPascalCase(componentName);
2099
+ const fileName = toKebabCase(componentName);
2100
+ if (context === "component") {
2101
+ componentImports.set(typeName, `./${fileName}`);
2102
+ } else {
2103
+ componentImports.set(typeName, `../../components/${fileName}`);
2104
+ }
2105
+ }
2106
+ }
2107
+ }
2108
+ }
2109
+ const lines = [];
2110
+ if (utilsImports.length > 0) {
2111
+ const utilsPath = context === "component" ? "../shared/utils" : "../../shared/utils";
2112
+ lines.push(`import type { ${utilsImports.join(", ")} } from '${utilsPath}';`);
2113
+ }
2114
+ for (const [typeName, importPath] of relationImports) {
2115
+ lines.push(`import type { ${typeName} } from '${importPath}';`);
2116
+ }
2117
+ for (const [typeName, importPath] of componentImports) {
2118
+ lines.push(`import type { ${typeName} } from '${importPath}';`);
2119
+ }
2120
+ return lines.join("\n");
2121
+ }
2122
+ function generateAttributes2(attributes) {
2123
+ const lines = [];
2124
+ for (const [name, attr] of Object.entries(attributes)) {
2125
+ const tsType = attributeToTsType2(attr);
2126
+ const optional = attr.required ? "" : "?";
2127
+ lines.push(` ${name}${optional}: ${tsType};`);
2128
+ }
2129
+ return lines.join("\n");
2130
+ }
2131
+ function attributeToTsType2(attr) {
2132
+ switch (attr.type) {
2133
+ case "string":
2134
+ case "text":
2135
+ case "richtext":
2136
+ case "email":
2137
+ case "password":
2138
+ case "uid":
2139
+ return "string";
2140
+ case "blocks":
2141
+ return "BlocksContent";
2142
+ case "integer":
2143
+ case "biginteger":
2144
+ case "float":
2145
+ case "decimal":
2146
+ return "number";
2147
+ case "boolean":
2148
+ return "boolean";
2149
+ case "date":
2150
+ case "time":
2151
+ case "datetime":
2152
+ case "timestamp":
2153
+ return "string";
2154
+ case "json":
2155
+ return "unknown";
2156
+ case "enumeration":
2157
+ if ("enum" in attr && attr.enum) {
2158
+ return attr.enum.map((v) => `'${v}'`).join(" | ");
2159
+ }
2160
+ return "string";
2161
+ case "media":
2162
+ if ("multiple" in attr && attr.multiple) {
2163
+ return "StrapiMedia[]";
2164
+ }
2165
+ return "StrapiMedia | null";
2166
+ case "relation":
2167
+ if ("target" in attr && attr.target) {
2168
+ const targetName = toPascalCase(attr.target.split(".").pop() || "unknown");
2169
+ const isMany = attr.relation === "oneToMany" || attr.relation === "manyToMany";
2170
+ return isMany ? `${targetName}[]` : `${targetName} | null`;
2171
+ }
2172
+ return "unknown";
2173
+ case "component":
2174
+ if ("component" in attr && attr.component) {
2175
+ const componentName = toPascalCase(attr.component.split(".").pop() || "unknown");
2176
+ if ("repeatable" in attr && attr.repeatable) {
2177
+ return `${componentName}[]`;
2178
+ }
2179
+ return `${componentName} | null`;
2180
+ }
2181
+ return "unknown";
2182
+ case "dynamiczone":
2183
+ if ("components" in attr && attr.components) {
2184
+ const types = attr.components.map((c) => toPascalCase(c.split(".").pop() || "unknown"));
2185
+ return `(${types.join(" | ")})[]`;
2186
+ }
2187
+ return "unknown[]";
2188
+ default:
2189
+ return "unknown";
2190
+ }
2191
+ }
2192
+ function generateCollectionService2(collection, strapiVersion) {
2193
+ const typeName = toPascalCase(collection.singularName);
2194
+ const serviceName = toCamelCase(collection.singularName) + "Service";
2195
+ const endpoint = collection.pluralName;
2196
+ const hasSlug = "slug" in collection.attributes;
2197
+ const { localized, draftAndPublish } = collection;
2198
+ const isV4 = strapiVersion === "v4";
2199
+ const idParam = isV4 ? "id: number" : "documentId: string";
2200
+ const idName = isV4 ? "id" : "documentId";
2201
+ const omitFields = isV4 ? "'id' | 'createdAt' | 'updatedAt' | 'publishedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt' | 'publishedAt'";
2202
+ const omitFieldsUpdate = isV4 ? "'id' | 'createdAt' | 'updatedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt'";
2203
+ const imports = [
2204
+ `import { collection } from '../../shared/client';`,
2205
+ `import type { ${typeName}, ${typeName}Filters } from './types';`,
2206
+ `import type { StrapiPagination } from '../../shared/utils';`
2207
+ ];
2208
+ if (localized) {
2209
+ imports.push(`import type { Locale } from '../../shared/locales';`);
2210
+ }
2211
+ const paginationFields = `
2212
+ /** Page number (1-indexed) - use with pageSize */
2213
+ page?: number;
2214
+ /** Number of items per page (default: 25) - use with page */
2215
+ pageSize?: number;
2216
+ /** Offset to start from (0-indexed) - use with limit */
2217
+ start?: number;
2218
+ /** Maximum number of items to return - use with start */
2219
+ limit?: number;`;
2220
+ let findManyOptionsFields = ` filters?: ${typeName}Filters;
2221
+ pagination?: {${paginationFields}
2222
+ };
2223
+ sort?: string | string[];
2224
+ populate?: string | string[] | Record<string, unknown>;`;
2225
+ let findOneOptionsFields = ` populate?: string | string[] | Record<string, unknown>;`;
2226
+ if (localized) {
2227
+ findManyOptionsFields += `
2228
+ locale?: Locale;`;
2229
+ findOneOptionsFields += `
2230
+ locale?: Locale;`;
2231
+ }
2232
+ if (draftAndPublish) {
2233
+ findManyOptionsFields += `
2234
+ status?: 'draft' | 'published';`;
2235
+ findOneOptionsFields += `
2236
+ status?: 'draft' | 'published';`;
2237
+ }
2238
+ let findParams = ` filters: options.filters,
2239
+ pagination: options.pagination,
2240
+ sort: options.sort,
2241
+ populate: options.populate,`;
2242
+ let findOneParams = ` populate: options.populate,`;
2243
+ if (localized) {
2244
+ findParams += `
2245
+ locale: options.locale,`;
2246
+ findOneParams += `
2247
+ locale: options.locale,`;
2248
+ }
2249
+ if (draftAndPublish) {
2250
+ findParams += `
2251
+ status: options.status,`;
2252
+ findOneParams += `
2253
+ status: options.status,`;
2254
+ }
2255
+ return `/**
2256
+ * ${collection.displayName} Service
2257
+ * ${collection.description || ""}
2258
+ * Generated by strapi-integrate
2259
+ * Strapi version: ${strapiVersion}
2260
+ */
2261
+
2262
+ ${imports.join("\n")}
2263
+
2264
+ export interface FindManyOptions {
2265
+ ${findManyOptionsFields}
2266
+ }
2267
+
2268
+ export interface FindOneOptions {
2269
+ ${findOneOptionsFields}
2270
+ }
2271
+
2272
+ // Create typed collection helper
2273
+ const ${toCamelCase(collection.singularName)}Collection = collection<${typeName}>('${endpoint}');
2274
+
2275
+ export const ${serviceName} = {
2276
+ async findMany(options: FindManyOptions = {}): Promise<{ data: ${typeName}[]; pagination: StrapiPagination }> {
2277
+ const response = await ${toCamelCase(collection.singularName)}Collection.find({
2278
+ ${findParams}
2279
+ });
2280
+
2281
+ return {
2282
+ data: response.data,
2283
+ pagination: response.meta.pagination,
2284
+ };
2285
+ },
2286
+
2287
+ async findAll(options: Omit<FindManyOptions, 'pagination'> = {}): Promise<${typeName}[]> {
2288
+ const allItems: ${typeName}[] = [];
2289
+ let page = 1;
2290
+ let hasMore = true;
2291
+
2292
+ while (hasMore) {
2293
+ const { data, pagination } = await this.findMany({
2294
+ ...options,
2295
+ pagination: { page, pageSize: 100 },
2296
+ });
2297
+
2298
+ allItems.push(...data);
2299
+ hasMore = page < pagination.pageCount;
2300
+ page++;
2301
+ }
2302
+
2303
+ return allItems;
2304
+ },
2305
+
2306
+ async findOne(${idParam}, options: FindOneOptions = {}): Promise<${typeName} | null> {
2307
+ try {
2308
+ const response = await ${toCamelCase(collection.singularName)}Collection.findOne(${idName}, {
2309
+ ${findOneParams}
2310
+ });
2311
+
2312
+ return response.data;
2313
+ } catch (error) {
2314
+ if (error instanceof Error && error.message.includes('404')) {
2315
+ return null;
2316
+ }
2317
+ throw error;
2318
+ }
2319
+ },
2320
+ ${hasSlug ? `
2321
+ async findBySlug(slug: string, options: FindOneOptions = {}): Promise<${typeName} | null> {
2322
+ const { data } = await this.findMany({
2323
+ filters: { slug: { $eq: slug } } as ${typeName}Filters,
2324
+ pagination: { pageSize: 1 },
2325
+ populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
2326
+ });
2327
+
2328
+ return data[0] || null;
2329
+ },
2330
+ ` : ""}
2331
+ async create(data: Partial<Omit<${typeName}, ${omitFields}>>): Promise<${typeName}> {
2332
+ const response = await ${toCamelCase(collection.singularName)}Collection.create({ data });
2333
+ return response.data;
2334
+ },
2335
+
2336
+ async update(${idParam}, data: Partial<Omit<${typeName}, ${omitFieldsUpdate}>>): Promise<${typeName}> {
2337
+ const response = await ${toCamelCase(collection.singularName)}Collection.update(${idName}, { data });
2338
+ return response.data;
2339
+ },
2340
+
2341
+ async delete(${idParam}): Promise<void> {
2342
+ await ${toCamelCase(collection.singularName)}Collection.delete(${idName});
2343
+ },
2344
+
2345
+ async count(filters?: ${typeName}Filters): Promise<number> {
2346
+ const { pagination } = await this.findMany({
2347
+ filters,
2348
+ pagination: { pageSize: 1 },
2349
+ });
2350
+
2351
+ return pagination.total;
2352
+ },
2353
+ };
2354
+ `;
2355
+ }
2356
+ function generateSingleService2(single, strapiVersion) {
2357
+ const typeName = toPascalCase(single.singularName);
2358
+ const serviceName = toCamelCase(single.singularName) + "Service";
2359
+ const endpoint = single.singularName;
2360
+ const { localized, draftAndPublish } = single;
2361
+ const isV4 = strapiVersion === "v4";
2362
+ const omitFields = isV4 ? "'id' | 'createdAt' | 'updatedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt'";
2363
+ const imports = [
2364
+ `import { single } from '../../shared/client';`,
2365
+ `import type { ${typeName} } from './types';`
2366
+ ];
2367
+ if (localized) {
2368
+ imports.push(`import type { Locale } from '../../shared/locales';`);
2369
+ }
2370
+ let findOptionsFields = ` populate?: string | string[] | Record<string, unknown>;`;
2371
+ if (localized) {
2372
+ findOptionsFields += `
2373
+ locale?: Locale;`;
2374
+ }
2375
+ if (draftAndPublish) {
2376
+ findOptionsFields += `
2377
+ status?: 'draft' | 'published';`;
2378
+ }
2379
+ let findParams = ` populate: options.populate,`;
2380
+ if (localized) {
2381
+ findParams += `
2382
+ locale: options.locale,`;
2383
+ }
2384
+ if (draftAndPublish) {
2385
+ findParams += `
2386
+ status: options.status,`;
2387
+ }
2388
+ return `/**
2389
+ * ${single.displayName} Service (Single Type)
2390
+ * ${single.description || ""}
2391
+ * Generated by strapi-integrate
2392
+ * Strapi version: ${strapiVersion}
2393
+ */
2394
+
2395
+ ${imports.join("\n")}
2396
+
2397
+ export interface FindOptions {
2398
+ ${findOptionsFields}
2399
+ }
2400
+
2401
+ // Create typed single helper
2402
+ const ${toCamelCase(single.singularName)}Single = single<${typeName}>('${endpoint}');
2403
+
2404
+ export const ${serviceName} = {
2405
+ async find(options: FindOptions = {}): Promise<${typeName} | null> {
2406
+ try {
2407
+ const response = await ${toCamelCase(single.singularName)}Single.find({
2408
+ ${findParams}
2409
+ });
2410
+
2411
+ return response.data;
2412
+ } catch (error) {
2413
+ if (error instanceof Error && error.message.includes('404')) {
2414
+ return null;
2415
+ }
2416
+ throw error;
2417
+ }
2418
+ },
2419
+
2420
+ async update(data: Partial<Omit<${typeName}, ${omitFields}>>): Promise<${typeName}> {
2421
+ const response = await ${toCamelCase(single.singularName)}Single.update({ data });
2422
+ return response.data;
2423
+ },
2424
+
2425
+ async delete(): Promise<void> {
2426
+ await ${toCamelCase(single.singularName)}Single.delete();
2427
+ },
2428
+ };
2429
+ `;
2430
+ }
2431
+ function generateCollectionActions2(collection, strapiVersion) {
2432
+ const typeName = toPascalCase(collection.singularName);
2433
+ const serviceName = toCamelCase(collection.singularName) + "Service";
2434
+ const actionPrefix = toCamelCase(collection.singularName);
2435
+ const isV4 = strapiVersion === "v4";
2436
+ const idInputSchema = isV4 ? "z.number().int().positive()" : "z.string()";
2437
+ const idParamName = isV4 ? "id" : "documentId";
2438
+ return `/**
2439
+ * ${collection.displayName} Astro Actions
2440
+ * ${collection.description || ""}
2441
+ * Generated by strapi-integrate
2442
+ * Strapi version: ${strapiVersion}
2443
+ */
2444
+
2445
+ import { defineAction } from 'astro:actions';
2446
+ import { z } from 'astro:schema';
2447
+ import { ${serviceName} } from './service';
2448
+ import type { ${typeName} } from './types';
2449
+
2450
+ export const ${actionPrefix}Actions = {
2451
+ getMany: defineAction({
2452
+ input: z.object({
2453
+ page: z.number().optional(),
2454
+ pageSize: z.number().optional(),
2455
+ sort: z.string().optional(),
2456
+ }).optional(),
2457
+ handler: async (input) => {
2458
+ const { data, pagination } = await ${serviceName}.findMany({
2459
+ pagination: input ? { page: input.page, pageSize: input.pageSize } : undefined,
2460
+ sort: input?.sort,
2461
+ });
2462
+ return { data, pagination };
2463
+ },
2464
+ }),
2465
+
2466
+ getOne: defineAction({
2467
+ input: z.object({
2468
+ ${idParamName}: ${idInputSchema},
2469
+ }),
2470
+ handler: async (input) => {
2471
+ const data = await ${serviceName}.findOne(input.${idParamName});
2472
+ return { data };
2473
+ },
2474
+ }),
2475
+
2476
+ create: defineAction({
2477
+ input: z.object({
2478
+ data: z.record(z.unknown()),
2479
+ }),
2480
+ handler: async (input) => {
2481
+ const data = await ${serviceName}.create(input.data as Partial<${typeName}>);
2482
+ return { data };
2483
+ },
2484
+ }),
2485
+
2486
+ update: defineAction({
2487
+ input: z.object({
2488
+ ${idParamName}: ${idInputSchema},
2489
+ data: z.record(z.unknown()),
2490
+ }),
2491
+ handler: async (input) => {
2492
+ const data = await ${serviceName}.update(input.${idParamName}, input.data as Partial<${typeName}>);
2493
+ return { data };
2494
+ },
2495
+ }),
2496
+
2497
+ delete: defineAction({
2498
+ input: z.object({
2499
+ ${idParamName}: ${idInputSchema},
2500
+ }),
2501
+ handler: async (input) => {
2502
+ await ${serviceName}.delete(input.${idParamName});
2503
+ return { success: true };
2504
+ },
2505
+ }),
2506
+ };
2507
+ `;
2508
+ }
2509
+
2510
+ export { deleteFile, ensureDir, fileExists, formatCode, formatJson, generateActions, generateByFeature, generateClient, generateLocales, generateServices, generateTypes, listFiles, pluralize, readFile, toCamelCase, toKebabCase, toPascalCase, writeFile };
2511
+ //# sourceMappingURL=index.js.map
2512
+ //# sourceMappingURL=index.js.map