@strapi2front/generators 0.2.0 → 0.3.1
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.d.ts +229 -5
- package/dist/index.js +2815 -1118
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import path9 from 'path';
|
|
2
2
|
import * as prettier from 'prettier';
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
4
|
|
|
5
|
-
// src/types
|
|
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 =
|
|
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) =>
|
|
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/
|
|
100
|
-
|
|
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(
|
|
104
|
-
await ensureDir(
|
|
105
|
-
const utilsPath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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}: ${
|
|
588
|
+
lines.push(` ${name}${optional}: ${mappedType.type};`);
|
|
471
589
|
}
|
|
472
590
|
return lines.join("\n");
|
|
473
591
|
}
|
|
474
|
-
function
|
|
475
|
-
|
|
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)}.
|
|
560
|
-
const filePath =
|
|
561
|
-
const content =
|
|
562
|
-
await writeFile(filePath,
|
|
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)}.
|
|
567
|
-
const filePath =
|
|
568
|
-
const content =
|
|
569
|
-
await writeFile(filePath,
|
|
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
|
|
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
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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
|
-
|
|
621
|
-
|
|
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
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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 (
|
|
633
|
-
|
|
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
|
-
|
|
636
|
-
|
|
637
|
-
];
|
|
638
|
-
if (localized) {
|
|
639
|
-
findOneParams.push(` locale: options.locale,`);
|
|
874
|
+
if (tsType.includes("'") && tsType.includes(" | ")) {
|
|
875
|
+
return "string";
|
|
640
876
|
}
|
|
641
|
-
|
|
642
|
-
|
|
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
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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
|
-
|
|
651
|
-
|
|
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
|
-
|
|
952
|
+
import { collection } from '../client.js';
|
|
661
953
|
|
|
662
|
-
|
|
663
|
-
${
|
|
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
|
-
|
|
667
|
-
|
|
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
|
-
|
|
671
|
-
|
|
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
|
|
981
|
+
async findMany(options = {}) {
|
|
678
982
|
const response = await ${toCamelCase(collection.singularName)}Collection.find({
|
|
679
|
-
|
|
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
|
|
692
|
-
|
|
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 ${
|
|
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
|
|
1026
|
+
async findOne(${idParam}, options = {}) {
|
|
714
1027
|
try {
|
|
715
|
-
const response = await ${toCamelCase(collection.singularName)}Collection.findOne(${
|
|
716
|
-
|
|
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
|
-
${
|
|
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
|
|
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
|
|
754
|
-
const response = await ${toCamelCase(collection.singularName)}Collection.update(${
|
|
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})
|
|
762
|
-
await ${toCamelCase(collection.singularName)}Collection.delete(${
|
|
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
|
|
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
|
|
788
|
-
|
|
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
|
-
|
|
1102
|
+
import { single } from '../client.js';
|
|
820
1103
|
|
|
821
|
-
|
|
822
|
-
${
|
|
823
|
-
}
|
|
1104
|
+
/** @typedef {import('${typesImportPath}/collections/${fileName}').${typeName}} ${typeName} */
|
|
1105
|
+
${localized ? `/** @typedef {import('../locales').Locale} Locale */` : ""}
|
|
824
1106
|
|
|
825
|
-
|
|
826
|
-
|
|
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
|
|
1121
|
+
async find(options = {}) {
|
|
833
1122
|
try {
|
|
834
1123
|
const response = await ${toCamelCase(single.singularName)}Single.find({
|
|
835
|
-
|
|
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
|
|
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()
|
|
1150
|
+
async delete() {
|
|
859
1151
|
await ${toCamelCase(single.singularName)}Single.delete();
|
|
860
1152
|
},
|
|
861
1153
|
};
|
|
862
1154
|
`;
|
|
863
1155
|
}
|
|
864
|
-
|
|
865
|
-
|
|
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 =
|
|
871
|
-
const content =
|
|
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 =
|
|
878
|
-
const content =
|
|
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
|
|
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
|
|
890
|
-
const
|
|
891
|
-
const
|
|
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}
|
|
1269
|
+
* ${collection.displayName} Service
|
|
895
1270
|
* ${collection.description || ""}
|
|
896
1271
|
* Generated by strapi2front
|
|
897
1272
|
* Strapi version: ${strapiVersion}
|
|
898
1273
|
*/
|
|
899
1274
|
|
|
900
|
-
|
|
901
|
-
import { z } from 'astro:schema';
|
|
902
|
-
import { ${serviceName} } from '${servicesImportPath}/${fileName}.service';
|
|
1275
|
+
${imports.join("\n")}
|
|
903
1276
|
|
|
904
|
-
|
|
905
|
-
|
|
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
|
-
|
|
914
|
-
|
|
915
|
-
|
|
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
|
-
*
|
|
1290
|
+
* Find multiple ${collection.pluralName}
|
|
918
1291
|
*/
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
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
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
});
|
|
937
|
-
}
|
|
938
|
-
},
|
|
939
|
-
}),
|
|
1297
|
+
return {
|
|
1298
|
+
data: response.data,
|
|
1299
|
+
pagination: response.meta.pagination,
|
|
1300
|
+
};
|
|
1301
|
+
},
|
|
940
1302
|
|
|
941
1303
|
/**
|
|
942
|
-
*
|
|
1304
|
+
* Find all ${collection.pluralName} (handles pagination automatically)
|
|
943
1305
|
*/
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
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
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
}
|
|
1311
|
+
while (hasMore) {
|
|
1312
|
+
const { data, pagination } = await this.findMany({
|
|
1313
|
+
...options,
|
|
1314
|
+
pagination: { page, pageSize: 100 },
|
|
1315
|
+
});
|
|
959
1316
|
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
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
|
-
*
|
|
1345
|
+
* Find one ${collection.singularName} by slug
|
|
973
1346
|
*/
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
slug:
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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
|
-
|
|
991
|
-
|
|
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:
|
|
1005
|
-
|
|
1006
|
-
|
|
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:
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
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:
|
|
1046
|
-
|
|
1047
|
-
|
|
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:
|
|
1066
|
-
|
|
1067
|
-
filters
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
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
|
|
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}
|
|
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
|
-
*
|
|
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 ${
|
|
1539
|
+
* Get all ${collection.pluralName} with pagination
|
|
1104
1540
|
*/
|
|
1105
|
-
|
|
1541
|
+
getAll: defineAction({
|
|
1106
1542
|
input: z.object({
|
|
1107
|
-
|
|
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}.
|
|
1112
|
-
|
|
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: '${
|
|
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 ${
|
|
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
|
-
*
|
|
1594
|
+
* Get a single ${collection.singularName} by slug
|
|
1135
1595
|
*/
|
|
1136
|
-
|
|
1596
|
+
getBySlug: defineAction({
|
|
1137
1597
|
input: z.object({
|
|
1138
|
-
|
|
1598
|
+
slug: z.string().min(1),
|
|
1599
|
+
populate: z.union([z.string(), z.array(z.string())]).optional(),
|
|
1139
1600
|
}),
|
|
1140
|
-
handler: async ({
|
|
1601
|
+
handler: async ({ slug, populate }) => {
|
|
1141
1602
|
try {
|
|
1142
|
-
const result = await ${serviceName}.
|
|
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,11 +1775,20 @@ 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
1788
|
const { outputDir, strapiVersion = "v5", apiPrefix = "/api" } = options;
|
|
1157
1789
|
const generatedFiles = [];
|
|
1158
1790
|
await ensureDir(outputDir);
|
|
1159
|
-
const filePath =
|
|
1791
|
+
const filePath = path9.join(outputDir, "client.ts");
|
|
1160
1792
|
const content = generateClientFile(strapiVersion, apiPrefix);
|
|
1161
1793
|
await writeFile(filePath, await formatCode(content));
|
|
1162
1794
|
generatedFiles.push(filePath);
|
|
@@ -1426,7 +2058,7 @@ async function generateLocales(locales, options) {
|
|
|
1426
2058
|
const { outputDir } = options;
|
|
1427
2059
|
const generatedFiles = [];
|
|
1428
2060
|
await ensureDir(outputDir);
|
|
1429
|
-
const filePath =
|
|
2061
|
+
const filePath = path9.join(outputDir, "locales.ts");
|
|
1430
2062
|
const content = generateLocalesFile(locales);
|
|
1431
2063
|
await writeFile(filePath, await formatCode(content));
|
|
1432
2064
|
generatedFiles.push(filePath);
|
|
@@ -1469,203 +2101,1196 @@ export const locales = [${localeArray}] as const;
|
|
|
1469
2101
|
*/
|
|
1470
2102
|
export type Locale = ${localeCodes};
|
|
1471
2103
|
|
|
1472
|
-
/**
|
|
1473
|
-
* Default locale
|
|
1474
|
-
*/
|
|
1475
|
-
export const defaultLocale: Locale = '${defaultLocale}';
|
|
2104
|
+
/**
|
|
2105
|
+
* Default locale
|
|
2106
|
+
*/
|
|
2107
|
+
export const defaultLocale: Locale = '${defaultLocale}';
|
|
2108
|
+
|
|
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
|
+
}),
|
|
3145
|
+
|
|
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
|
+
}),
|
|
1476
3156
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
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
|
+
}),
|
|
1482
3166
|
};
|
|
1483
|
-
|
|
1484
|
-
/**
|
|
1485
|
-
* Check if a string is a valid locale
|
|
1486
|
-
*/
|
|
1487
|
-
export function isValidLocale(code: string): code is Locale {
|
|
1488
|
-
return locales.includes(code as Locale);
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
/**
|
|
1492
|
-
* Get locale name by code
|
|
1493
|
-
*/
|
|
1494
|
-
export function getLocaleName(code: Locale): string {
|
|
1495
|
-
return localeNames[code] || code;
|
|
1496
|
-
}
|
|
1497
3167
|
`;
|
|
1498
3168
|
}
|
|
1499
|
-
|
|
1500
|
-
const { outputDir, features, blocksRendererInstalled = false, strapiVersion = "v5", apiPrefix = "/api" } = options;
|
|
1501
|
-
const generatedFiles = [];
|
|
1502
|
-
await ensureDir(path7.join(outputDir, "collections"));
|
|
1503
|
-
await ensureDir(path7.join(outputDir, "singles"));
|
|
1504
|
-
await ensureDir(path7.join(outputDir, "components"));
|
|
1505
|
-
await ensureDir(path7.join(outputDir, "shared"));
|
|
1506
|
-
const sharedDir = path7.join(outputDir, "shared");
|
|
1507
|
-
const utilsPath = path7.join(sharedDir, "utils.ts");
|
|
1508
|
-
await writeFile(utilsPath, await formatCode(generateUtilityTypes2(blocksRendererInstalled, strapiVersion)));
|
|
1509
|
-
generatedFiles.push(utilsPath);
|
|
1510
|
-
const clientPath = path7.join(sharedDir, "client.ts");
|
|
1511
|
-
await writeFile(clientPath, await formatCode(generateClient2(strapiVersion, apiPrefix)));
|
|
1512
|
-
generatedFiles.push(clientPath);
|
|
1513
|
-
const localesPath = path7.join(sharedDir, "locales.ts");
|
|
1514
|
-
await writeFile(localesPath, await formatCode(generateLocalesFile2(locales)));
|
|
1515
|
-
generatedFiles.push(localesPath);
|
|
1516
|
-
for (const collection of schema.collections) {
|
|
1517
|
-
const featureDir = path7.join(outputDir, "collections", toKebabCase(collection.singularName));
|
|
1518
|
-
await ensureDir(featureDir);
|
|
1519
|
-
if (features.types) {
|
|
1520
|
-
const typesPath = path7.join(featureDir, "types.ts");
|
|
1521
|
-
await writeFile(typesPath, await formatCode(generateCollectionTypes(collection, schema)));
|
|
1522
|
-
generatedFiles.push(typesPath);
|
|
1523
|
-
}
|
|
1524
|
-
if (features.services) {
|
|
1525
|
-
const servicePath = path7.join(featureDir, "service.ts");
|
|
1526
|
-
await writeFile(servicePath, await formatCode(generateCollectionService2(collection, strapiVersion)));
|
|
1527
|
-
generatedFiles.push(servicePath);
|
|
1528
|
-
}
|
|
1529
|
-
if (features.actions) {
|
|
1530
|
-
const actionsPath = path7.join(featureDir, "actions.ts");
|
|
1531
|
-
await writeFile(actionsPath, await formatCode(generateCollectionActions2(collection, strapiVersion)));
|
|
1532
|
-
generatedFiles.push(actionsPath);
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
for (const single of schema.singles) {
|
|
1536
|
-
const featureDir = path7.join(outputDir, "singles", toKebabCase(single.singularName));
|
|
1537
|
-
await ensureDir(featureDir);
|
|
1538
|
-
if (features.types) {
|
|
1539
|
-
const typesPath = path7.join(featureDir, "types.ts");
|
|
1540
|
-
await writeFile(typesPath, await formatCode(generateSingleTypes(single, schema)));
|
|
1541
|
-
generatedFiles.push(typesPath);
|
|
1542
|
-
}
|
|
1543
|
-
if (features.services) {
|
|
1544
|
-
const servicePath = path7.join(featureDir, "service.ts");
|
|
1545
|
-
await writeFile(servicePath, await formatCode(generateSingleService2(single, strapiVersion)));
|
|
1546
|
-
generatedFiles.push(servicePath);
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
for (const component of schema.components) {
|
|
1550
|
-
const componentPath = path7.join(outputDir, "components", `${toKebabCase(component.name)}.ts`);
|
|
1551
|
-
await writeFile(componentPath, await formatCode(generateComponentTypes(component, schema)));
|
|
1552
|
-
generatedFiles.push(componentPath);
|
|
1553
|
-
}
|
|
1554
|
-
return generatedFiles;
|
|
1555
|
-
}
|
|
1556
|
-
function generateUtilityTypes2(blocksRendererInstalled, strapiVersion) {
|
|
3169
|
+
function generateUtilityTypesJSDoc(blocksRendererInstalled, strapiVersion) {
|
|
1557
3170
|
const isV4 = strapiVersion === "v4";
|
|
1558
3171
|
const blocksContentType = isV4 ? `/**
|
|
1559
3172
|
* Rich text content (Strapi v4)
|
|
1560
3173
|
* Can be markdown string or custom JSON structure
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
* Blocks content type (Strapi v5 rich text)
|
|
1564
|
-
* Re-exported from @strapi/blocks-react-renderer
|
|
1565
|
-
*/
|
|
1566
|
-
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` : `/**
|
|
1567
3176
|
* Blocks content type (Strapi v5 rich text)
|
|
1568
3177
|
*
|
|
1569
3178
|
* For full type support and rendering, install:
|
|
1570
3179
|
* npm install @strapi/blocks-react-renderer
|
|
1571
3180
|
*
|
|
1572
3181
|
* Then re-run: npx strapi2front sync
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
const baseEntity = isV4 ?
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
}
|
|
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
|
+
*/`;
|
|
1633
3244
|
const v4RawResponseTypes = isV4 ? `
|
|
1634
3245
|
|
|
1635
3246
|
/**
|
|
1636
|
-
* 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
|
|
1637
3252
|
*/
|
|
1638
|
-
export interface StrapiV4RawItem<T> {
|
|
1639
|
-
id: number;
|
|
1640
|
-
attributes: Omit<T, 'id'>;
|
|
1641
|
-
}
|
|
1642
3253
|
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
}
|
|
3254
|
+
/**
|
|
3255
|
+
* Strapi v4 raw API response
|
|
3256
|
+
* @template T
|
|
3257
|
+
* @typedef {Object} StrapiV4RawResponse
|
|
3258
|
+
* @property {StrapiV4RawItem<T>} data
|
|
3259
|
+
* @property {Object} meta
|
|
3260
|
+
*/
|
|
1647
3261
|
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
}
|
|
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
|
+
*/
|
|
1654
3270
|
|
|
1655
3271
|
/**
|
|
1656
3272
|
* Flatten Strapi v4 response item
|
|
3273
|
+
* @template T
|
|
3274
|
+
* @param {StrapiV4RawItem<T>} item
|
|
3275
|
+
* @returns {T}
|
|
1657
3276
|
*/
|
|
1658
|
-
|
|
1659
|
-
return { id: item.id, ...item.attributes }
|
|
3277
|
+
function flattenV4Response(item) {
|
|
3278
|
+
return { id: item.id, ...item.attributes };
|
|
1660
3279
|
}
|
|
1661
3280
|
|
|
1662
3281
|
/**
|
|
1663
3282
|
* Flatten Strapi v4 list response
|
|
3283
|
+
* @template T
|
|
3284
|
+
* @param {StrapiV4RawItem<T>[]} items
|
|
3285
|
+
* @returns {T[]}
|
|
1664
3286
|
*/
|
|
1665
|
-
|
|
1666
|
-
return items.map(item => flattenV4Response
|
|
1667
|
-
}
|
|
1668
|
-
|
|
3287
|
+
function flattenV4ListResponse(items) {
|
|
3288
|
+
return items.map(item => flattenV4Response(item));
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
module.exports = { flattenV4Response, flattenV4ListResponse };` : "";
|
|
3292
|
+
return `// @ts-check
|
|
3293
|
+
/**
|
|
1669
3294
|
* Strapi utility types
|
|
1670
3295
|
* Generated by strapi2front
|
|
1671
3296
|
* Strapi version: ${strapiVersion}
|
|
@@ -1673,61 +3298,67 @@ export function flattenV4ListResponse<T>(items: StrapiV4RawItem<T>[]): T[] {
|
|
|
1673
3298
|
|
|
1674
3299
|
${mediaType}
|
|
1675
3300
|
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
}
|
|
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
|
+
*/
|
|
1686
3312
|
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
}
|
|
3313
|
+
/**
|
|
3314
|
+
* @typedef {Object} StrapiPagination
|
|
3315
|
+
* @property {number} page
|
|
3316
|
+
* @property {number} pageSize
|
|
3317
|
+
* @property {number} pageCount
|
|
3318
|
+
* @property {number} total
|
|
3319
|
+
*/
|
|
1693
3320
|
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
}
|
|
3321
|
+
/**
|
|
3322
|
+
* @template T
|
|
3323
|
+
* @typedef {Object} StrapiResponse
|
|
3324
|
+
* @property {T} data
|
|
3325
|
+
* @property {Object} meta
|
|
3326
|
+
* @property {StrapiPagination} [meta.pagination]
|
|
3327
|
+
*/
|
|
1700
3328
|
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
}
|
|
3329
|
+
/**
|
|
3330
|
+
* @template T
|
|
3331
|
+
* @typedef {Object} StrapiListResponse
|
|
3332
|
+
* @property {T[]} data
|
|
3333
|
+
* @property {Object} meta
|
|
3334
|
+
* @property {StrapiPagination} meta.pagination
|
|
3335
|
+
*/
|
|
1707
3336
|
|
|
1708
3337
|
${baseEntity}
|
|
1709
3338
|
${v4RawResponseTypes}
|
|
1710
3339
|
${blocksContentType}
|
|
3340
|
+
|
|
3341
|
+
module.exports = {};
|
|
1711
3342
|
`;
|
|
1712
3343
|
}
|
|
1713
|
-
function
|
|
3344
|
+
function generateClientJSDoc(strapiVersion, apiPrefix = "/api") {
|
|
1714
3345
|
const isV4 = strapiVersion === "v4";
|
|
1715
3346
|
const normalizedPrefix = apiPrefix.startsWith("/") ? apiPrefix : "/" + apiPrefix;
|
|
1716
3347
|
if (isV4) {
|
|
1717
|
-
return
|
|
3348
|
+
return `// @ts-check
|
|
3349
|
+
/**
|
|
1718
3350
|
* Strapi Client (v4)
|
|
1719
3351
|
* Generated by strapi2front
|
|
1720
3352
|
*/
|
|
1721
3353
|
|
|
1722
|
-
|
|
1723
|
-
import type { StrapiPagination } from './utils';
|
|
3354
|
+
const Strapi = require('strapi-sdk-js').default;
|
|
1724
3355
|
|
|
1725
3356
|
// Initialize the Strapi client
|
|
1726
|
-
const strapiUrl =
|
|
1727
|
-
const strapiToken =
|
|
1728
|
-
const strapiApiPrefix =
|
|
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}';
|
|
1729
3360
|
|
|
1730
|
-
|
|
3361
|
+
const strapi = new Strapi({
|
|
1731
3362
|
url: strapiUrl,
|
|
1732
3363
|
prefix: strapiApiPrefix,
|
|
1733
3364
|
axiosOptions: {
|
|
@@ -1737,63 +3368,55 @@ export const strapi = new Strapi({
|
|
|
1737
3368
|
},
|
|
1738
3369
|
});
|
|
1739
3370
|
|
|
1740
|
-
|
|
1741
|
-
const defaultPagination
|
|
3371
|
+
/** @type {import('./utils').StrapiPagination} */
|
|
3372
|
+
const defaultPagination = {
|
|
1742
3373
|
page: 1,
|
|
1743
3374
|
pageSize: 25,
|
|
1744
3375
|
pageCount: 1,
|
|
1745
3376
|
total: 0,
|
|
1746
3377
|
};
|
|
1747
3378
|
|
|
1748
|
-
// Strapi v4 raw response types (with nested attributes)
|
|
1749
|
-
interface StrapiV4RawItem<T> {
|
|
1750
|
-
id: number;
|
|
1751
|
-
attributes: Omit<T, 'id'>;
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1754
|
-
interface StrapiV4RawListResponse<T> {
|
|
1755
|
-
data: StrapiV4RawItem<T>[];
|
|
1756
|
-
meta: {
|
|
1757
|
-
pagination?: StrapiPagination;
|
|
1758
|
-
};
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
interface StrapiV4RawSingleResponse<T> {
|
|
1762
|
-
data: StrapiV4RawItem<T>;
|
|
1763
|
-
meta?: Record<string, unknown>;
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
3379
|
/**
|
|
1767
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}
|
|
1768
3384
|
*/
|
|
1769
|
-
function flattenItem
|
|
1770
|
-
|
|
3385
|
+
function flattenItem(item) {
|
|
3386
|
+
/** @type {any} */
|
|
3387
|
+
const merged = { id: item.id, ...item.attributes };
|
|
3388
|
+
return merged;
|
|
1771
3389
|
}
|
|
1772
3390
|
|
|
1773
3391
|
/**
|
|
1774
3392
|
* Recursively flatten nested relations in Strapi v4 response
|
|
3393
|
+
* @template T
|
|
3394
|
+
* @param {T} data
|
|
3395
|
+
* @returns {T}
|
|
1775
3396
|
*/
|
|
1776
|
-
function flattenRelations
|
|
3397
|
+
function flattenRelations(data) {
|
|
1777
3398
|
if (data === null || data === undefined) return data;
|
|
1778
3399
|
if (Array.isArray(data)) {
|
|
1779
|
-
|
|
3400
|
+
/** @type {any} */
|
|
3401
|
+
const mapped = data.map(item => flattenRelations(item));
|
|
3402
|
+
return mapped;
|
|
1780
3403
|
}
|
|
1781
3404
|
if (typeof data === 'object') {
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
3405
|
+
/** @type {Record<string, unknown>} */
|
|
3406
|
+
const result = {};
|
|
3407
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1785
3408
|
if (value && typeof value === 'object' && 'data' in value) {
|
|
1786
|
-
|
|
3409
|
+
/** @type {any} */
|
|
3410
|
+
const wrapper = value;
|
|
3411
|
+
const relationData = wrapper.data;
|
|
1787
3412
|
if (relationData === null) {
|
|
1788
3413
|
result[key] = null;
|
|
1789
3414
|
} else if (Array.isArray(relationData)) {
|
|
1790
|
-
|
|
1791
|
-
result[key] = relationData.map((item: StrapiV4RawItem<unknown>) =>
|
|
3415
|
+
result[key] = relationData.map((item) =>
|
|
1792
3416
|
flattenRelations(flattenItem(item))
|
|
1793
3417
|
);
|
|
1794
|
-
} else if (typeof relationData === 'object' && 'id' in relationData && 'attributes' in relationData) {
|
|
1795
|
-
|
|
1796
|
-
result[key] = flattenRelations(flattenItem(relationData as StrapiV4RawItem<unknown>));
|
|
3418
|
+
} else if (typeof relationData === 'object' && relationData !== null && 'id' in relationData && 'attributes' in relationData) {
|
|
3419
|
+
result[key] = flattenRelations(flattenItem(relationData));
|
|
1797
3420
|
} else {
|
|
1798
3421
|
result[key] = flattenRelations(value);
|
|
1799
3422
|
}
|
|
@@ -1801,76 +3424,150 @@ function flattenRelations<T>(data: T): T {
|
|
|
1801
3424
|
result[key] = flattenRelations(value);
|
|
1802
3425
|
}
|
|
1803
3426
|
}
|
|
1804
|
-
|
|
3427
|
+
/** @type {any} */
|
|
3428
|
+
const typed = result;
|
|
3429
|
+
return typed;
|
|
1805
3430
|
}
|
|
1806
3431
|
return data;
|
|
1807
3432
|
}
|
|
1808
3433
|
|
|
1809
|
-
|
|
1810
|
-
|
|
3434
|
+
/**
|
|
3435
|
+
* Helper to get typed collection
|
|
3436
|
+
* @template T
|
|
3437
|
+
* @param {string} pluralName
|
|
3438
|
+
*/
|
|
3439
|
+
function collection(pluralName) {
|
|
1811
3440
|
return {
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
3441
|
+
/**
|
|
3442
|
+
* @param {Record<string, unknown>} [params]
|
|
3443
|
+
* @returns {Promise<{ data: T[], meta: { pagination: import('./utils').StrapiPagination } }>}
|
|
3444
|
+
*/
|
|
3445
|
+
async find(params) {
|
|
3446
|
+
const response = await strapi.find(pluralName, params);
|
|
3447
|
+
/** @type {any} */
|
|
3448
|
+
const rawData = response.data;
|
|
3449
|
+
/** @type {T[]} */
|
|
3450
|
+
const data = Array.isArray(rawData)
|
|
3451
|
+
? rawData.map(item => flattenRelations(flattenItem(item)))
|
|
1816
3452
|
: [];
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
3453
|
+
/** @type {any} */
|
|
3454
|
+
const rawMeta = response.meta;
|
|
3455
|
+
/** @type {any} */
|
|
3456
|
+
const rawPag = rawMeta?.pagination;
|
|
3457
|
+
/** @type {import('./utils').StrapiPagination} */
|
|
3458
|
+
const pagination = {
|
|
3459
|
+
page: rawPag?.page ?? defaultPagination.page,
|
|
3460
|
+
pageSize: rawPag?.pageSize ?? defaultPagination.pageSize,
|
|
3461
|
+
pageCount: rawPag?.pageCount ?? defaultPagination.pageCount,
|
|
3462
|
+
total: rawPag?.total ?? defaultPagination.total,
|
|
1822
3463
|
};
|
|
3464
|
+
return { data, meta: { pagination } };
|
|
1823
3465
|
},
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
3466
|
+
/**
|
|
3467
|
+
* @param {number|string} id
|
|
3468
|
+
* @param {Record<string, unknown>} [params]
|
|
3469
|
+
* @returns {Promise<{ data: T }>}
|
|
3470
|
+
*/
|
|
3471
|
+
async findOne(id, params) {
|
|
3472
|
+
const response = await strapi.findOne(pluralName, String(id), params);
|
|
3473
|
+
/** @type {any} */
|
|
3474
|
+
const rawData = response.data;
|
|
3475
|
+
/** @type {T} */
|
|
3476
|
+
const data = flattenRelations(flattenItem(rawData));
|
|
3477
|
+
return { data };
|
|
1827
3478
|
},
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
3479
|
+
/**
|
|
3480
|
+
* @param {{ data: Partial<T> }} data
|
|
3481
|
+
* @returns {Promise<{ data: T }>}
|
|
3482
|
+
*/
|
|
3483
|
+
async create(data) {
|
|
3484
|
+
const response = await strapi.create(pluralName, data.data);
|
|
3485
|
+
/** @type {any} */
|
|
3486
|
+
const rawData = response.data;
|
|
3487
|
+
/** @type {T} */
|
|
3488
|
+
const result = flattenRelations(flattenItem(rawData));
|
|
3489
|
+
return { data: result };
|
|
1831
3490
|
},
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
3491
|
+
/**
|
|
3492
|
+
* @param {number|string} id
|
|
3493
|
+
* @param {{ data: Partial<T> }} data
|
|
3494
|
+
* @returns {Promise<{ data: T }>}
|
|
3495
|
+
*/
|
|
3496
|
+
async update(id, data) {
|
|
3497
|
+
const response = await strapi.update(pluralName, String(id), data.data);
|
|
3498
|
+
/** @type {any} */
|
|
3499
|
+
const rawData = response.data;
|
|
3500
|
+
/** @type {T} */
|
|
3501
|
+
const result = flattenRelations(flattenItem(rawData));
|
|
3502
|
+
return { data: result };
|
|
1835
3503
|
},
|
|
1836
|
-
|
|
3504
|
+
/**
|
|
3505
|
+
* @param {number|string} id
|
|
3506
|
+
* @returns {Promise<void>}
|
|
3507
|
+
*/
|
|
3508
|
+
async delete(id) {
|
|
1837
3509
|
await strapi.delete(pluralName, String(id));
|
|
1838
3510
|
},
|
|
1839
3511
|
};
|
|
1840
3512
|
}
|
|
1841
3513
|
|
|
1842
|
-
|
|
1843
|
-
|
|
3514
|
+
/**
|
|
3515
|
+
* Helper to get typed single type
|
|
3516
|
+
* @template T
|
|
3517
|
+
* @param {string} singularName
|
|
3518
|
+
*/
|
|
3519
|
+
function single(singularName) {
|
|
1844
3520
|
return {
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
3521
|
+
/**
|
|
3522
|
+
* @param {Record<string, unknown>} [params]
|
|
3523
|
+
* @returns {Promise<{ data: T }>}
|
|
3524
|
+
*/
|
|
3525
|
+
async find(params) {
|
|
3526
|
+
const response = await strapi.find(singularName, params);
|
|
3527
|
+
/** @type {any} */
|
|
3528
|
+
const rawData = response.data;
|
|
3529
|
+
/** @type {T} */
|
|
3530
|
+
const data = flattenRelations(flattenItem(rawData));
|
|
3531
|
+
return { data };
|
|
1848
3532
|
},
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
3533
|
+
/**
|
|
3534
|
+
* @param {{ data: Partial<T> }} data
|
|
3535
|
+
* @returns {Promise<{ data: T }>}
|
|
3536
|
+
*/
|
|
3537
|
+
async update(data) {
|
|
3538
|
+
const response = await strapi.update(singularName, String(1), data.data);
|
|
3539
|
+
/** @type {any} */
|
|
3540
|
+
const rawData = response.data;
|
|
3541
|
+
/** @type {T} */
|
|
3542
|
+
const result = flattenRelations(flattenItem(rawData));
|
|
3543
|
+
return { data: result };
|
|
1852
3544
|
},
|
|
1853
|
-
|
|
1854
|
-
|
|
3545
|
+
/**
|
|
3546
|
+
* @returns {Promise<void>}
|
|
3547
|
+
*/
|
|
3548
|
+
async delete() {
|
|
3549
|
+
await strapi.delete(singularName, String(1));
|
|
1855
3550
|
},
|
|
1856
3551
|
};
|
|
1857
3552
|
}
|
|
3553
|
+
|
|
3554
|
+
module.exports = { strapi, collection, single };
|
|
1858
3555
|
`;
|
|
1859
3556
|
}
|
|
1860
|
-
return
|
|
3557
|
+
return `// @ts-check
|
|
3558
|
+
/**
|
|
1861
3559
|
* Strapi Client (v5)
|
|
1862
3560
|
* Generated by strapi2front
|
|
1863
3561
|
*/
|
|
1864
3562
|
|
|
1865
|
-
|
|
1866
|
-
import type { StrapiPagination } from './utils';
|
|
3563
|
+
const Strapi = require('strapi-sdk-js').default;
|
|
1867
3564
|
|
|
1868
3565
|
// Initialize the Strapi client
|
|
1869
|
-
const strapiUrl =
|
|
1870
|
-
const strapiToken =
|
|
1871
|
-
const strapiApiPrefix =
|
|
3566
|
+
const strapiUrl = process.env.STRAPI_URL || 'http://localhost:1337';
|
|
3567
|
+
const strapiToken = process.env.STRAPI_TOKEN;
|
|
3568
|
+
const strapiApiPrefix = process.env.STRAPI_API_PREFIX || '${normalizedPrefix}';
|
|
1872
3569
|
|
|
1873
|
-
|
|
3570
|
+
const strapi = new Strapi({
|
|
1874
3571
|
url: strapiUrl,
|
|
1875
3572
|
prefix: strapiApiPrefix,
|
|
1876
3573
|
axiosOptions: {
|
|
@@ -1880,267 +3577,304 @@ export const strapi = new Strapi({
|
|
|
1880
3577
|
},
|
|
1881
3578
|
});
|
|
1882
3579
|
|
|
1883
|
-
|
|
1884
|
-
const defaultPagination
|
|
3580
|
+
/** @type {import('./utils').StrapiPagination} */
|
|
3581
|
+
const defaultPagination = {
|
|
1885
3582
|
page: 1,
|
|
1886
3583
|
pageSize: 25,
|
|
1887
3584
|
pageCount: 1,
|
|
1888
3585
|
total: 0,
|
|
1889
3586
|
};
|
|
1890
3587
|
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
interface StrapiSingleResponse<T> {
|
|
1900
|
-
data: T;
|
|
1901
|
-
meta?: Record<string, unknown>;
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
|
-
// Helper to get typed collection
|
|
1905
|
-
export function collection<T>(pluralName: string) {
|
|
3588
|
+
/**
|
|
3589
|
+
* Helper to get typed collection
|
|
3590
|
+
* @template T
|
|
3591
|
+
* @param {string} pluralName
|
|
3592
|
+
*/
|
|
3593
|
+
function collection(pluralName) {
|
|
1906
3594
|
return {
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
3595
|
+
/**
|
|
3596
|
+
* @param {Record<string, unknown>} [params]
|
|
3597
|
+
* @returns {Promise<{ data: T[], meta: { pagination: import('./utils').StrapiPagination } }>}
|
|
3598
|
+
*/
|
|
3599
|
+
async find(params) {
|
|
3600
|
+
const response = await strapi.find(pluralName, params);
|
|
3601
|
+
/** @type {any} */
|
|
3602
|
+
const rawMeta = response.meta;
|
|
3603
|
+
/** @type {any} */
|
|
3604
|
+
const rawPag = rawMeta?.pagination;
|
|
3605
|
+
/** @type {import('./utils').StrapiPagination} */
|
|
3606
|
+
const pagination = {
|
|
3607
|
+
page: rawPag?.page ?? defaultPagination.page,
|
|
3608
|
+
pageSize: rawPag?.pageSize ?? defaultPagination.pageSize,
|
|
3609
|
+
pageCount: rawPag?.pageCount ?? defaultPagination.pageCount,
|
|
3610
|
+
total: rawPag?.total ?? defaultPagination.total,
|
|
1914
3611
|
};
|
|
3612
|
+
/** @type {any} */
|
|
3613
|
+
const rawData = response.data;
|
|
3614
|
+
/** @type {T[]} */
|
|
3615
|
+
const data = Array.isArray(rawData) ? rawData : [];
|
|
3616
|
+
return { data, meta: { pagination } };
|
|
1915
3617
|
},
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
3618
|
+
/**
|
|
3619
|
+
* @param {string} documentId
|
|
3620
|
+
* @param {Record<string, unknown>} [params]
|
|
3621
|
+
* @returns {Promise<{ data: T }>}
|
|
3622
|
+
*/
|
|
3623
|
+
async findOne(documentId, params) {
|
|
3624
|
+
const response = await strapi.findOne(pluralName, documentId, params);
|
|
3625
|
+
/** @type {any} */
|
|
3626
|
+
const rawData = response.data;
|
|
3627
|
+
/** @type {T} */
|
|
3628
|
+
const data = rawData;
|
|
3629
|
+
return { data };
|
|
1919
3630
|
},
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
3631
|
+
/**
|
|
3632
|
+
* @param {{ data: Partial<T> }} data
|
|
3633
|
+
* @returns {Promise<{ data: T }>}
|
|
3634
|
+
*/
|
|
3635
|
+
async create(data) {
|
|
3636
|
+
const response = await strapi.create(pluralName, data.data);
|
|
3637
|
+
/** @type {any} */
|
|
3638
|
+
const rawData = response.data;
|
|
3639
|
+
/** @type {T} */
|
|
3640
|
+
const result = rawData;
|
|
3641
|
+
return { data: result };
|
|
1923
3642
|
},
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
3643
|
+
/**
|
|
3644
|
+
* @param {string} documentId
|
|
3645
|
+
* @param {{ data: Partial<T> }} data
|
|
3646
|
+
* @returns {Promise<{ data: T }>}
|
|
3647
|
+
*/
|
|
3648
|
+
async update(documentId, data) {
|
|
3649
|
+
const response = await strapi.update(pluralName, documentId, data.data);
|
|
3650
|
+
/** @type {any} */
|
|
3651
|
+
const rawData = response.data;
|
|
3652
|
+
/** @type {T} */
|
|
3653
|
+
const result = rawData;
|
|
3654
|
+
return { data: result };
|
|
1927
3655
|
},
|
|
1928
|
-
|
|
3656
|
+
/**
|
|
3657
|
+
* @param {string} documentId
|
|
3658
|
+
* @returns {Promise<void>}
|
|
3659
|
+
*/
|
|
3660
|
+
async delete(documentId) {
|
|
1929
3661
|
await strapi.delete(pluralName, documentId);
|
|
1930
3662
|
},
|
|
1931
3663
|
};
|
|
1932
3664
|
}
|
|
1933
3665
|
|
|
1934
|
-
|
|
1935
|
-
|
|
3666
|
+
/**
|
|
3667
|
+
* Helper to get typed single type
|
|
3668
|
+
* @template T
|
|
3669
|
+
* @param {string} singularName
|
|
3670
|
+
*/
|
|
3671
|
+
function single(singularName) {
|
|
1936
3672
|
return {
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
3673
|
+
/**
|
|
3674
|
+
* @param {Record<string, unknown>} [params]
|
|
3675
|
+
* @returns {Promise<{ data: T }>}
|
|
3676
|
+
*/
|
|
3677
|
+
async find(params) {
|
|
3678
|
+
const response = await strapi.find(singularName, params);
|
|
3679
|
+
/** @type {any} */
|
|
3680
|
+
const rawData = response.data;
|
|
3681
|
+
/** @type {T} */
|
|
3682
|
+
const data = rawData;
|
|
3683
|
+
return { data };
|
|
1940
3684
|
},
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
3685
|
+
/**
|
|
3686
|
+
* @param {{ data: Partial<T> }} data
|
|
3687
|
+
* @returns {Promise<{ data: T }>}
|
|
3688
|
+
*/
|
|
3689
|
+
async update(data) {
|
|
3690
|
+
const response = await strapi.update(singularName, String(1), data.data);
|
|
3691
|
+
/** @type {any} */
|
|
3692
|
+
const rawData = response.data;
|
|
3693
|
+
/** @type {T} */
|
|
3694
|
+
const result = rawData;
|
|
3695
|
+
return { data: result };
|
|
1944
3696
|
},
|
|
1945
|
-
|
|
1946
|
-
|
|
3697
|
+
/**
|
|
3698
|
+
* @returns {Promise<void>}
|
|
3699
|
+
*/
|
|
3700
|
+
async delete() {
|
|
3701
|
+
await strapi.delete(singularName, String(1));
|
|
1947
3702
|
},
|
|
1948
3703
|
};
|
|
1949
3704
|
}
|
|
3705
|
+
|
|
3706
|
+
module.exports = { strapi, collection, single };
|
|
1950
3707
|
`;
|
|
1951
3708
|
}
|
|
1952
|
-
function
|
|
3709
|
+
function generateLocalesFileJSDoc(locales) {
|
|
1953
3710
|
if (locales.length === 0) {
|
|
1954
|
-
return
|
|
3711
|
+
return `// @ts-check
|
|
3712
|
+
/**
|
|
1955
3713
|
* Strapi locales
|
|
1956
3714
|
* Generated by strapi2front
|
|
1957
3715
|
* Note: i18n is not enabled in Strapi
|
|
1958
3716
|
*/
|
|
1959
3717
|
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
export const defaultLocale: Locale = 'en';
|
|
1963
|
-
export const localeNames: Record<string, string> = {};
|
|
3718
|
+
/** @type {readonly string[]} */
|
|
3719
|
+
const locales = [];
|
|
1964
3720
|
|
|
1965
|
-
|
|
3721
|
+
/** @typedef {string} Locale */
|
|
3722
|
+
|
|
3723
|
+
/** @type {string} */
|
|
3724
|
+
const defaultLocale = 'en';
|
|
3725
|
+
|
|
3726
|
+
/** @type {Record<string, string>} */
|
|
3727
|
+
const localeNames = {};
|
|
3728
|
+
|
|
3729
|
+
/**
|
|
3730
|
+
* @param {string} _code
|
|
3731
|
+
* @returns {boolean}
|
|
3732
|
+
*/
|
|
3733
|
+
function isValidLocale(_code) {
|
|
1966
3734
|
return true;
|
|
1967
3735
|
}
|
|
1968
3736
|
|
|
1969
|
-
|
|
3737
|
+
/**
|
|
3738
|
+
* @param {string} code
|
|
3739
|
+
* @returns {string}
|
|
3740
|
+
*/
|
|
3741
|
+
function getLocaleName(code) {
|
|
1970
3742
|
return code;
|
|
1971
3743
|
}
|
|
3744
|
+
|
|
3745
|
+
module.exports = { locales, defaultLocale, localeNames, isValidLocale, getLocaleName };
|
|
1972
3746
|
`;
|
|
1973
3747
|
}
|
|
1974
3748
|
const localeCodes = locales.map((l) => l.code);
|
|
1975
3749
|
const defaultLocale = locales.find((l) => l.isDefault)?.code || locales[0]?.code || "en";
|
|
1976
|
-
return
|
|
3750
|
+
return `// @ts-check
|
|
3751
|
+
/**
|
|
1977
3752
|
* Strapi locales
|
|
1978
3753
|
* Generated by strapi2front
|
|
1979
3754
|
*/
|
|
1980
3755
|
|
|
1981
|
-
|
|
3756
|
+
/** @type {readonly [${localeCodes.map((c) => `'${c}'`).join(", ")}]} */
|
|
3757
|
+
const locales = [${localeCodes.map((c) => `'${c}'`).join(", ")}];
|
|
1982
3758
|
|
|
1983
|
-
|
|
3759
|
+
/** @typedef {${localeCodes.map((c) => `'${c}'`).join(" | ")}} Locale */
|
|
1984
3760
|
|
|
1985
|
-
|
|
3761
|
+
/** @type {Locale} */
|
|
3762
|
+
const defaultLocale = '${defaultLocale}';
|
|
1986
3763
|
|
|
1987
|
-
|
|
3764
|
+
/** @type {Record<Locale, string>} */
|
|
3765
|
+
const localeNames = {
|
|
1988
3766
|
${locales.map((l) => ` '${l.code}': '${l.name}'`).join(",\n")}
|
|
1989
3767
|
};
|
|
1990
3768
|
|
|
1991
|
-
|
|
1992
|
-
|
|
3769
|
+
/**
|
|
3770
|
+
* @param {string} code
|
|
3771
|
+
* @returns {boolean}
|
|
3772
|
+
*/
|
|
3773
|
+
function isValidLocale(code) {
|
|
3774
|
+
/** @type {readonly string[]} */
|
|
3775
|
+
const localeList = locales;
|
|
3776
|
+
return localeList.includes(code);
|
|
1993
3777
|
}
|
|
1994
3778
|
|
|
1995
|
-
|
|
3779
|
+
/**
|
|
3780
|
+
* @param {Locale} code
|
|
3781
|
+
* @returns {string}
|
|
3782
|
+
*/
|
|
3783
|
+
function getLocaleName(code) {
|
|
1996
3784
|
return localeNames[code] || code;
|
|
1997
3785
|
}
|
|
3786
|
+
|
|
3787
|
+
module.exports = { locales, defaultLocale, localeNames, isValidLocale, getLocaleName };
|
|
1998
3788
|
`;
|
|
1999
3789
|
}
|
|
2000
|
-
function
|
|
3790
|
+
function generateCollectionTypesJSDoc(collection, schema) {
|
|
2001
3791
|
const typeName = toPascalCase(collection.singularName);
|
|
2002
|
-
const attributes =
|
|
2003
|
-
|
|
2004
|
-
|
|
3792
|
+
const attributes = generateJSDocAttributes(collection.attributes, schema, "collection");
|
|
3793
|
+
return `// @ts-check
|
|
3794
|
+
/**
|
|
2005
3795
|
* ${collection.displayName}
|
|
2006
3796
|
* ${collection.description || ""}
|
|
2007
3797
|
* Generated by strapi2front
|
|
2008
3798
|
*/
|
|
2009
3799
|
|
|
2010
|
-
|
|
3800
|
+
/**
|
|
3801
|
+
* @typedef {import('../../shared/utils').StrapiBaseEntity & ${typeName}Attributes} ${typeName}
|
|
3802
|
+
*/
|
|
2011
3803
|
|
|
2012
|
-
|
|
3804
|
+
/**
|
|
3805
|
+
* @typedef {Object} ${typeName}Attributes
|
|
2013
3806
|
${attributes}
|
|
2014
|
-
|
|
3807
|
+
*/
|
|
2015
3808
|
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
}
|
|
3809
|
+
/**
|
|
3810
|
+
* @typedef {Object} ${typeName}Filters
|
|
3811
|
+
* @property {number|{$eq?: number, $ne?: number, $in?: number[], $notIn?: number[]}} [id]
|
|
3812
|
+
* @property {string|{$eq?: string, $ne?: string}} [documentId]
|
|
3813
|
+
* @property {string|{$eq?: string, $gt?: string, $gte?: string, $lt?: string, $lte?: string}} [createdAt]
|
|
3814
|
+
* @property {string|{$eq?: string, $gt?: string, $gte?: string, $lt?: string, $lte?: string}} [updatedAt]
|
|
3815
|
+
* @property {string|null|{$eq?: string, $ne?: string, $null?: boolean}} [publishedAt]
|
|
3816
|
+
* @property {${typeName}Filters[]} [$and]
|
|
3817
|
+
* @property {${typeName}Filters[]} [$or]
|
|
3818
|
+
* @property {${typeName}Filters} [$not]
|
|
3819
|
+
*/
|
|
3820
|
+
|
|
3821
|
+
module.exports = {};
|
|
2026
3822
|
`;
|
|
2027
3823
|
}
|
|
2028
|
-
function
|
|
3824
|
+
function generateSingleTypesJSDoc(single, schema) {
|
|
2029
3825
|
const typeName = toPascalCase(single.singularName);
|
|
2030
|
-
const attributes =
|
|
2031
|
-
|
|
2032
|
-
|
|
3826
|
+
const attributes = generateJSDocAttributes(single.attributes, schema, "single");
|
|
3827
|
+
return `// @ts-check
|
|
3828
|
+
/**
|
|
2033
3829
|
* ${single.displayName}
|
|
2034
3830
|
* ${single.description || ""}
|
|
2035
3831
|
* Generated by strapi2front
|
|
2036
3832
|
*/
|
|
2037
3833
|
|
|
2038
|
-
|
|
3834
|
+
/**
|
|
3835
|
+
* @typedef {import('../../shared/utils').StrapiBaseEntity & ${typeName}Attributes} ${typeName}
|
|
3836
|
+
*/
|
|
2039
3837
|
|
|
2040
|
-
|
|
3838
|
+
/**
|
|
3839
|
+
* @typedef {Object} ${typeName}Attributes
|
|
2041
3840
|
${attributes}
|
|
2042
|
-
|
|
3841
|
+
*/
|
|
3842
|
+
|
|
3843
|
+
module.exports = {};
|
|
2043
3844
|
`;
|
|
2044
3845
|
}
|
|
2045
|
-
function
|
|
3846
|
+
function generateComponentTypesJSDoc(component, schema) {
|
|
2046
3847
|
const typeName = toPascalCase(component.name);
|
|
2047
|
-
const attributes =
|
|
2048
|
-
|
|
2049
|
-
|
|
3848
|
+
const attributes = generateJSDocAttributes(component.attributes, schema, "component");
|
|
3849
|
+
return `// @ts-check
|
|
3850
|
+
/**
|
|
2050
3851
|
* ${component.displayName} component
|
|
2051
3852
|
* Category: ${component.category}
|
|
2052
3853
|
* ${component.description || ""}
|
|
2053
3854
|
* Generated by strapi2front
|
|
2054
3855
|
*/
|
|
2055
3856
|
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
id: number;
|
|
3857
|
+
/**
|
|
3858
|
+
* @typedef {Object} ${typeName}
|
|
3859
|
+
* @property {number} id
|
|
2060
3860
|
${attributes}
|
|
2061
|
-
|
|
3861
|
+
*/
|
|
3862
|
+
|
|
3863
|
+
module.exports = {};
|
|
2062
3864
|
`;
|
|
2063
3865
|
}
|
|
2064
|
-
function
|
|
2065
|
-
const utilsImports = [];
|
|
2066
|
-
const relationImports = /* @__PURE__ */ new Map();
|
|
2067
|
-
const componentImports = /* @__PURE__ */ new Map();
|
|
2068
|
-
const attributesStr = JSON.stringify(attributes);
|
|
2069
|
-
if (context !== "component") {
|
|
2070
|
-
utilsImports.push("StrapiBaseEntity");
|
|
2071
|
-
}
|
|
2072
|
-
if (attributesStr.includes('"type":"media"')) {
|
|
2073
|
-
utilsImports.push("StrapiMedia");
|
|
2074
|
-
}
|
|
2075
|
-
if (attributesStr.includes('"type":"blocks"')) {
|
|
2076
|
-
utilsImports.push("BlocksContent");
|
|
2077
|
-
}
|
|
2078
|
-
const relativePrefix = context === "component" ? ".." : "../..";
|
|
2079
|
-
for (const attr of Object.values(attributes)) {
|
|
2080
|
-
if (attr.type === "relation" && "target" in attr && attr.target) {
|
|
2081
|
-
const targetName = attr.target.split(".").pop() || "";
|
|
2082
|
-
if (targetName) {
|
|
2083
|
-
const typeName = toPascalCase(targetName);
|
|
2084
|
-
const fileName = toKebabCase(targetName);
|
|
2085
|
-
const isCollection = schema.collections.some((c) => c.singularName === targetName);
|
|
2086
|
-
const isSingle = schema.singles.some((s) => s.singularName === targetName);
|
|
2087
|
-
if (isCollection) {
|
|
2088
|
-
relationImports.set(typeName, `${relativePrefix}/collections/${fileName}/types`);
|
|
2089
|
-
} else if (isSingle) {
|
|
2090
|
-
relationImports.set(typeName, `${relativePrefix}/singles/${fileName}/types`);
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
if (attr.type === "component" && "component" in attr && attr.component) {
|
|
2095
|
-
const componentName = attr.component.split(".").pop() || "";
|
|
2096
|
-
if (componentName) {
|
|
2097
|
-
const typeName = toPascalCase(componentName);
|
|
2098
|
-
const fileName = toKebabCase(componentName);
|
|
2099
|
-
if (context === "component") {
|
|
2100
|
-
componentImports.set(typeName, `./${fileName}`);
|
|
2101
|
-
} else {
|
|
2102
|
-
componentImports.set(typeName, `../../components/${fileName}`);
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2106
|
-
if (attr.type === "dynamiczone" && "components" in attr && attr.components) {
|
|
2107
|
-
for (const comp of attr.components) {
|
|
2108
|
-
const componentName = comp.split(".").pop() || "";
|
|
2109
|
-
if (componentName) {
|
|
2110
|
-
const typeName = toPascalCase(componentName);
|
|
2111
|
-
const fileName = toKebabCase(componentName);
|
|
2112
|
-
if (context === "component") {
|
|
2113
|
-
componentImports.set(typeName, `./${fileName}`);
|
|
2114
|
-
} else {
|
|
2115
|
-
componentImports.set(typeName, `../../components/${fileName}`);
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
}
|
|
2121
|
-
const lines = [];
|
|
2122
|
-
if (utilsImports.length > 0) {
|
|
2123
|
-
const utilsPath = context === "component" ? "../shared/utils" : "../../shared/utils";
|
|
2124
|
-
lines.push(`import type { ${utilsImports.join(", ")} } from '${utilsPath}';`);
|
|
2125
|
-
}
|
|
2126
|
-
for (const [typeName, importPath] of relationImports) {
|
|
2127
|
-
lines.push(`import type { ${typeName} } from '${importPath}';`);
|
|
2128
|
-
}
|
|
2129
|
-
for (const [typeName, importPath] of componentImports) {
|
|
2130
|
-
lines.push(`import type { ${typeName} } from '${importPath}';`);
|
|
2131
|
-
}
|
|
2132
|
-
return lines.join("\n");
|
|
2133
|
-
}
|
|
2134
|
-
function generateAttributes2(attributes) {
|
|
3866
|
+
function generateJSDocAttributes(attributes, schema, context) {
|
|
2135
3867
|
const lines = [];
|
|
3868
|
+
const relativePrefix = context === "component" ? ".." : "../..";
|
|
2136
3869
|
for (const [name, attr] of Object.entries(attributes)) {
|
|
2137
|
-
const
|
|
2138
|
-
const optional = attr.required ? "" : "
|
|
2139
|
-
|
|
3870
|
+
const jsType = attributeToJSDocType(attr, schema, relativePrefix);
|
|
3871
|
+
const optional = attr.required ? "" : "[";
|
|
3872
|
+
const optionalEnd = attr.required ? "" : "]";
|
|
3873
|
+
lines.push(` * @property {${jsType}} ${optional}${name}${optionalEnd}`);
|
|
2140
3874
|
}
|
|
2141
3875
|
return lines.join("\n");
|
|
2142
3876
|
}
|
|
2143
|
-
function
|
|
3877
|
+
function attributeToJSDocType(attr, schema, relativePrefix) {
|
|
2144
3878
|
switch (attr.type) {
|
|
2145
3879
|
case "string":
|
|
2146
3880
|
case "text":
|
|
@@ -2150,7 +3884,7 @@ function attributeToTsType2(attr) {
|
|
|
2150
3884
|
case "uid":
|
|
2151
3885
|
return "string";
|
|
2152
3886
|
case "blocks":
|
|
2153
|
-
return "
|
|
3887
|
+
return "Array<Object>";
|
|
2154
3888
|
case "integer":
|
|
2155
3889
|
case "biginteger":
|
|
2156
3890
|
case "float":
|
|
@@ -2164,130 +3898,116 @@ function attributeToTsType2(attr) {
|
|
|
2164
3898
|
case "timestamp":
|
|
2165
3899
|
return "string";
|
|
2166
3900
|
case "json":
|
|
2167
|
-
return "
|
|
3901
|
+
return "Object";
|
|
2168
3902
|
case "enumeration":
|
|
2169
3903
|
if ("enum" in attr && attr.enum) {
|
|
2170
|
-
return attr.enum.map((v) => `'${v}'`).join("
|
|
3904
|
+
return attr.enum.map((v) => `'${v}'`).join("|");
|
|
2171
3905
|
}
|
|
2172
3906
|
return "string";
|
|
2173
3907
|
case "media":
|
|
2174
3908
|
if ("multiple" in attr && attr.multiple) {
|
|
2175
|
-
return "StrapiMedia[]
|
|
3909
|
+
return `import("${relativePrefix}/shared/utils").StrapiMedia[]`;
|
|
2176
3910
|
}
|
|
2177
|
-
return "StrapiMedia
|
|
3911
|
+
return `import("${relativePrefix}/shared/utils").StrapiMedia|null`;
|
|
2178
3912
|
case "relation":
|
|
2179
3913
|
if ("target" in attr && attr.target) {
|
|
2180
|
-
const targetName =
|
|
3914
|
+
const targetName = attr.target.split(".").pop() || "unknown";
|
|
3915
|
+
const typeName = toPascalCase(targetName);
|
|
3916
|
+
const fileName = toKebabCase(targetName);
|
|
2181
3917
|
const isMany = attr.relation === "oneToMany" || attr.relation === "manyToMany";
|
|
2182
|
-
|
|
3918
|
+
const isCollection = schema.collections.some((c) => c.singularName === targetName);
|
|
3919
|
+
const isSingle = schema.singles.some((s) => s.singularName === targetName);
|
|
3920
|
+
let importPath;
|
|
3921
|
+
if (isCollection) {
|
|
3922
|
+
importPath = `${relativePrefix}/collections/${fileName}/types`;
|
|
3923
|
+
} else if (isSingle) {
|
|
3924
|
+
importPath = `${relativePrefix}/singles/${fileName}/types`;
|
|
3925
|
+
} else {
|
|
3926
|
+
importPath = `${relativePrefix}/collections/${fileName}/types`;
|
|
3927
|
+
}
|
|
3928
|
+
const importType = `import("${importPath}").${typeName}`;
|
|
3929
|
+
return isMany ? `${importType}[]` : `${importType}|null`;
|
|
2183
3930
|
}
|
|
2184
|
-
return "
|
|
3931
|
+
return "Object";
|
|
2185
3932
|
case "component":
|
|
2186
3933
|
if ("component" in attr && attr.component) {
|
|
2187
|
-
const componentName =
|
|
3934
|
+
const componentName = attr.component.split(".").pop() || "unknown";
|
|
3935
|
+
const typeName = toPascalCase(componentName);
|
|
3936
|
+
const fileName = toKebabCase(componentName);
|
|
3937
|
+
const importPath = `${relativePrefix}/components/${fileName}`;
|
|
3938
|
+
const importType = `import("${importPath}").${typeName}`;
|
|
2188
3939
|
if ("repeatable" in attr && attr.repeatable) {
|
|
2189
|
-
return `${
|
|
3940
|
+
return `${importType}[]`;
|
|
2190
3941
|
}
|
|
2191
|
-
return `${
|
|
3942
|
+
return `${importType}|null`;
|
|
2192
3943
|
}
|
|
2193
|
-
return "
|
|
3944
|
+
return "Object";
|
|
2194
3945
|
case "dynamiczone":
|
|
2195
3946
|
if ("components" in attr && attr.components) {
|
|
2196
|
-
const types = attr.components.map((
|
|
2197
|
-
|
|
3947
|
+
const types = attr.components.map((comp) => {
|
|
3948
|
+
const componentName = comp.split(".").pop() || "unknown";
|
|
3949
|
+
const typeName = toPascalCase(componentName);
|
|
3950
|
+
const fileName = toKebabCase(componentName);
|
|
3951
|
+
const importPath = `${relativePrefix}/components/${fileName}`;
|
|
3952
|
+
return `import("${importPath}").${typeName}`;
|
|
3953
|
+
});
|
|
3954
|
+
return `(${types.join("|")})[]`;
|
|
2198
3955
|
}
|
|
2199
|
-
return "
|
|
3956
|
+
return "Object[]";
|
|
2200
3957
|
default:
|
|
2201
|
-
return "
|
|
3958
|
+
return "Object";
|
|
2202
3959
|
}
|
|
2203
3960
|
}
|
|
2204
|
-
function
|
|
3961
|
+
function generateCollectionServiceJSDoc(collection, strapiVersion) {
|
|
2205
3962
|
const typeName = toPascalCase(collection.singularName);
|
|
2206
3963
|
const serviceName = toCamelCase(collection.singularName) + "Service";
|
|
2207
3964
|
const endpoint = collection.pluralName;
|
|
2208
3965
|
const hasSlug = "slug" in collection.attributes;
|
|
2209
3966
|
const { localized, draftAndPublish } = collection;
|
|
2210
3967
|
const isV4 = strapiVersion === "v4";
|
|
2211
|
-
const idParam = isV4 ? "id
|
|
2212
|
-
const
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
const imports = [
|
|
2216
|
-
`import { collection } from '../../shared/client';`,
|
|
2217
|
-
`import type { ${typeName}, ${typeName}Filters } from './types';`,
|
|
2218
|
-
`import type { StrapiPagination } from '../../shared/utils';`
|
|
2219
|
-
];
|
|
2220
|
-
if (localized) {
|
|
2221
|
-
imports.push(`import type { Locale } from '../../shared/locales';`);
|
|
2222
|
-
}
|
|
2223
|
-
const paginationFields = `
|
|
2224
|
-
/** Page number (1-indexed) - use with pageSize */
|
|
2225
|
-
page?: number;
|
|
2226
|
-
/** Number of items per page (default: 25) - use with page */
|
|
2227
|
-
pageSize?: number;
|
|
2228
|
-
/** Offset to start from (0-indexed) - use with limit */
|
|
2229
|
-
start?: number;
|
|
2230
|
-
/** Maximum number of items to return - use with start */
|
|
2231
|
-
limit?: number;`;
|
|
2232
|
-
let findManyOptionsFields = ` filters?: ${typeName}Filters;
|
|
2233
|
-
pagination?: {${paginationFields}
|
|
2234
|
-
};
|
|
2235
|
-
sort?: string | string[];
|
|
2236
|
-
populate?: string | string[] | Record<string, unknown>;`;
|
|
2237
|
-
let findOneOptionsFields = ` populate?: string | string[] | Record<string, unknown>;`;
|
|
2238
|
-
if (localized) {
|
|
2239
|
-
findManyOptionsFields += `
|
|
2240
|
-
locale?: Locale;`;
|
|
2241
|
-
findOneOptionsFields += `
|
|
2242
|
-
locale?: Locale;`;
|
|
2243
|
-
}
|
|
2244
|
-
if (draftAndPublish) {
|
|
2245
|
-
findManyOptionsFields += `
|
|
2246
|
-
status?: 'draft' | 'published';`;
|
|
2247
|
-
findOneOptionsFields += `
|
|
2248
|
-
status?: 'draft' | 'published';`;
|
|
2249
|
-
}
|
|
2250
|
-
let findParams = ` filters: options.filters,
|
|
2251
|
-
pagination: options.pagination,
|
|
2252
|
-
sort: options.sort,
|
|
2253
|
-
populate: options.populate,`;
|
|
2254
|
-
let findOneParams = ` populate: options.populate,`;
|
|
2255
|
-
if (localized) {
|
|
2256
|
-
findParams += `
|
|
2257
|
-
locale: options.locale,`;
|
|
2258
|
-
findOneParams += `
|
|
2259
|
-
locale: options.locale,`;
|
|
2260
|
-
}
|
|
2261
|
-
if (draftAndPublish) {
|
|
2262
|
-
findParams += `
|
|
2263
|
-
status: options.status,`;
|
|
2264
|
-
findOneParams += `
|
|
2265
|
-
status: options.status,`;
|
|
2266
|
-
}
|
|
2267
|
-
return `/**
|
|
3968
|
+
const idParam = isV4 ? "id" : "documentId";
|
|
3969
|
+
const idType = isV4 ? "number" : "string";
|
|
3970
|
+
return `// @ts-check
|
|
3971
|
+
/**
|
|
2268
3972
|
* ${collection.displayName} Service
|
|
2269
3973
|
* ${collection.description || ""}
|
|
2270
3974
|
* Generated by strapi2front
|
|
2271
3975
|
* Strapi version: ${strapiVersion}
|
|
2272
3976
|
*/
|
|
2273
3977
|
|
|
2274
|
-
|
|
3978
|
+
const { collection } = require('../../shared/client');
|
|
2275
3979
|
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
}
|
|
3980
|
+
/**
|
|
3981
|
+
* @typedef {Object} FindManyOptions
|
|
3982
|
+
* @property {import('./types').${typeName}Filters} [filters]
|
|
3983
|
+
* @property {Object} [pagination]
|
|
3984
|
+
* @property {number} [pagination.page]
|
|
3985
|
+
* @property {number} [pagination.pageSize]
|
|
3986
|
+
* @property {number} [pagination.start]
|
|
3987
|
+
* @property {number} [pagination.limit]
|
|
3988
|
+
* @property {string|string[]} [sort]
|
|
3989
|
+
* @property {string|string[]|Record<string, unknown>} [populate]${localized ? "\n * @property {string} [locale]" : ""}${draftAndPublish ? "\n * @property {'draft'|'published'} [status]" : ""}
|
|
3990
|
+
*/
|
|
2279
3991
|
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
}
|
|
3992
|
+
/**
|
|
3993
|
+
* @typedef {Object} FindOneOptions
|
|
3994
|
+
* @property {string|string[]|Record<string, unknown>} [populate]${localized ? "\n * @property {string} [locale]" : ""}${draftAndPublish ? "\n * @property {'draft'|'published'} [status]" : ""}
|
|
3995
|
+
*/
|
|
2283
3996
|
|
|
2284
|
-
|
|
2285
|
-
const ${toCamelCase(collection.singularName)}Collection = collection
|
|
3997
|
+
/** @type {ReturnType<typeof collection<import('./types').${typeName}>>} */
|
|
3998
|
+
const ${toCamelCase(collection.singularName)}Collection = collection('${endpoint}');
|
|
2286
3999
|
|
|
2287
|
-
|
|
2288
|
-
|
|
4000
|
+
const ${serviceName} = {
|
|
4001
|
+
/**
|
|
4002
|
+
* @param {FindManyOptions} [options]
|
|
4003
|
+
* @returns {Promise<{ data: import('./types').${typeName}[], pagination: import('../../shared/utils').StrapiPagination }>}
|
|
4004
|
+
*/
|
|
4005
|
+
async findMany(options = {}) {
|
|
2289
4006
|
const response = await ${toCamelCase(collection.singularName)}Collection.find({
|
|
2290
|
-
|
|
4007
|
+
filters: options.filters,
|
|
4008
|
+
pagination: options.pagination,
|
|
4009
|
+
sort: options.sort,
|
|
4010
|
+
populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
|
|
2291
4011
|
});
|
|
2292
4012
|
|
|
2293
4013
|
return {
|
|
@@ -2296,8 +4016,13 @@ ${findParams}
|
|
|
2296
4016
|
};
|
|
2297
4017
|
},
|
|
2298
4018
|
|
|
2299
|
-
|
|
2300
|
-
|
|
4019
|
+
/**
|
|
4020
|
+
* @param {Omit<FindManyOptions, 'pagination'>} [options]
|
|
4021
|
+
* @returns {Promise<import('./types').${typeName}[]>}
|
|
4022
|
+
*/
|
|
4023
|
+
async findAll(options = {}) {
|
|
4024
|
+
/** @type {import('./types').${typeName}[]} */
|
|
4025
|
+
const allItems = [];
|
|
2301
4026
|
let page = 1;
|
|
2302
4027
|
let hasMore = true;
|
|
2303
4028
|
|
|
@@ -2315,10 +4040,15 @@ ${findParams}
|
|
|
2315
4040
|
return allItems;
|
|
2316
4041
|
},
|
|
2317
4042
|
|
|
2318
|
-
|
|
4043
|
+
/**
|
|
4044
|
+
* @param {${idType}} ${idParam}
|
|
4045
|
+
* @param {FindOneOptions} [options]
|
|
4046
|
+
* @returns {Promise<import('./types').${typeName}|null>}
|
|
4047
|
+
*/
|
|
4048
|
+
async findOne(${idParam}, options = {}) {
|
|
2319
4049
|
try {
|
|
2320
|
-
const response = await ${toCamelCase(collection.singularName)}Collection.findOne(${
|
|
2321
|
-
${
|
|
4050
|
+
const response = await ${toCamelCase(collection.singularName)}Collection.findOne(${idParam}, {
|
|
4051
|
+
populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
|
|
2322
4052
|
});
|
|
2323
4053
|
|
|
2324
4054
|
return response.data;
|
|
@@ -2330,9 +4060,14 @@ ${findOneParams}
|
|
|
2330
4060
|
}
|
|
2331
4061
|
},
|
|
2332
4062
|
${hasSlug ? `
|
|
2333
|
-
|
|
4063
|
+
/**
|
|
4064
|
+
* @param {string} slug
|
|
4065
|
+
* @param {FindOneOptions} [options]
|
|
4066
|
+
* @returns {Promise<import('./types').${typeName}|null>}
|
|
4067
|
+
*/
|
|
4068
|
+
async findBySlug(slug, options = {}) {
|
|
2334
4069
|
const { data } = await this.findMany({
|
|
2335
|
-
filters: { slug: { $eq: slug } }
|
|
4070
|
+
filters: /** @type {import('./types').${typeName}Filters} */ ({ slug: { $eq: slug } }),
|
|
2336
4071
|
pagination: { pageSize: 1 },
|
|
2337
4072
|
populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
|
|
2338
4073
|
});
|
|
@@ -2340,21 +4075,38 @@ ${hasSlug ? `
|
|
|
2340
4075
|
return data[0] || null;
|
|
2341
4076
|
},
|
|
2342
4077
|
` : ""}
|
|
2343
|
-
|
|
4078
|
+
/**
|
|
4079
|
+
* @param {Partial<import('./types').${typeName}>} data
|
|
4080
|
+
* @returns {Promise<import('./types').${typeName}>}
|
|
4081
|
+
*/
|
|
4082
|
+
async create(data) {
|
|
2344
4083
|
const response = await ${toCamelCase(collection.singularName)}Collection.create({ data });
|
|
2345
4084
|
return response.data;
|
|
2346
4085
|
},
|
|
2347
4086
|
|
|
2348
|
-
|
|
2349
|
-
|
|
4087
|
+
/**
|
|
4088
|
+
* @param {${idType}} ${idParam}
|
|
4089
|
+
* @param {Partial<import('./types').${typeName}>} data
|
|
4090
|
+
* @returns {Promise<import('./types').${typeName}>}
|
|
4091
|
+
*/
|
|
4092
|
+
async update(${idParam}, data) {
|
|
4093
|
+
const response = await ${toCamelCase(collection.singularName)}Collection.update(${idParam}, { data });
|
|
2350
4094
|
return response.data;
|
|
2351
4095
|
},
|
|
2352
4096
|
|
|
2353
|
-
|
|
2354
|
-
|
|
4097
|
+
/**
|
|
4098
|
+
* @param {${idType}} ${idParam}
|
|
4099
|
+
* @returns {Promise<void>}
|
|
4100
|
+
*/
|
|
4101
|
+
async delete(${idParam}) {
|
|
4102
|
+
await ${toCamelCase(collection.singularName)}Collection.delete(${idParam});
|
|
2355
4103
|
},
|
|
2356
4104
|
|
|
2357
|
-
|
|
4105
|
+
/**
|
|
4106
|
+
* @param {import('./types').${typeName}Filters} [filters]
|
|
4107
|
+
* @returns {Promise<number>}
|
|
4108
|
+
*/
|
|
4109
|
+
async count(filters) {
|
|
2358
4110
|
const { pagination } = await this.findMany({
|
|
2359
4111
|
filters,
|
|
2360
4112
|
pagination: { pageSize: 1 },
|
|
@@ -2363,61 +4115,42 @@ ${hasSlug ? `
|
|
|
2363
4115
|
return pagination.total;
|
|
2364
4116
|
},
|
|
2365
4117
|
};
|
|
4118
|
+
|
|
4119
|
+
module.exports = { ${serviceName} };
|
|
2366
4120
|
`;
|
|
2367
4121
|
}
|
|
2368
|
-
function
|
|
4122
|
+
function generateSingleServiceJSDoc(single, strapiVersion) {
|
|
2369
4123
|
const typeName = toPascalCase(single.singularName);
|
|
2370
4124
|
const serviceName = toCamelCase(single.singularName) + "Service";
|
|
2371
4125
|
const endpoint = single.singularName;
|
|
2372
4126
|
const { localized, draftAndPublish } = single;
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
const imports = [
|
|
2376
|
-
`import { single } from '../../shared/client';`,
|
|
2377
|
-
`import type { ${typeName} } from './types';`
|
|
2378
|
-
];
|
|
2379
|
-
if (localized) {
|
|
2380
|
-
imports.push(`import type { Locale } from '../../shared/locales';`);
|
|
2381
|
-
}
|
|
2382
|
-
let findOptionsFields = ` populate?: string | string[] | Record<string, unknown>;`;
|
|
2383
|
-
if (localized) {
|
|
2384
|
-
findOptionsFields += `
|
|
2385
|
-
locale?: Locale;`;
|
|
2386
|
-
}
|
|
2387
|
-
if (draftAndPublish) {
|
|
2388
|
-
findOptionsFields += `
|
|
2389
|
-
status?: 'draft' | 'published';`;
|
|
2390
|
-
}
|
|
2391
|
-
let findParams = ` populate: options.populate,`;
|
|
2392
|
-
if (localized) {
|
|
2393
|
-
findParams += `
|
|
2394
|
-
locale: options.locale,`;
|
|
2395
|
-
}
|
|
2396
|
-
if (draftAndPublish) {
|
|
2397
|
-
findParams += `
|
|
2398
|
-
status: options.status,`;
|
|
2399
|
-
}
|
|
2400
|
-
return `/**
|
|
4127
|
+
return `// @ts-check
|
|
4128
|
+
/**
|
|
2401
4129
|
* ${single.displayName} Service (Single Type)
|
|
2402
4130
|
* ${single.description || ""}
|
|
2403
4131
|
* Generated by strapi2front
|
|
2404
4132
|
* Strapi version: ${strapiVersion}
|
|
2405
4133
|
*/
|
|
2406
4134
|
|
|
2407
|
-
|
|
4135
|
+
const { single } = require('../../shared/client');
|
|
2408
4136
|
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
}
|
|
4137
|
+
/**
|
|
4138
|
+
* @typedef {Object} FindOptions
|
|
4139
|
+
* @property {string|string[]|Record<string, unknown>} [populate]${localized ? "\n * @property {string} [locale]" : ""}${draftAndPublish ? "\n * @property {'draft'|'published'} [status]" : ""}
|
|
4140
|
+
*/
|
|
2412
4141
|
|
|
2413
|
-
|
|
2414
|
-
const ${toCamelCase(single.singularName)}Single = single
|
|
4142
|
+
/** @type {ReturnType<typeof single<import('./types').${typeName}>>} */
|
|
4143
|
+
const ${toCamelCase(single.singularName)}Single = single('${endpoint}');
|
|
2415
4144
|
|
|
2416
|
-
|
|
2417
|
-
|
|
4145
|
+
const ${serviceName} = {
|
|
4146
|
+
/**
|
|
4147
|
+
* @param {FindOptions} [options]
|
|
4148
|
+
* @returns {Promise<import('./types').${typeName}|null>}
|
|
4149
|
+
*/
|
|
4150
|
+
async find(options = {}) {
|
|
2418
4151
|
try {
|
|
2419
4152
|
const response = await ${toCamelCase(single.singularName)}Single.find({
|
|
2420
|
-
${
|
|
4153
|
+
populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
|
|
2421
4154
|
});
|
|
2422
4155
|
|
|
2423
4156
|
return response.data;
|
|
@@ -2429,96 +4162,60 @@ ${findParams}
|
|
|
2429
4162
|
}
|
|
2430
4163
|
},
|
|
2431
4164
|
|
|
2432
|
-
|
|
4165
|
+
/**
|
|
4166
|
+
* @param {Partial<import('./types').${typeName}>} data
|
|
4167
|
+
* @returns {Promise<import('./types').${typeName}>}
|
|
4168
|
+
*/
|
|
4169
|
+
async update(data) {
|
|
2433
4170
|
const response = await ${toCamelCase(single.singularName)}Single.update({ data });
|
|
2434
4171
|
return response.data;
|
|
2435
4172
|
},
|
|
2436
4173
|
|
|
2437
|
-
|
|
4174
|
+
/**
|
|
4175
|
+
* @returns {Promise<void>}
|
|
4176
|
+
*/
|
|
4177
|
+
async delete() {
|
|
2438
4178
|
await ${toCamelCase(single.singularName)}Single.delete();
|
|
2439
4179
|
},
|
|
2440
4180
|
};
|
|
4181
|
+
|
|
4182
|
+
module.exports = { ${serviceName} };
|
|
2441
4183
|
`;
|
|
2442
4184
|
}
|
|
2443
|
-
function generateCollectionActions2(collection, strapiVersion) {
|
|
2444
|
-
const typeName = toPascalCase(collection.singularName);
|
|
2445
|
-
const serviceName = toCamelCase(collection.singularName) + "Service";
|
|
2446
|
-
const actionPrefix = toCamelCase(collection.singularName);
|
|
2447
|
-
const isV4 = strapiVersion === "v4";
|
|
2448
|
-
const idInputSchema = isV4 ? "z.number().int().positive()" : "z.string()";
|
|
2449
|
-
const idParamName = isV4 ? "id" : "documentId";
|
|
2450
|
-
return `/**
|
|
2451
|
-
* ${collection.displayName} Astro Actions
|
|
2452
|
-
* ${collection.description || ""}
|
|
2453
|
-
* Generated by strapi2front
|
|
2454
|
-
* Strapi version: ${strapiVersion}
|
|
2455
|
-
*/
|
|
2456
|
-
|
|
2457
|
-
import { defineAction } from 'astro:actions';
|
|
2458
|
-
import { z } from 'astro:schema';
|
|
2459
|
-
import { ${serviceName} } from './service';
|
|
2460
|
-
import type { ${typeName} } from './types';
|
|
2461
|
-
|
|
2462
|
-
export const ${actionPrefix}Actions = {
|
|
2463
|
-
getMany: defineAction({
|
|
2464
|
-
input: z.object({
|
|
2465
|
-
page: z.number().optional(),
|
|
2466
|
-
pageSize: z.number().optional(),
|
|
2467
|
-
sort: z.string().optional(),
|
|
2468
|
-
}).optional(),
|
|
2469
|
-
handler: async (input) => {
|
|
2470
|
-
const { data, pagination } = await ${serviceName}.findMany({
|
|
2471
|
-
pagination: input ? { page: input.page, pageSize: input.pageSize } : undefined,
|
|
2472
|
-
sort: input?.sort,
|
|
2473
|
-
});
|
|
2474
|
-
return { data, pagination };
|
|
2475
|
-
},
|
|
2476
|
-
}),
|
|
2477
|
-
|
|
2478
|
-
getOne: defineAction({
|
|
2479
|
-
input: z.object({
|
|
2480
|
-
${idParamName}: ${idInputSchema},
|
|
2481
|
-
}),
|
|
2482
|
-
handler: async (input) => {
|
|
2483
|
-
const data = await ${serviceName}.findOne(input.${idParamName});
|
|
2484
|
-
return { data };
|
|
2485
|
-
},
|
|
2486
|
-
}),
|
|
2487
4185
|
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
4186
|
+
// src/frameworks/nextjs/actions.ts
|
|
4187
|
+
async function generateNextJsActions(_schema, _options) {
|
|
4188
|
+
throw new Error(
|
|
4189
|
+
"Next.js Server Actions generator is not yet implemented. Coming soon! For now, you can use the generated services directly."
|
|
4190
|
+
);
|
|
4191
|
+
}
|
|
4192
|
+
function isNextJsActionsSupported(nextVersion) {
|
|
4193
|
+
if (!nextVersion) return false;
|
|
4194
|
+
const match = nextVersion.replace(/^[\^~]/, "").match(/^(\d+)/);
|
|
4195
|
+
const majorVersion = match ? parseInt(match[1], 10) : null;
|
|
4196
|
+
return majorVersion !== null && majorVersion >= 14;
|
|
4197
|
+
}
|
|
2497
4198
|
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
4199
|
+
// src/frameworks/nuxt/server-routes.ts
|
|
4200
|
+
async function generateNuxtServerRoutes(_schema, _options) {
|
|
4201
|
+
throw new Error(
|
|
4202
|
+
"Nuxt Server Routes generator is not yet implemented. Coming soon! For now, you can use the generated services directly."
|
|
4203
|
+
);
|
|
4204
|
+
}
|
|
4205
|
+
function isNuxtServerRoutesSupported(nuxtVersion) {
|
|
4206
|
+
if (!nuxtVersion) return false;
|
|
4207
|
+
const match = nuxtVersion.replace(/^[\^~]/, "").match(/^(\d+)/);
|
|
4208
|
+
const majorVersion = match ? parseInt(match[1], 10) : null;
|
|
4209
|
+
return majorVersion !== null && majorVersion >= 3;
|
|
4210
|
+
}
|
|
2508
4211
|
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
await ${serviceName}.delete(input.${idParamName});
|
|
2515
|
-
return { success: true };
|
|
2516
|
-
},
|
|
2517
|
-
}),
|
|
4212
|
+
// src/frameworks/index.ts
|
|
4213
|
+
var frameworkSupport = {
|
|
4214
|
+
astro: { status: "stable", minVersion: "4.0.0" },
|
|
4215
|
+
nextjs: { status: "coming-soon", minVersion: "14.0.0" },
|
|
4216
|
+
nuxt: { status: "coming-soon", minVersion: "3.0.0" }
|
|
2518
4217
|
};
|
|
2519
|
-
`;
|
|
2520
|
-
}
|
|
2521
4218
|
|
|
2522
|
-
export { deleteFile, ensureDir, fileExists, formatCode, formatJson, generateActions, generateByFeature, generateClient, generateLocales, generateServices, generateTypes, listFiles, pluralize, readFile, toCamelCase, toKebabCase, toPascalCase, writeFile };
|
|
4219
|
+
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 };
|
|
2523
4220
|
//# sourceMappingURL=index.js.map
|
|
2524
4221
|
//# sourceMappingURL=index.js.map
|