@strapi2front/generators 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +229 -5
- package/dist/index.js +2806 -1117
- 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,49 @@ 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
|
-
return { id: item.id, ...item.attributes }
|
|
3385
|
+
function flattenItem(item) {
|
|
3386
|
+
return { id: item.id, ...item.attributes };
|
|
1771
3387
|
}
|
|
1772
3388
|
|
|
1773
3389
|
/**
|
|
1774
3390
|
* Recursively flatten nested relations in Strapi v4 response
|
|
3391
|
+
* @template T
|
|
3392
|
+
* @param {T} data
|
|
3393
|
+
* @returns {T}
|
|
1775
3394
|
*/
|
|
1776
|
-
function flattenRelations
|
|
3395
|
+
function flattenRelations(data) {
|
|
1777
3396
|
if (data === null || data === undefined) return data;
|
|
1778
3397
|
if (Array.isArray(data)) {
|
|
1779
|
-
return data.map(item => flattenRelations(item))
|
|
3398
|
+
return /** @type {T} */ (data.map(item => flattenRelations(item)));
|
|
1780
3399
|
}
|
|
1781
3400
|
if (typeof data === 'object') {
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
3401
|
+
/** @type {Record<string, unknown>} */
|
|
3402
|
+
const result = {};
|
|
3403
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1785
3404
|
if (value && typeof value === 'object' && 'data' in value) {
|
|
1786
|
-
const relationData =
|
|
3405
|
+
const relationData = /** @type {{ data: unknown }} */ (value).data;
|
|
1787
3406
|
if (relationData === null) {
|
|
1788
3407
|
result[key] = null;
|
|
1789
3408
|
} else if (Array.isArray(relationData)) {
|
|
1790
|
-
|
|
1791
|
-
result[key] = relationData.map((item: StrapiV4RawItem<unknown>) =>
|
|
3409
|
+
result[key] = relationData.map((item) =>
|
|
1792
3410
|
flattenRelations(flattenItem(item))
|
|
1793
3411
|
);
|
|
1794
3412
|
} else if (typeof relationData === 'object' && 'id' in relationData && 'attributes' in relationData) {
|
|
1795
|
-
|
|
1796
|
-
result[key] = flattenRelations(flattenItem(relationData as StrapiV4RawItem<unknown>));
|
|
3413
|
+
result[key] = flattenRelations(flattenItem(/** @type {{ id: number, attributes: object }} */ (relationData)));
|
|
1797
3414
|
} else {
|
|
1798
3415
|
result[key] = flattenRelations(value);
|
|
1799
3416
|
}
|
|
@@ -1801,76 +3418,148 @@ function flattenRelations<T>(data: T): T {
|
|
|
1801
3418
|
result[key] = flattenRelations(value);
|
|
1802
3419
|
}
|
|
1803
3420
|
}
|
|
1804
|
-
return
|
|
3421
|
+
return /** @type {T} */ (result);
|
|
1805
3422
|
}
|
|
1806
3423
|
return data;
|
|
1807
3424
|
}
|
|
1808
3425
|
|
|
1809
|
-
|
|
1810
|
-
|
|
3426
|
+
/**
|
|
3427
|
+
* Helper to get typed collection
|
|
3428
|
+
* @template T
|
|
3429
|
+
* @param {string} pluralName
|
|
3430
|
+
*/
|
|
3431
|
+
function collection(pluralName) {
|
|
1811
3432
|
return {
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
3433
|
+
/**
|
|
3434
|
+
* @param {Record<string, unknown>} [params]
|
|
3435
|
+
* @returns {Promise<{ data: T[], meta: { pagination: import('./utils').StrapiPagination } }>}
|
|
3436
|
+
*/
|
|
3437
|
+
async find(params) {
|
|
3438
|
+
const response = await strapi.find(pluralName, params);
|
|
3439
|
+
/** @type {any} */
|
|
3440
|
+
const rawData = response.data;
|
|
3441
|
+
/** @type {T[]} */
|
|
3442
|
+
const data = Array.isArray(rawData)
|
|
3443
|
+
? rawData.map(item => flattenRelations(flattenItem(item)))
|
|
1816
3444
|
: [];
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
3445
|
+
/** @type {any} */
|
|
3446
|
+
const rawMeta = response.meta;
|
|
3447
|
+
/** @type {any} */
|
|
3448
|
+
const rawPag = rawMeta?.pagination;
|
|
3449
|
+
/** @type {import('./utils').StrapiPagination} */
|
|
3450
|
+
const pagination = {
|
|
3451
|
+
page: rawPag?.page ?? defaultPagination.page,
|
|
3452
|
+
pageSize: rawPag?.pageSize ?? defaultPagination.pageSize,
|
|
3453
|
+
pageCount: rawPag?.pageCount ?? defaultPagination.pageCount,
|
|
3454
|
+
total: rawPag?.total ?? defaultPagination.total,
|
|
1822
3455
|
};
|
|
3456
|
+
return { data, meta: { pagination } };
|
|
1823
3457
|
},
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
3458
|
+
/**
|
|
3459
|
+
* @param {number|string} id
|
|
3460
|
+
* @param {Record<string, unknown>} [params]
|
|
3461
|
+
* @returns {Promise<{ data: T }>}
|
|
3462
|
+
*/
|
|
3463
|
+
async findOne(id, params) {
|
|
3464
|
+
const response = await strapi.findOne(pluralName, String(id), params);
|
|
3465
|
+
/** @type {any} */
|
|
3466
|
+
const rawData = response.data;
|
|
3467
|
+
/** @type {T} */
|
|
3468
|
+
const data = flattenRelations(flattenItem(rawData));
|
|
3469
|
+
return { data };
|
|
1827
3470
|
},
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
3471
|
+
/**
|
|
3472
|
+
* @param {{ data: Partial<T> }} data
|
|
3473
|
+
* @returns {Promise<{ data: T }>}
|
|
3474
|
+
*/
|
|
3475
|
+
async create(data) {
|
|
3476
|
+
const response = await strapi.create(pluralName, data.data);
|
|
3477
|
+
/** @type {any} */
|
|
3478
|
+
const rawData = response.data;
|
|
3479
|
+
/** @type {T} */
|
|
3480
|
+
const result = flattenRelations(flattenItem(rawData));
|
|
3481
|
+
return { data: result };
|
|
1831
3482
|
},
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
3483
|
+
/**
|
|
3484
|
+
* @param {number|string} id
|
|
3485
|
+
* @param {{ data: Partial<T> }} data
|
|
3486
|
+
* @returns {Promise<{ data: T }>}
|
|
3487
|
+
*/
|
|
3488
|
+
async update(id, data) {
|
|
3489
|
+
const response = await strapi.update(pluralName, String(id), data.data);
|
|
3490
|
+
/** @type {any} */
|
|
3491
|
+
const rawData = response.data;
|
|
3492
|
+
/** @type {T} */
|
|
3493
|
+
const result = flattenRelations(flattenItem(rawData));
|
|
3494
|
+
return { data: result };
|
|
1835
3495
|
},
|
|
1836
|
-
|
|
3496
|
+
/**
|
|
3497
|
+
* @param {number|string} id
|
|
3498
|
+
* @returns {Promise<void>}
|
|
3499
|
+
*/
|
|
3500
|
+
async delete(id) {
|
|
1837
3501
|
await strapi.delete(pluralName, String(id));
|
|
1838
3502
|
},
|
|
1839
3503
|
};
|
|
1840
3504
|
}
|
|
1841
3505
|
|
|
1842
|
-
|
|
1843
|
-
|
|
3506
|
+
/**
|
|
3507
|
+
* Helper to get typed single type
|
|
3508
|
+
* @template T
|
|
3509
|
+
* @param {string} singularName
|
|
3510
|
+
*/
|
|
3511
|
+
function single(singularName) {
|
|
1844
3512
|
return {
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
3513
|
+
/**
|
|
3514
|
+
* @param {Record<string, unknown>} [params]
|
|
3515
|
+
* @returns {Promise<{ data: T }>}
|
|
3516
|
+
*/
|
|
3517
|
+
async find(params) {
|
|
3518
|
+
const response = await strapi.find(singularName, params);
|
|
3519
|
+
/** @type {any} */
|
|
3520
|
+
const rawData = response.data;
|
|
3521
|
+
/** @type {T} */
|
|
3522
|
+
const data = flattenRelations(flattenItem(rawData));
|
|
3523
|
+
return { data };
|
|
1848
3524
|
},
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
3525
|
+
/**
|
|
3526
|
+
* @param {{ data: Partial<T> }} data
|
|
3527
|
+
* @returns {Promise<{ data: T }>}
|
|
3528
|
+
*/
|
|
3529
|
+
async update(data) {
|
|
3530
|
+
const response = await strapi.update(singularName, String(1), data.data);
|
|
3531
|
+
/** @type {any} */
|
|
3532
|
+
const rawData = response.data;
|
|
3533
|
+
/** @type {T} */
|
|
3534
|
+
const result = flattenRelations(flattenItem(rawData));
|
|
3535
|
+
return { data: result };
|
|
1852
3536
|
},
|
|
1853
|
-
|
|
1854
|
-
|
|
3537
|
+
/**
|
|
3538
|
+
* @returns {Promise<void>}
|
|
3539
|
+
*/
|
|
3540
|
+
async delete() {
|
|
3541
|
+
await strapi.delete(singularName, String(1));
|
|
1855
3542
|
},
|
|
1856
3543
|
};
|
|
1857
3544
|
}
|
|
3545
|
+
|
|
3546
|
+
module.exports = { strapi, collection, single };
|
|
1858
3547
|
`;
|
|
1859
3548
|
}
|
|
1860
|
-
return
|
|
3549
|
+
return `// @ts-check
|
|
3550
|
+
/**
|
|
1861
3551
|
* Strapi Client (v5)
|
|
1862
3552
|
* Generated by strapi2front
|
|
1863
3553
|
*/
|
|
1864
3554
|
|
|
1865
|
-
|
|
1866
|
-
import type { StrapiPagination } from './utils';
|
|
3555
|
+
const Strapi = require('strapi-sdk-js').default;
|
|
1867
3556
|
|
|
1868
3557
|
// Initialize the Strapi client
|
|
1869
|
-
const strapiUrl =
|
|
1870
|
-
const strapiToken =
|
|
1871
|
-
const strapiApiPrefix =
|
|
3558
|
+
const strapiUrl = process.env.STRAPI_URL || 'http://localhost:1337';
|
|
3559
|
+
const strapiToken = process.env.STRAPI_TOKEN;
|
|
3560
|
+
const strapiApiPrefix = process.env.STRAPI_API_PREFIX || '${normalizedPrefix}';
|
|
1872
3561
|
|
|
1873
|
-
|
|
3562
|
+
const strapi = new Strapi({
|
|
1874
3563
|
url: strapiUrl,
|
|
1875
3564
|
prefix: strapiApiPrefix,
|
|
1876
3565
|
axiosOptions: {
|
|
@@ -1880,267 +3569,304 @@ export const strapi = new Strapi({
|
|
|
1880
3569
|
},
|
|
1881
3570
|
});
|
|
1882
3571
|
|
|
1883
|
-
|
|
1884
|
-
const defaultPagination
|
|
3572
|
+
/** @type {import('./utils').StrapiPagination} */
|
|
3573
|
+
const defaultPagination = {
|
|
1885
3574
|
page: 1,
|
|
1886
3575
|
pageSize: 25,
|
|
1887
3576
|
pageCount: 1,
|
|
1888
3577
|
total: 0,
|
|
1889
3578
|
};
|
|
1890
3579
|
|
|
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) {
|
|
3580
|
+
/**
|
|
3581
|
+
* Helper to get typed collection
|
|
3582
|
+
* @template T
|
|
3583
|
+
* @param {string} pluralName
|
|
3584
|
+
*/
|
|
3585
|
+
function collection(pluralName) {
|
|
1906
3586
|
return {
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
3587
|
+
/**
|
|
3588
|
+
* @param {Record<string, unknown>} [params]
|
|
3589
|
+
* @returns {Promise<{ data: T[], meta: { pagination: import('./utils').StrapiPagination } }>}
|
|
3590
|
+
*/
|
|
3591
|
+
async find(params) {
|
|
3592
|
+
const response = await strapi.find(pluralName, params);
|
|
3593
|
+
/** @type {any} */
|
|
3594
|
+
const rawMeta = response.meta;
|
|
3595
|
+
/** @type {any} */
|
|
3596
|
+
const rawPag = rawMeta?.pagination;
|
|
3597
|
+
/** @type {import('./utils').StrapiPagination} */
|
|
3598
|
+
const pagination = {
|
|
3599
|
+
page: rawPag?.page ?? defaultPagination.page,
|
|
3600
|
+
pageSize: rawPag?.pageSize ?? defaultPagination.pageSize,
|
|
3601
|
+
pageCount: rawPag?.pageCount ?? defaultPagination.pageCount,
|
|
3602
|
+
total: rawPag?.total ?? defaultPagination.total,
|
|
1914
3603
|
};
|
|
3604
|
+
/** @type {any} */
|
|
3605
|
+
const rawData = response.data;
|
|
3606
|
+
/** @type {T[]} */
|
|
3607
|
+
const data = Array.isArray(rawData) ? rawData : [];
|
|
3608
|
+
return { data, meta: { pagination } };
|
|
1915
3609
|
},
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
3610
|
+
/**
|
|
3611
|
+
* @param {string} documentId
|
|
3612
|
+
* @param {Record<string, unknown>} [params]
|
|
3613
|
+
* @returns {Promise<{ data: T }>}
|
|
3614
|
+
*/
|
|
3615
|
+
async findOne(documentId, params) {
|
|
3616
|
+
const response = await strapi.findOne(pluralName, documentId, params);
|
|
3617
|
+
/** @type {any} */
|
|
3618
|
+
const rawData = response.data;
|
|
3619
|
+
/** @type {T} */
|
|
3620
|
+
const data = rawData;
|
|
3621
|
+
return { data };
|
|
1919
3622
|
},
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
3623
|
+
/**
|
|
3624
|
+
* @param {{ data: Partial<T> }} data
|
|
3625
|
+
* @returns {Promise<{ data: T }>}
|
|
3626
|
+
*/
|
|
3627
|
+
async create(data) {
|
|
3628
|
+
const response = await strapi.create(pluralName, data.data);
|
|
3629
|
+
/** @type {any} */
|
|
3630
|
+
const rawData = response.data;
|
|
3631
|
+
/** @type {T} */
|
|
3632
|
+
const result = rawData;
|
|
3633
|
+
return { data: result };
|
|
1923
3634
|
},
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
3635
|
+
/**
|
|
3636
|
+
* @param {string} documentId
|
|
3637
|
+
* @param {{ data: Partial<T> }} data
|
|
3638
|
+
* @returns {Promise<{ data: T }>}
|
|
3639
|
+
*/
|
|
3640
|
+
async update(documentId, data) {
|
|
3641
|
+
const response = await strapi.update(pluralName, documentId, data.data);
|
|
3642
|
+
/** @type {any} */
|
|
3643
|
+
const rawData = response.data;
|
|
3644
|
+
/** @type {T} */
|
|
3645
|
+
const result = rawData;
|
|
3646
|
+
return { data: result };
|
|
1927
3647
|
},
|
|
1928
|
-
|
|
3648
|
+
/**
|
|
3649
|
+
* @param {string} documentId
|
|
3650
|
+
* @returns {Promise<void>}
|
|
3651
|
+
*/
|
|
3652
|
+
async delete(documentId) {
|
|
1929
3653
|
await strapi.delete(pluralName, documentId);
|
|
1930
3654
|
},
|
|
1931
3655
|
};
|
|
1932
3656
|
}
|
|
1933
3657
|
|
|
1934
|
-
|
|
1935
|
-
|
|
3658
|
+
/**
|
|
3659
|
+
* Helper to get typed single type
|
|
3660
|
+
* @template T
|
|
3661
|
+
* @param {string} singularName
|
|
3662
|
+
*/
|
|
3663
|
+
function single(singularName) {
|
|
1936
3664
|
return {
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
3665
|
+
/**
|
|
3666
|
+
* @param {Record<string, unknown>} [params]
|
|
3667
|
+
* @returns {Promise<{ data: T }>}
|
|
3668
|
+
*/
|
|
3669
|
+
async find(params) {
|
|
3670
|
+
const response = await strapi.find(singularName, params);
|
|
3671
|
+
/** @type {any} */
|
|
3672
|
+
const rawData = response.data;
|
|
3673
|
+
/** @type {T} */
|
|
3674
|
+
const data = rawData;
|
|
3675
|
+
return { data };
|
|
1940
3676
|
},
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
3677
|
+
/**
|
|
3678
|
+
* @param {{ data: Partial<T> }} data
|
|
3679
|
+
* @returns {Promise<{ data: T }>}
|
|
3680
|
+
*/
|
|
3681
|
+
async update(data) {
|
|
3682
|
+
const response = await strapi.update(singularName, String(1), data.data);
|
|
3683
|
+
/** @type {any} */
|
|
3684
|
+
const rawData = response.data;
|
|
3685
|
+
/** @type {T} */
|
|
3686
|
+
const result = rawData;
|
|
3687
|
+
return { data: result };
|
|
1944
3688
|
},
|
|
1945
|
-
|
|
1946
|
-
|
|
3689
|
+
/**
|
|
3690
|
+
* @returns {Promise<void>}
|
|
3691
|
+
*/
|
|
3692
|
+
async delete() {
|
|
3693
|
+
await strapi.delete(singularName, String(1));
|
|
1947
3694
|
},
|
|
1948
3695
|
};
|
|
1949
3696
|
}
|
|
3697
|
+
|
|
3698
|
+
module.exports = { strapi, collection, single };
|
|
1950
3699
|
`;
|
|
1951
3700
|
}
|
|
1952
|
-
function
|
|
3701
|
+
function generateLocalesFileJSDoc(locales) {
|
|
1953
3702
|
if (locales.length === 0) {
|
|
1954
|
-
return
|
|
3703
|
+
return `// @ts-check
|
|
3704
|
+
/**
|
|
1955
3705
|
* Strapi locales
|
|
1956
3706
|
* Generated by strapi2front
|
|
1957
3707
|
* Note: i18n is not enabled in Strapi
|
|
1958
3708
|
*/
|
|
1959
3709
|
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
export const defaultLocale: Locale = 'en';
|
|
1963
|
-
export const localeNames: Record<string, string> = {};
|
|
3710
|
+
/** @type {readonly string[]} */
|
|
3711
|
+
const locales = [];
|
|
1964
3712
|
|
|
1965
|
-
|
|
3713
|
+
/** @typedef {string} Locale */
|
|
3714
|
+
|
|
3715
|
+
/** @type {string} */
|
|
3716
|
+
const defaultLocale = 'en';
|
|
3717
|
+
|
|
3718
|
+
/** @type {Record<string, string>} */
|
|
3719
|
+
const localeNames = {};
|
|
3720
|
+
|
|
3721
|
+
/**
|
|
3722
|
+
* @param {string} _code
|
|
3723
|
+
* @returns {boolean}
|
|
3724
|
+
*/
|
|
3725
|
+
function isValidLocale(_code) {
|
|
1966
3726
|
return true;
|
|
1967
3727
|
}
|
|
1968
3728
|
|
|
1969
|
-
|
|
3729
|
+
/**
|
|
3730
|
+
* @param {string} code
|
|
3731
|
+
* @returns {string}
|
|
3732
|
+
*/
|
|
3733
|
+
function getLocaleName(code) {
|
|
1970
3734
|
return code;
|
|
1971
3735
|
}
|
|
3736
|
+
|
|
3737
|
+
module.exports = { locales, defaultLocale, localeNames, isValidLocale, getLocaleName };
|
|
1972
3738
|
`;
|
|
1973
3739
|
}
|
|
1974
3740
|
const localeCodes = locales.map((l) => l.code);
|
|
1975
3741
|
const defaultLocale = locales.find((l) => l.isDefault)?.code || locales[0]?.code || "en";
|
|
1976
|
-
return
|
|
3742
|
+
return `// @ts-check
|
|
3743
|
+
/**
|
|
1977
3744
|
* Strapi locales
|
|
1978
3745
|
* Generated by strapi2front
|
|
1979
3746
|
*/
|
|
1980
3747
|
|
|
1981
|
-
|
|
3748
|
+
/** @type {readonly [${localeCodes.map((c) => `'${c}'`).join(", ")}]} */
|
|
3749
|
+
const locales = [${localeCodes.map((c) => `'${c}'`).join(", ")}];
|
|
1982
3750
|
|
|
1983
|
-
|
|
3751
|
+
/** @typedef {${localeCodes.map((c) => `'${c}'`).join(" | ")}} Locale */
|
|
1984
3752
|
|
|
1985
|
-
|
|
3753
|
+
/** @type {Locale} */
|
|
3754
|
+
const defaultLocale = '${defaultLocale}';
|
|
1986
3755
|
|
|
1987
|
-
|
|
3756
|
+
/** @type {Record<Locale, string>} */
|
|
3757
|
+
const localeNames = {
|
|
1988
3758
|
${locales.map((l) => ` '${l.code}': '${l.name}'`).join(",\n")}
|
|
1989
3759
|
};
|
|
1990
3760
|
|
|
1991
|
-
|
|
1992
|
-
|
|
3761
|
+
/**
|
|
3762
|
+
* @param {string} code
|
|
3763
|
+
* @returns {boolean}
|
|
3764
|
+
*/
|
|
3765
|
+
function isValidLocale(code) {
|
|
3766
|
+
/** @type {readonly string[]} */
|
|
3767
|
+
const localeList = locales;
|
|
3768
|
+
return localeList.includes(code);
|
|
1993
3769
|
}
|
|
1994
3770
|
|
|
1995
|
-
|
|
3771
|
+
/**
|
|
3772
|
+
* @param {Locale} code
|
|
3773
|
+
* @returns {string}
|
|
3774
|
+
*/
|
|
3775
|
+
function getLocaleName(code) {
|
|
1996
3776
|
return localeNames[code] || code;
|
|
1997
3777
|
}
|
|
3778
|
+
|
|
3779
|
+
module.exports = { locales, defaultLocale, localeNames, isValidLocale, getLocaleName };
|
|
1998
3780
|
`;
|
|
1999
3781
|
}
|
|
2000
|
-
function
|
|
3782
|
+
function generateCollectionTypesJSDoc(collection, schema) {
|
|
2001
3783
|
const typeName = toPascalCase(collection.singularName);
|
|
2002
|
-
const attributes =
|
|
2003
|
-
|
|
2004
|
-
|
|
3784
|
+
const attributes = generateJSDocAttributes(collection.attributes, schema, "collection");
|
|
3785
|
+
return `// @ts-check
|
|
3786
|
+
/**
|
|
2005
3787
|
* ${collection.displayName}
|
|
2006
3788
|
* ${collection.description || ""}
|
|
2007
3789
|
* Generated by strapi2front
|
|
2008
3790
|
*/
|
|
2009
3791
|
|
|
2010
|
-
|
|
3792
|
+
/**
|
|
3793
|
+
* @typedef {import('../../shared/utils').StrapiBaseEntity & ${typeName}Attributes} ${typeName}
|
|
3794
|
+
*/
|
|
2011
3795
|
|
|
2012
|
-
|
|
3796
|
+
/**
|
|
3797
|
+
* @typedef {Object} ${typeName}Attributes
|
|
2013
3798
|
${attributes}
|
|
2014
|
-
|
|
3799
|
+
*/
|
|
2015
3800
|
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
}
|
|
3801
|
+
/**
|
|
3802
|
+
* @typedef {Object} ${typeName}Filters
|
|
3803
|
+
* @property {number|{$eq?: number, $ne?: number, $in?: number[], $notIn?: number[]}} [id]
|
|
3804
|
+
* @property {string|{$eq?: string, $ne?: string}} [documentId]
|
|
3805
|
+
* @property {string|{$eq?: string, $gt?: string, $gte?: string, $lt?: string, $lte?: string}} [createdAt]
|
|
3806
|
+
* @property {string|{$eq?: string, $gt?: string, $gte?: string, $lt?: string, $lte?: string}} [updatedAt]
|
|
3807
|
+
* @property {string|null|{$eq?: string, $ne?: string, $null?: boolean}} [publishedAt]
|
|
3808
|
+
* @property {${typeName}Filters[]} [$and]
|
|
3809
|
+
* @property {${typeName}Filters[]} [$or]
|
|
3810
|
+
* @property {${typeName}Filters} [$not]
|
|
3811
|
+
*/
|
|
3812
|
+
|
|
3813
|
+
module.exports = {};
|
|
2026
3814
|
`;
|
|
2027
3815
|
}
|
|
2028
|
-
function
|
|
3816
|
+
function generateSingleTypesJSDoc(single, schema) {
|
|
2029
3817
|
const typeName = toPascalCase(single.singularName);
|
|
2030
|
-
const attributes =
|
|
2031
|
-
|
|
2032
|
-
|
|
3818
|
+
const attributes = generateJSDocAttributes(single.attributes, schema, "single");
|
|
3819
|
+
return `// @ts-check
|
|
3820
|
+
/**
|
|
2033
3821
|
* ${single.displayName}
|
|
2034
3822
|
* ${single.description || ""}
|
|
2035
3823
|
* Generated by strapi2front
|
|
2036
3824
|
*/
|
|
2037
3825
|
|
|
2038
|
-
|
|
3826
|
+
/**
|
|
3827
|
+
* @typedef {import('../../shared/utils').StrapiBaseEntity & ${typeName}Attributes} ${typeName}
|
|
3828
|
+
*/
|
|
2039
3829
|
|
|
2040
|
-
|
|
3830
|
+
/**
|
|
3831
|
+
* @typedef {Object} ${typeName}Attributes
|
|
2041
3832
|
${attributes}
|
|
2042
|
-
|
|
3833
|
+
*/
|
|
3834
|
+
|
|
3835
|
+
module.exports = {};
|
|
2043
3836
|
`;
|
|
2044
3837
|
}
|
|
2045
|
-
function
|
|
3838
|
+
function generateComponentTypesJSDoc(component, schema) {
|
|
2046
3839
|
const typeName = toPascalCase(component.name);
|
|
2047
|
-
const attributes =
|
|
2048
|
-
|
|
2049
|
-
|
|
3840
|
+
const attributes = generateJSDocAttributes(component.attributes, schema, "component");
|
|
3841
|
+
return `// @ts-check
|
|
3842
|
+
/**
|
|
2050
3843
|
* ${component.displayName} component
|
|
2051
3844
|
* Category: ${component.category}
|
|
2052
3845
|
* ${component.description || ""}
|
|
2053
3846
|
* Generated by strapi2front
|
|
2054
3847
|
*/
|
|
2055
3848
|
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
id: number;
|
|
3849
|
+
/**
|
|
3850
|
+
* @typedef {Object} ${typeName}
|
|
3851
|
+
* @property {number} id
|
|
2060
3852
|
${attributes}
|
|
2061
|
-
|
|
3853
|
+
*/
|
|
3854
|
+
|
|
3855
|
+
module.exports = {};
|
|
2062
3856
|
`;
|
|
2063
3857
|
}
|
|
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) {
|
|
3858
|
+
function generateJSDocAttributes(attributes, schema, context) {
|
|
2135
3859
|
const lines = [];
|
|
3860
|
+
const relativePrefix = context === "component" ? ".." : "../..";
|
|
2136
3861
|
for (const [name, attr] of Object.entries(attributes)) {
|
|
2137
|
-
const
|
|
2138
|
-
const optional = attr.required ? "" : "
|
|
2139
|
-
|
|
3862
|
+
const jsType = attributeToJSDocType(attr, schema, relativePrefix);
|
|
3863
|
+
const optional = attr.required ? "" : "[";
|
|
3864
|
+
const optionalEnd = attr.required ? "" : "]";
|
|
3865
|
+
lines.push(` * @property {${jsType}} ${optional}${name}${optionalEnd}`);
|
|
2140
3866
|
}
|
|
2141
3867
|
return lines.join("\n");
|
|
2142
3868
|
}
|
|
2143
|
-
function
|
|
3869
|
+
function attributeToJSDocType(attr, schema, relativePrefix) {
|
|
2144
3870
|
switch (attr.type) {
|
|
2145
3871
|
case "string":
|
|
2146
3872
|
case "text":
|
|
@@ -2150,7 +3876,7 @@ function attributeToTsType2(attr) {
|
|
|
2150
3876
|
case "uid":
|
|
2151
3877
|
return "string";
|
|
2152
3878
|
case "blocks":
|
|
2153
|
-
return "
|
|
3879
|
+
return "Array<Object>";
|
|
2154
3880
|
case "integer":
|
|
2155
3881
|
case "biginteger":
|
|
2156
3882
|
case "float":
|
|
@@ -2164,130 +3890,116 @@ function attributeToTsType2(attr) {
|
|
|
2164
3890
|
case "timestamp":
|
|
2165
3891
|
return "string";
|
|
2166
3892
|
case "json":
|
|
2167
|
-
return "
|
|
3893
|
+
return "Object";
|
|
2168
3894
|
case "enumeration":
|
|
2169
3895
|
if ("enum" in attr && attr.enum) {
|
|
2170
|
-
return attr.enum.map((v) => `'${v}'`).join("
|
|
3896
|
+
return attr.enum.map((v) => `'${v}'`).join("|");
|
|
2171
3897
|
}
|
|
2172
3898
|
return "string";
|
|
2173
3899
|
case "media":
|
|
2174
3900
|
if ("multiple" in attr && attr.multiple) {
|
|
2175
|
-
return "StrapiMedia[]
|
|
3901
|
+
return `import("${relativePrefix}/shared/utils").StrapiMedia[]`;
|
|
2176
3902
|
}
|
|
2177
|
-
return "StrapiMedia
|
|
3903
|
+
return `import("${relativePrefix}/shared/utils").StrapiMedia|null`;
|
|
2178
3904
|
case "relation":
|
|
2179
3905
|
if ("target" in attr && attr.target) {
|
|
2180
|
-
const targetName =
|
|
3906
|
+
const targetName = attr.target.split(".").pop() || "unknown";
|
|
3907
|
+
const typeName = toPascalCase(targetName);
|
|
3908
|
+
const fileName = toKebabCase(targetName);
|
|
2181
3909
|
const isMany = attr.relation === "oneToMany" || attr.relation === "manyToMany";
|
|
2182
|
-
|
|
3910
|
+
const isCollection = schema.collections.some((c) => c.singularName === targetName);
|
|
3911
|
+
const isSingle = schema.singles.some((s) => s.singularName === targetName);
|
|
3912
|
+
let importPath;
|
|
3913
|
+
if (isCollection) {
|
|
3914
|
+
importPath = `${relativePrefix}/collections/${fileName}/types`;
|
|
3915
|
+
} else if (isSingle) {
|
|
3916
|
+
importPath = `${relativePrefix}/singles/${fileName}/types`;
|
|
3917
|
+
} else {
|
|
3918
|
+
importPath = `${relativePrefix}/collections/${fileName}/types`;
|
|
3919
|
+
}
|
|
3920
|
+
const importType = `import("${importPath}").${typeName}`;
|
|
3921
|
+
return isMany ? `${importType}[]` : `${importType}|null`;
|
|
2183
3922
|
}
|
|
2184
|
-
return "
|
|
3923
|
+
return "Object";
|
|
2185
3924
|
case "component":
|
|
2186
3925
|
if ("component" in attr && attr.component) {
|
|
2187
|
-
const componentName =
|
|
3926
|
+
const componentName = attr.component.split(".").pop() || "unknown";
|
|
3927
|
+
const typeName = toPascalCase(componentName);
|
|
3928
|
+
const fileName = toKebabCase(componentName);
|
|
3929
|
+
const importPath = `${relativePrefix}/components/${fileName}`;
|
|
3930
|
+
const importType = `import("${importPath}").${typeName}`;
|
|
2188
3931
|
if ("repeatable" in attr && attr.repeatable) {
|
|
2189
|
-
return `${
|
|
3932
|
+
return `${importType}[]`;
|
|
2190
3933
|
}
|
|
2191
|
-
return `${
|
|
3934
|
+
return `${importType}|null`;
|
|
2192
3935
|
}
|
|
2193
|
-
return "
|
|
3936
|
+
return "Object";
|
|
2194
3937
|
case "dynamiczone":
|
|
2195
3938
|
if ("components" in attr && attr.components) {
|
|
2196
|
-
const types = attr.components.map((
|
|
2197
|
-
|
|
3939
|
+
const types = attr.components.map((comp) => {
|
|
3940
|
+
const componentName = comp.split(".").pop() || "unknown";
|
|
3941
|
+
const typeName = toPascalCase(componentName);
|
|
3942
|
+
const fileName = toKebabCase(componentName);
|
|
3943
|
+
const importPath = `${relativePrefix}/components/${fileName}`;
|
|
3944
|
+
return `import("${importPath}").${typeName}`;
|
|
3945
|
+
});
|
|
3946
|
+
return `(${types.join("|")})[]`;
|
|
2198
3947
|
}
|
|
2199
|
-
return "
|
|
3948
|
+
return "Object[]";
|
|
2200
3949
|
default:
|
|
2201
|
-
return "
|
|
3950
|
+
return "Object";
|
|
2202
3951
|
}
|
|
2203
3952
|
}
|
|
2204
|
-
function
|
|
3953
|
+
function generateCollectionServiceJSDoc(collection, strapiVersion) {
|
|
2205
3954
|
const typeName = toPascalCase(collection.singularName);
|
|
2206
3955
|
const serviceName = toCamelCase(collection.singularName) + "Service";
|
|
2207
3956
|
const endpoint = collection.pluralName;
|
|
2208
3957
|
const hasSlug = "slug" in collection.attributes;
|
|
2209
3958
|
const { localized, draftAndPublish } = collection;
|
|
2210
3959
|
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 `/**
|
|
3960
|
+
const idParam = isV4 ? "id" : "documentId";
|
|
3961
|
+
const idType = isV4 ? "number" : "string";
|
|
3962
|
+
return `// @ts-check
|
|
3963
|
+
/**
|
|
2268
3964
|
* ${collection.displayName} Service
|
|
2269
3965
|
* ${collection.description || ""}
|
|
2270
3966
|
* Generated by strapi2front
|
|
2271
3967
|
* Strapi version: ${strapiVersion}
|
|
2272
3968
|
*/
|
|
2273
3969
|
|
|
2274
|
-
|
|
3970
|
+
const { collection } = require('../../shared/client');
|
|
2275
3971
|
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
}
|
|
3972
|
+
/**
|
|
3973
|
+
* @typedef {Object} FindManyOptions
|
|
3974
|
+
* @property {import('./types').${typeName}Filters} [filters]
|
|
3975
|
+
* @property {Object} [pagination]
|
|
3976
|
+
* @property {number} [pagination.page]
|
|
3977
|
+
* @property {number} [pagination.pageSize]
|
|
3978
|
+
* @property {number} [pagination.start]
|
|
3979
|
+
* @property {number} [pagination.limit]
|
|
3980
|
+
* @property {string|string[]} [sort]
|
|
3981
|
+
* @property {string|string[]|Record<string, unknown>} [populate]${localized ? "\n * @property {string} [locale]" : ""}${draftAndPublish ? "\n * @property {'draft'|'published'} [status]" : ""}
|
|
3982
|
+
*/
|
|
2279
3983
|
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
}
|
|
3984
|
+
/**
|
|
3985
|
+
* @typedef {Object} FindOneOptions
|
|
3986
|
+
* @property {string|string[]|Record<string, unknown>} [populate]${localized ? "\n * @property {string} [locale]" : ""}${draftAndPublish ? "\n * @property {'draft'|'published'} [status]" : ""}
|
|
3987
|
+
*/
|
|
2283
3988
|
|
|
2284
|
-
|
|
2285
|
-
const ${toCamelCase(collection.singularName)}Collection = collection
|
|
3989
|
+
/** @type {ReturnType<typeof collection<import('./types').${typeName}>>} */
|
|
3990
|
+
const ${toCamelCase(collection.singularName)}Collection = collection('${endpoint}');
|
|
2286
3991
|
|
|
2287
|
-
|
|
2288
|
-
|
|
3992
|
+
const ${serviceName} = {
|
|
3993
|
+
/**
|
|
3994
|
+
* @param {FindManyOptions} [options]
|
|
3995
|
+
* @returns {Promise<{ data: import('./types').${typeName}[], pagination: import('../../shared/utils').StrapiPagination }>}
|
|
3996
|
+
*/
|
|
3997
|
+
async findMany(options = {}) {
|
|
2289
3998
|
const response = await ${toCamelCase(collection.singularName)}Collection.find({
|
|
2290
|
-
|
|
3999
|
+
filters: options.filters,
|
|
4000
|
+
pagination: options.pagination,
|
|
4001
|
+
sort: options.sort,
|
|
4002
|
+
populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
|
|
2291
4003
|
});
|
|
2292
4004
|
|
|
2293
4005
|
return {
|
|
@@ -2296,8 +4008,13 @@ ${findParams}
|
|
|
2296
4008
|
};
|
|
2297
4009
|
},
|
|
2298
4010
|
|
|
2299
|
-
|
|
2300
|
-
|
|
4011
|
+
/**
|
|
4012
|
+
* @param {Omit<FindManyOptions, 'pagination'>} [options]
|
|
4013
|
+
* @returns {Promise<import('./types').${typeName}[]>}
|
|
4014
|
+
*/
|
|
4015
|
+
async findAll(options = {}) {
|
|
4016
|
+
/** @type {import('./types').${typeName}[]} */
|
|
4017
|
+
const allItems = [];
|
|
2301
4018
|
let page = 1;
|
|
2302
4019
|
let hasMore = true;
|
|
2303
4020
|
|
|
@@ -2315,10 +4032,15 @@ ${findParams}
|
|
|
2315
4032
|
return allItems;
|
|
2316
4033
|
},
|
|
2317
4034
|
|
|
2318
|
-
|
|
4035
|
+
/**
|
|
4036
|
+
* @param {${idType}} ${idParam}
|
|
4037
|
+
* @param {FindOneOptions} [options]
|
|
4038
|
+
* @returns {Promise<import('./types').${typeName}|null>}
|
|
4039
|
+
*/
|
|
4040
|
+
async findOne(${idParam}, options = {}) {
|
|
2319
4041
|
try {
|
|
2320
|
-
const response = await ${toCamelCase(collection.singularName)}Collection.findOne(${
|
|
2321
|
-
${
|
|
4042
|
+
const response = await ${toCamelCase(collection.singularName)}Collection.findOne(${idParam}, {
|
|
4043
|
+
populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
|
|
2322
4044
|
});
|
|
2323
4045
|
|
|
2324
4046
|
return response.data;
|
|
@@ -2330,9 +4052,14 @@ ${findOneParams}
|
|
|
2330
4052
|
}
|
|
2331
4053
|
},
|
|
2332
4054
|
${hasSlug ? `
|
|
2333
|
-
|
|
4055
|
+
/**
|
|
4056
|
+
* @param {string} slug
|
|
4057
|
+
* @param {FindOneOptions} [options]
|
|
4058
|
+
* @returns {Promise<import('./types').${typeName}|null>}
|
|
4059
|
+
*/
|
|
4060
|
+
async findBySlug(slug, options = {}) {
|
|
2334
4061
|
const { data } = await this.findMany({
|
|
2335
|
-
filters: { slug: { $eq: slug } }
|
|
4062
|
+
filters: /** @type {import('./types').${typeName}Filters} */ ({ slug: { $eq: slug } }),
|
|
2336
4063
|
pagination: { pageSize: 1 },
|
|
2337
4064
|
populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
|
|
2338
4065
|
});
|
|
@@ -2340,21 +4067,38 @@ ${hasSlug ? `
|
|
|
2340
4067
|
return data[0] || null;
|
|
2341
4068
|
},
|
|
2342
4069
|
` : ""}
|
|
2343
|
-
|
|
4070
|
+
/**
|
|
4071
|
+
* @param {Partial<import('./types').${typeName}>} data
|
|
4072
|
+
* @returns {Promise<import('./types').${typeName}>}
|
|
4073
|
+
*/
|
|
4074
|
+
async create(data) {
|
|
2344
4075
|
const response = await ${toCamelCase(collection.singularName)}Collection.create({ data });
|
|
2345
4076
|
return response.data;
|
|
2346
4077
|
},
|
|
2347
4078
|
|
|
2348
|
-
|
|
2349
|
-
|
|
4079
|
+
/**
|
|
4080
|
+
* @param {${idType}} ${idParam}
|
|
4081
|
+
* @param {Partial<import('./types').${typeName}>} data
|
|
4082
|
+
* @returns {Promise<import('./types').${typeName}>}
|
|
4083
|
+
*/
|
|
4084
|
+
async update(${idParam}, data) {
|
|
4085
|
+
const response = await ${toCamelCase(collection.singularName)}Collection.update(${idParam}, { data });
|
|
2350
4086
|
return response.data;
|
|
2351
4087
|
},
|
|
2352
4088
|
|
|
2353
|
-
|
|
2354
|
-
|
|
4089
|
+
/**
|
|
4090
|
+
* @param {${idType}} ${idParam}
|
|
4091
|
+
* @returns {Promise<void>}
|
|
4092
|
+
*/
|
|
4093
|
+
async delete(${idParam}) {
|
|
4094
|
+
await ${toCamelCase(collection.singularName)}Collection.delete(${idParam});
|
|
2355
4095
|
},
|
|
2356
4096
|
|
|
2357
|
-
|
|
4097
|
+
/**
|
|
4098
|
+
* @param {import('./types').${typeName}Filters} [filters]
|
|
4099
|
+
* @returns {Promise<number>}
|
|
4100
|
+
*/
|
|
4101
|
+
async count(filters) {
|
|
2358
4102
|
const { pagination } = await this.findMany({
|
|
2359
4103
|
filters,
|
|
2360
4104
|
pagination: { pageSize: 1 },
|
|
@@ -2363,61 +4107,42 @@ ${hasSlug ? `
|
|
|
2363
4107
|
return pagination.total;
|
|
2364
4108
|
},
|
|
2365
4109
|
};
|
|
4110
|
+
|
|
4111
|
+
module.exports = { ${serviceName} };
|
|
2366
4112
|
`;
|
|
2367
4113
|
}
|
|
2368
|
-
function
|
|
4114
|
+
function generateSingleServiceJSDoc(single, strapiVersion) {
|
|
2369
4115
|
const typeName = toPascalCase(single.singularName);
|
|
2370
4116
|
const serviceName = toCamelCase(single.singularName) + "Service";
|
|
2371
4117
|
const endpoint = single.singularName;
|
|
2372
4118
|
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 `/**
|
|
4119
|
+
return `// @ts-check
|
|
4120
|
+
/**
|
|
2401
4121
|
* ${single.displayName} Service (Single Type)
|
|
2402
4122
|
* ${single.description || ""}
|
|
2403
4123
|
* Generated by strapi2front
|
|
2404
4124
|
* Strapi version: ${strapiVersion}
|
|
2405
4125
|
*/
|
|
2406
4126
|
|
|
2407
|
-
|
|
4127
|
+
const { single } = require('../../shared/client');
|
|
2408
4128
|
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
}
|
|
4129
|
+
/**
|
|
4130
|
+
* @typedef {Object} FindOptions
|
|
4131
|
+
* @property {string|string[]|Record<string, unknown>} [populate]${localized ? "\n * @property {string} [locale]" : ""}${draftAndPublish ? "\n * @property {'draft'|'published'} [status]" : ""}
|
|
4132
|
+
*/
|
|
2412
4133
|
|
|
2413
|
-
|
|
2414
|
-
const ${toCamelCase(single.singularName)}Single = single
|
|
4134
|
+
/** @type {ReturnType<typeof single<import('./types').${typeName}>>} */
|
|
4135
|
+
const ${toCamelCase(single.singularName)}Single = single('${endpoint}');
|
|
2415
4136
|
|
|
2416
|
-
|
|
2417
|
-
|
|
4137
|
+
const ${serviceName} = {
|
|
4138
|
+
/**
|
|
4139
|
+
* @param {FindOptions} [options]
|
|
4140
|
+
* @returns {Promise<import('./types').${typeName}|null>}
|
|
4141
|
+
*/
|
|
4142
|
+
async find(options = {}) {
|
|
2418
4143
|
try {
|
|
2419
4144
|
const response = await ${toCamelCase(single.singularName)}Single.find({
|
|
2420
|
-
${
|
|
4145
|
+
populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
|
|
2421
4146
|
});
|
|
2422
4147
|
|
|
2423
4148
|
return response.data;
|
|
@@ -2429,96 +4154,60 @@ ${findParams}
|
|
|
2429
4154
|
}
|
|
2430
4155
|
},
|
|
2431
4156
|
|
|
2432
|
-
|
|
4157
|
+
/**
|
|
4158
|
+
* @param {Partial<import('./types').${typeName}>} data
|
|
4159
|
+
* @returns {Promise<import('./types').${typeName}>}
|
|
4160
|
+
*/
|
|
4161
|
+
async update(data) {
|
|
2433
4162
|
const response = await ${toCamelCase(single.singularName)}Single.update({ data });
|
|
2434
4163
|
return response.data;
|
|
2435
4164
|
},
|
|
2436
4165
|
|
|
2437
|
-
|
|
4166
|
+
/**
|
|
4167
|
+
* @returns {Promise<void>}
|
|
4168
|
+
*/
|
|
4169
|
+
async delete() {
|
|
2438
4170
|
await ${toCamelCase(single.singularName)}Single.delete();
|
|
2439
4171
|
},
|
|
2440
4172
|
};
|
|
4173
|
+
|
|
4174
|
+
module.exports = { ${serviceName} };
|
|
2441
4175
|
`;
|
|
2442
4176
|
}
|
|
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
4177
|
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
4178
|
+
// src/frameworks/nextjs/actions.ts
|
|
4179
|
+
async function generateNextJsActions(_schema, _options) {
|
|
4180
|
+
throw new Error(
|
|
4181
|
+
"Next.js Server Actions generator is not yet implemented. Coming soon! For now, you can use the generated services directly."
|
|
4182
|
+
);
|
|
4183
|
+
}
|
|
4184
|
+
function isNextJsActionsSupported(nextVersion) {
|
|
4185
|
+
if (!nextVersion) return false;
|
|
4186
|
+
const match = nextVersion.replace(/^[\^~]/, "").match(/^(\d+)/);
|
|
4187
|
+
const majorVersion = match ? parseInt(match[1], 10) : null;
|
|
4188
|
+
return majorVersion !== null && majorVersion >= 14;
|
|
4189
|
+
}
|
|
2497
4190
|
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
4191
|
+
// src/frameworks/nuxt/server-routes.ts
|
|
4192
|
+
async function generateNuxtServerRoutes(_schema, _options) {
|
|
4193
|
+
throw new Error(
|
|
4194
|
+
"Nuxt Server Routes generator is not yet implemented. Coming soon! For now, you can use the generated services directly."
|
|
4195
|
+
);
|
|
4196
|
+
}
|
|
4197
|
+
function isNuxtServerRoutesSupported(nuxtVersion) {
|
|
4198
|
+
if (!nuxtVersion) return false;
|
|
4199
|
+
const match = nuxtVersion.replace(/^[\^~]/, "").match(/^(\d+)/);
|
|
4200
|
+
const majorVersion = match ? parseInt(match[1], 10) : null;
|
|
4201
|
+
return majorVersion !== null && majorVersion >= 3;
|
|
4202
|
+
}
|
|
2508
4203
|
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
await ${serviceName}.delete(input.${idParamName});
|
|
2515
|
-
return { success: true };
|
|
2516
|
-
},
|
|
2517
|
-
}),
|
|
4204
|
+
// src/frameworks/index.ts
|
|
4205
|
+
var frameworkSupport = {
|
|
4206
|
+
astro: { status: "stable", minVersion: "4.0.0" },
|
|
4207
|
+
nextjs: { status: "coming-soon", minVersion: "14.0.0" },
|
|
4208
|
+
nuxt: { status: "coming-soon", minVersion: "3.0.0" }
|
|
2518
4209
|
};
|
|
2519
|
-
`;
|
|
2520
|
-
}
|
|
2521
4210
|
|
|
2522
|
-
export { deleteFile, ensureDir, fileExists, formatCode, formatJson, generateActions, generateByFeature, generateClient, generateLocales, generateServices, generateTypes, listFiles, pluralize, readFile, toCamelCase, toKebabCase, toPascalCase, writeFile };
|
|
4211
|
+
export { deleteFile, ensureDir, fileExists, formatCode, formatJson, frameworkSupport, generateActions, generateAstroActions, generateByFeature, generateClient, generateJSDocServices, generateJSDocTypes, generateLocales, generateNextJsActions, generateNuxtServerRoutes, generateServices, generateTypeScriptTypes, generateTypes, isAstroActionsSupported, isNextJsActionsSupported, isNuxtServerRoutesSupported, listFiles, pluralize, readFile, toCamelCase, toKebabCase, toPascalCase, writeFile };
|
|
2523
4212
|
//# sourceMappingURL=index.js.map
|
|
2524
4213
|
//# sourceMappingURL=index.js.map
|