@strapi2front/generators 0.1.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +231 -5
- package/dist/index.js +2817 -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,24 +1775,34 @@ export const ${actionsName} = {
|
|
|
1152
1775
|
};
|
|
1153
1776
|
`;
|
|
1154
1777
|
}
|
|
1778
|
+
|
|
1779
|
+
// src/actions/generator.ts
|
|
1780
|
+
async function generateActions(schema, options) {
|
|
1781
|
+
return generateAstroActions(schema, {
|
|
1782
|
+
outputDir: options.outputDir,
|
|
1783
|
+
servicesImportPath: options.servicesImportPath,
|
|
1784
|
+
strapiVersion: options.strapiVersion
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1155
1787
|
async function generateClient(options) {
|
|
1156
|
-
const { outputDir, strapiVersion = "v5" } = options;
|
|
1788
|
+
const { outputDir, strapiVersion = "v5", apiPrefix = "/api" } = options;
|
|
1157
1789
|
const generatedFiles = [];
|
|
1158
1790
|
await ensureDir(outputDir);
|
|
1159
|
-
const filePath =
|
|
1160
|
-
const content = generateClientFile(strapiVersion);
|
|
1791
|
+
const filePath = path9.join(outputDir, "client.ts");
|
|
1792
|
+
const content = generateClientFile(strapiVersion, apiPrefix);
|
|
1161
1793
|
await writeFile(filePath, await formatCode(content));
|
|
1162
1794
|
generatedFiles.push(filePath);
|
|
1163
1795
|
return generatedFiles;
|
|
1164
1796
|
}
|
|
1165
|
-
function generateClientFile(strapiVersion) {
|
|
1797
|
+
function generateClientFile(strapiVersion, apiPrefix) {
|
|
1166
1798
|
const isV4 = strapiVersion === "v4";
|
|
1167
1799
|
if (isV4) {
|
|
1168
|
-
return generateV4ClientFile();
|
|
1800
|
+
return generateV4ClientFile(apiPrefix);
|
|
1169
1801
|
}
|
|
1170
|
-
return generateV5ClientFile();
|
|
1802
|
+
return generateV5ClientFile(apiPrefix);
|
|
1171
1803
|
}
|
|
1172
|
-
function generateV5ClientFile() {
|
|
1804
|
+
function generateV5ClientFile(apiPrefix) {
|
|
1805
|
+
const normalizedPrefix = apiPrefix.startsWith("/") ? apiPrefix : "/" + apiPrefix;
|
|
1173
1806
|
return `/**
|
|
1174
1807
|
* Strapi Client (v5)
|
|
1175
1808
|
* Generated by strapi2front
|
|
@@ -1180,9 +1813,11 @@ import Strapi from 'strapi-sdk-js';
|
|
|
1180
1813
|
// Initialize the Strapi client
|
|
1181
1814
|
const strapiUrl = import.meta.env.STRAPI_URL || process.env.STRAPI_URL || 'http://localhost:1337';
|
|
1182
1815
|
const strapiToken = import.meta.env.STRAPI_TOKEN || process.env.STRAPI_TOKEN;
|
|
1816
|
+
const strapiApiPrefix = import.meta.env.STRAPI_API_PREFIX || process.env.STRAPI_API_PREFIX || '${normalizedPrefix}';
|
|
1183
1817
|
|
|
1184
1818
|
export const strapi = new Strapi({
|
|
1185
1819
|
url: strapiUrl,
|
|
1820
|
+
prefix: strapiApiPrefix,
|
|
1186
1821
|
axiosOptions: {
|
|
1187
1822
|
headers: strapiToken ? {
|
|
1188
1823
|
Authorization: \`Bearer \${strapiToken}\`,
|
|
@@ -1267,7 +1902,8 @@ export function single<T>(singularName: string) {
|
|
|
1267
1902
|
}
|
|
1268
1903
|
`;
|
|
1269
1904
|
}
|
|
1270
|
-
function generateV4ClientFile() {
|
|
1905
|
+
function generateV4ClientFile(apiPrefix) {
|
|
1906
|
+
const normalizedPrefix = apiPrefix.startsWith("/") ? apiPrefix : "/" + apiPrefix;
|
|
1271
1907
|
return `/**
|
|
1272
1908
|
* Strapi Client (v4)
|
|
1273
1909
|
* Generated by strapi2front
|
|
@@ -1278,9 +1914,11 @@ import Strapi from 'strapi-sdk-js';
|
|
|
1278
1914
|
// Initialize the Strapi client
|
|
1279
1915
|
const strapiUrl = import.meta.env.STRAPI_URL || process.env.STRAPI_URL || 'http://localhost:1337';
|
|
1280
1916
|
const strapiToken = import.meta.env.STRAPI_TOKEN || process.env.STRAPI_TOKEN;
|
|
1917
|
+
const strapiApiPrefix = import.meta.env.STRAPI_API_PREFIX || process.env.STRAPI_API_PREFIX || '${normalizedPrefix}';
|
|
1281
1918
|
|
|
1282
1919
|
export const strapi = new Strapi({
|
|
1283
1920
|
url: strapiUrl,
|
|
1921
|
+
prefix: strapiApiPrefix,
|
|
1284
1922
|
axiosOptions: {
|
|
1285
1923
|
headers: strapiToken ? {
|
|
1286
1924
|
Authorization: \`Bearer \${strapiToken}\`,
|
|
@@ -1420,7 +2058,7 @@ async function generateLocales(locales, options) {
|
|
|
1420
2058
|
const { outputDir } = options;
|
|
1421
2059
|
const generatedFiles = [];
|
|
1422
2060
|
await ensureDir(outputDir);
|
|
1423
|
-
const filePath =
|
|
2061
|
+
const filePath = path9.join(outputDir, "locales.ts");
|
|
1424
2062
|
const content = generateLocalesFile(locales);
|
|
1425
2063
|
await writeFile(filePath, await formatCode(content));
|
|
1426
2064
|
generatedFiles.push(filePath);
|
|
@@ -1468,198 +2106,1191 @@ export type Locale = ${localeCodes};
|
|
|
1468
2106
|
*/
|
|
1469
2107
|
export const defaultLocale: Locale = '${defaultLocale}';
|
|
1470
2108
|
|
|
1471
|
-
/**
|
|
1472
|
-
* Locale display names
|
|
1473
|
-
*/
|
|
1474
|
-
export const localeNames: Record<Locale, string> = {
|
|
1475
|
-
${localeNames}
|
|
1476
|
-
};
|
|
2109
|
+
/**
|
|
2110
|
+
* Locale display names
|
|
2111
|
+
*/
|
|
2112
|
+
export const localeNames: Record<Locale, string> = {
|
|
2113
|
+
${localeNames}
|
|
2114
|
+
};
|
|
2115
|
+
|
|
2116
|
+
/**
|
|
2117
|
+
* Check if a string is a valid locale
|
|
2118
|
+
*/
|
|
2119
|
+
export function isValidLocale(code: string): code is Locale {
|
|
2120
|
+
return locales.includes(code as Locale);
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
/**
|
|
2124
|
+
* Get locale name by code
|
|
2125
|
+
*/
|
|
2126
|
+
export function getLocaleName(code: Locale): string {
|
|
2127
|
+
return localeNames[code] || code;
|
|
2128
|
+
}
|
|
2129
|
+
`;
|
|
2130
|
+
}
|
|
2131
|
+
async function generateByFeature(schema, locales, options) {
|
|
2132
|
+
const {
|
|
2133
|
+
outputDir,
|
|
2134
|
+
features,
|
|
2135
|
+
blocksRendererInstalled = false,
|
|
2136
|
+
strapiVersion = "v5",
|
|
2137
|
+
apiPrefix = "/api",
|
|
2138
|
+
outputFormat = "typescript"
|
|
2139
|
+
} = options;
|
|
2140
|
+
const generatedFiles = [];
|
|
2141
|
+
const ext = outputFormat === "jsdoc" ? "js" : "ts";
|
|
2142
|
+
await ensureDir(path9.join(outputDir, "collections"));
|
|
2143
|
+
await ensureDir(path9.join(outputDir, "singles"));
|
|
2144
|
+
await ensureDir(path9.join(outputDir, "components"));
|
|
2145
|
+
await ensureDir(path9.join(outputDir, "shared"));
|
|
2146
|
+
const sharedDir = path9.join(outputDir, "shared");
|
|
2147
|
+
const utilsPath = path9.join(sharedDir, `utils.${ext}`);
|
|
2148
|
+
const utilsContent = outputFormat === "jsdoc" ? generateUtilityTypesJSDoc(blocksRendererInstalled, strapiVersion) : generateUtilityTypes3(blocksRendererInstalled, strapiVersion);
|
|
2149
|
+
await writeFile(utilsPath, await formatCode(utilsContent));
|
|
2150
|
+
generatedFiles.push(utilsPath);
|
|
2151
|
+
const clientPath = path9.join(sharedDir, `client.${ext}`);
|
|
2152
|
+
const clientContent = outputFormat === "jsdoc" ? generateClientJSDoc(strapiVersion, apiPrefix) : generateClient2(strapiVersion, apiPrefix);
|
|
2153
|
+
await writeFile(clientPath, await formatCode(clientContent));
|
|
2154
|
+
generatedFiles.push(clientPath);
|
|
2155
|
+
const localesPath = path9.join(sharedDir, `locales.${ext}`);
|
|
2156
|
+
const localesContent = outputFormat === "jsdoc" ? generateLocalesFileJSDoc(locales) : generateLocalesFile2(locales);
|
|
2157
|
+
await writeFile(localesPath, await formatCode(localesContent));
|
|
2158
|
+
generatedFiles.push(localesPath);
|
|
2159
|
+
for (const collection of schema.collections) {
|
|
2160
|
+
const featureDir = path9.join(outputDir, "collections", toKebabCase(collection.singularName));
|
|
2161
|
+
await ensureDir(featureDir);
|
|
2162
|
+
if (features.types) {
|
|
2163
|
+
const typesPath = path9.join(featureDir, `types.${ext}`);
|
|
2164
|
+
const content = outputFormat === "jsdoc" ? generateCollectionTypesJSDoc(collection, schema) : generateCollectionTypes(collection, schema);
|
|
2165
|
+
await writeFile(typesPath, await formatCode(content));
|
|
2166
|
+
generatedFiles.push(typesPath);
|
|
2167
|
+
}
|
|
2168
|
+
if (features.services) {
|
|
2169
|
+
const servicePath = path9.join(featureDir, `service.${ext}`);
|
|
2170
|
+
const content = outputFormat === "jsdoc" ? generateCollectionServiceJSDoc(collection, strapiVersion) : generateCollectionService3(collection, strapiVersion);
|
|
2171
|
+
await writeFile(servicePath, await formatCode(content));
|
|
2172
|
+
generatedFiles.push(servicePath);
|
|
2173
|
+
}
|
|
2174
|
+
if (features.actions) {
|
|
2175
|
+
const actionsPath = path9.join(featureDir, `actions.${ext}`);
|
|
2176
|
+
await writeFile(actionsPath, await formatCode(generateCollectionActions2(collection, strapiVersion)));
|
|
2177
|
+
generatedFiles.push(actionsPath);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
for (const single of schema.singles) {
|
|
2181
|
+
const featureDir = path9.join(outputDir, "singles", toKebabCase(single.singularName));
|
|
2182
|
+
await ensureDir(featureDir);
|
|
2183
|
+
if (features.types) {
|
|
2184
|
+
const typesPath = path9.join(featureDir, `types.${ext}`);
|
|
2185
|
+
const content = outputFormat === "jsdoc" ? generateSingleTypesJSDoc(single, schema) : generateSingleTypes(single, schema);
|
|
2186
|
+
await writeFile(typesPath, await formatCode(content));
|
|
2187
|
+
generatedFiles.push(typesPath);
|
|
2188
|
+
}
|
|
2189
|
+
if (features.services) {
|
|
2190
|
+
const servicePath = path9.join(featureDir, `service.${ext}`);
|
|
2191
|
+
const content = outputFormat === "jsdoc" ? generateSingleServiceJSDoc(single, strapiVersion) : generateSingleService3(single, strapiVersion);
|
|
2192
|
+
await writeFile(servicePath, await formatCode(content));
|
|
2193
|
+
generatedFiles.push(servicePath);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
for (const component of schema.components) {
|
|
2197
|
+
const componentPath = path9.join(outputDir, "components", `${toKebabCase(component.name)}.${ext}`);
|
|
2198
|
+
const content = outputFormat === "jsdoc" ? generateComponentTypesJSDoc(component, schema) : generateComponentTypes(component, schema);
|
|
2199
|
+
await writeFile(componentPath, await formatCode(content));
|
|
2200
|
+
generatedFiles.push(componentPath);
|
|
2201
|
+
}
|
|
2202
|
+
return generatedFiles;
|
|
2203
|
+
}
|
|
2204
|
+
function generateUtilityTypes3(blocksRendererInstalled, strapiVersion) {
|
|
2205
|
+
const isV4 = strapiVersion === "v4";
|
|
2206
|
+
const blocksContentType = isV4 ? `/**
|
|
2207
|
+
* Rich text content (Strapi v4)
|
|
2208
|
+
* Can be markdown string or custom JSON structure
|
|
2209
|
+
*/
|
|
2210
|
+
export type RichTextContent = string;` : blocksRendererInstalled ? `/**
|
|
2211
|
+
* Blocks content type (Strapi v5 rich text)
|
|
2212
|
+
* Re-exported from @strapi/blocks-react-renderer
|
|
2213
|
+
*/
|
|
2214
|
+
export type { BlocksContent } from '@strapi/blocks-react-renderer';` : `/**
|
|
2215
|
+
* Blocks content type (Strapi v5 rich text)
|
|
2216
|
+
*
|
|
2217
|
+
* For full type support and rendering, install:
|
|
2218
|
+
* npm install @strapi/blocks-react-renderer
|
|
2219
|
+
*
|
|
2220
|
+
* Then re-run: npx strapi2front sync
|
|
2221
|
+
*/
|
|
2222
|
+
export type BlocksContent = unknown[];`;
|
|
2223
|
+
const baseEntity = isV4 ? `export interface StrapiBaseEntity {
|
|
2224
|
+
id: number;
|
|
2225
|
+
createdAt: string;
|
|
2226
|
+
updatedAt: string;
|
|
2227
|
+
publishedAt: string | null;
|
|
2228
|
+
}` : `export interface StrapiBaseEntity {
|
|
2229
|
+
id: number;
|
|
2230
|
+
documentId: string;
|
|
2231
|
+
createdAt: string;
|
|
2232
|
+
updatedAt: string;
|
|
2233
|
+
publishedAt: string | null;
|
|
2234
|
+
}`;
|
|
2235
|
+
const mediaType = isV4 ? `export interface StrapiMedia {
|
|
2236
|
+
id: number;
|
|
2237
|
+
name: string;
|
|
2238
|
+
alternativeText: string | null;
|
|
2239
|
+
caption: string | null;
|
|
2240
|
+
width: number;
|
|
2241
|
+
height: number;
|
|
2242
|
+
formats: {
|
|
2243
|
+
thumbnail?: StrapiMediaFormat;
|
|
2244
|
+
small?: StrapiMediaFormat;
|
|
2245
|
+
medium?: StrapiMediaFormat;
|
|
2246
|
+
large?: StrapiMediaFormat;
|
|
2247
|
+
} | null;
|
|
2248
|
+
hash: string;
|
|
2249
|
+
ext: string;
|
|
2250
|
+
mime: string;
|
|
2251
|
+
size: number;
|
|
2252
|
+
url: string;
|
|
2253
|
+
previewUrl: string | null;
|
|
2254
|
+
provider: string;
|
|
2255
|
+
createdAt: string;
|
|
2256
|
+
updatedAt: string;
|
|
2257
|
+
}` : `export interface StrapiMedia {
|
|
2258
|
+
id: number;
|
|
2259
|
+
documentId: string;
|
|
2260
|
+
name: string;
|
|
2261
|
+
alternativeText: string | null;
|
|
2262
|
+
caption: string | null;
|
|
2263
|
+
width: number;
|
|
2264
|
+
height: number;
|
|
2265
|
+
formats: {
|
|
2266
|
+
thumbnail?: StrapiMediaFormat;
|
|
2267
|
+
small?: StrapiMediaFormat;
|
|
2268
|
+
medium?: StrapiMediaFormat;
|
|
2269
|
+
large?: StrapiMediaFormat;
|
|
2270
|
+
} | null;
|
|
2271
|
+
hash: string;
|
|
2272
|
+
ext: string;
|
|
2273
|
+
mime: string;
|
|
2274
|
+
size: number;
|
|
2275
|
+
url: string;
|
|
2276
|
+
previewUrl: string | null;
|
|
2277
|
+
provider: string;
|
|
2278
|
+
createdAt: string;
|
|
2279
|
+
updatedAt: string;
|
|
2280
|
+
}`;
|
|
2281
|
+
const v4RawResponseTypes = isV4 ? `
|
|
2282
|
+
|
|
2283
|
+
/**
|
|
2284
|
+
* Strapi v4 raw API response (with nested attributes)
|
|
2285
|
+
*/
|
|
2286
|
+
export interface StrapiV4RawItem<T> {
|
|
2287
|
+
id: number;
|
|
2288
|
+
attributes: Omit<T, 'id'>;
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
export interface StrapiV4RawResponse<T> {
|
|
2292
|
+
data: StrapiV4RawItem<T>;
|
|
2293
|
+
meta: Record<string, unknown>;
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
export interface StrapiV4RawListResponse<T> {
|
|
2297
|
+
data: StrapiV4RawItem<T>[];
|
|
2298
|
+
meta: {
|
|
2299
|
+
pagination: StrapiPagination;
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
/**
|
|
2304
|
+
* Flatten Strapi v4 response item
|
|
2305
|
+
*/
|
|
2306
|
+
export function flattenV4Response<T>(item: StrapiV4RawItem<T>): T {
|
|
2307
|
+
return { id: item.id, ...item.attributes } as T;
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
/**
|
|
2311
|
+
* Flatten Strapi v4 list response
|
|
2312
|
+
*/
|
|
2313
|
+
export function flattenV4ListResponse<T>(items: StrapiV4RawItem<T>[]): T[] {
|
|
2314
|
+
return items.map(item => flattenV4Response<T>(item));
|
|
2315
|
+
}` : "";
|
|
2316
|
+
return `/**
|
|
2317
|
+
* Strapi utility types
|
|
2318
|
+
* Generated by strapi2front
|
|
2319
|
+
* Strapi version: ${strapiVersion}
|
|
2320
|
+
*/
|
|
2321
|
+
|
|
2322
|
+
${mediaType}
|
|
2323
|
+
|
|
2324
|
+
export interface StrapiMediaFormat {
|
|
2325
|
+
name: string;
|
|
2326
|
+
hash: string;
|
|
2327
|
+
ext: string;
|
|
2328
|
+
mime: string;
|
|
2329
|
+
width: number;
|
|
2330
|
+
height: number;
|
|
2331
|
+
size: number;
|
|
2332
|
+
url: string;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
export interface StrapiPagination {
|
|
2336
|
+
page: number;
|
|
2337
|
+
pageSize: number;
|
|
2338
|
+
pageCount: number;
|
|
2339
|
+
total: number;
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
export interface StrapiResponse<T> {
|
|
2343
|
+
data: T;
|
|
2344
|
+
meta: {
|
|
2345
|
+
pagination?: StrapiPagination;
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
export interface StrapiListResponse<T> {
|
|
2350
|
+
data: T[];
|
|
2351
|
+
meta: {
|
|
2352
|
+
pagination: StrapiPagination;
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
${baseEntity}
|
|
2357
|
+
${v4RawResponseTypes}
|
|
2358
|
+
${blocksContentType}
|
|
2359
|
+
`;
|
|
2360
|
+
}
|
|
2361
|
+
function generateClient2(strapiVersion, apiPrefix = "/api") {
|
|
2362
|
+
const isV4 = strapiVersion === "v4";
|
|
2363
|
+
const normalizedPrefix = apiPrefix.startsWith("/") ? apiPrefix : "/" + apiPrefix;
|
|
2364
|
+
if (isV4) {
|
|
2365
|
+
return `/**
|
|
2366
|
+
* Strapi Client (v4)
|
|
2367
|
+
* Generated by strapi2front
|
|
2368
|
+
*/
|
|
2369
|
+
|
|
2370
|
+
import Strapi from 'strapi-sdk-js';
|
|
2371
|
+
import type { StrapiPagination } from './utils';
|
|
2372
|
+
|
|
2373
|
+
// Initialize the Strapi client
|
|
2374
|
+
const strapiUrl = import.meta.env.STRAPI_URL || process.env.STRAPI_URL || 'http://localhost:1337';
|
|
2375
|
+
const strapiToken = import.meta.env.STRAPI_TOKEN || process.env.STRAPI_TOKEN;
|
|
2376
|
+
const strapiApiPrefix = import.meta.env.STRAPI_API_PREFIX || process.env.STRAPI_API_PREFIX || '${normalizedPrefix}';
|
|
2377
|
+
|
|
2378
|
+
export const strapi = new Strapi({
|
|
2379
|
+
url: strapiUrl,
|
|
2380
|
+
prefix: strapiApiPrefix,
|
|
2381
|
+
axiosOptions: {
|
|
2382
|
+
headers: strapiToken ? {
|
|
2383
|
+
Authorization: \`Bearer \${strapiToken}\`,
|
|
2384
|
+
} : {},
|
|
2385
|
+
},
|
|
2386
|
+
});
|
|
2387
|
+
|
|
2388
|
+
// Default pagination for fallback
|
|
2389
|
+
const defaultPagination: StrapiPagination = {
|
|
2390
|
+
page: 1,
|
|
2391
|
+
pageSize: 25,
|
|
2392
|
+
pageCount: 1,
|
|
2393
|
+
total: 0,
|
|
2394
|
+
};
|
|
2395
|
+
|
|
2396
|
+
// Strapi v4 raw response types (with nested attributes)
|
|
2397
|
+
interface StrapiV4RawItem<T> {
|
|
2398
|
+
id: number;
|
|
2399
|
+
attributes: Omit<T, 'id'>;
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
interface StrapiV4RawListResponse<T> {
|
|
2403
|
+
data: StrapiV4RawItem<T>[];
|
|
2404
|
+
meta: {
|
|
2405
|
+
pagination?: StrapiPagination;
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
interface StrapiV4RawSingleResponse<T> {
|
|
2410
|
+
data: StrapiV4RawItem<T>;
|
|
2411
|
+
meta?: Record<string, unknown>;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
/**
|
|
2415
|
+
* Flatten a Strapi v4 response item (merges id with attributes)
|
|
2416
|
+
*/
|
|
2417
|
+
function flattenItem<T>(item: StrapiV4RawItem<T>): T {
|
|
2418
|
+
return { id: item.id, ...item.attributes } as T;
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
/**
|
|
2422
|
+
* Recursively flatten nested relations in Strapi v4 response
|
|
2423
|
+
*/
|
|
2424
|
+
function flattenRelations<T>(data: T): T {
|
|
2425
|
+
if (data === null || data === undefined) return data;
|
|
2426
|
+
if (Array.isArray(data)) {
|
|
2427
|
+
return data.map(item => flattenRelations(item)) as unknown as T;
|
|
2428
|
+
}
|
|
2429
|
+
if (typeof data === 'object') {
|
|
2430
|
+
const result: Record<string, unknown> = {};
|
|
2431
|
+
for (const [key, value] of Object.entries(data as Record<string, unknown>)) {
|
|
2432
|
+
// Check if this is a Strapi v4 relation response { data: { id, attributes } }
|
|
2433
|
+
if (value && typeof value === 'object' && 'data' in value) {
|
|
2434
|
+
const relationData = (value as { data: unknown }).data;
|
|
2435
|
+
if (relationData === null) {
|
|
2436
|
+
result[key] = null;
|
|
2437
|
+
} else if (Array.isArray(relationData)) {
|
|
2438
|
+
// To-many relation
|
|
2439
|
+
result[key] = relationData.map((item: StrapiV4RawItem<unknown>) =>
|
|
2440
|
+
flattenRelations(flattenItem(item))
|
|
2441
|
+
);
|
|
2442
|
+
} else if (typeof relationData === 'object' && 'id' in relationData && 'attributes' in relationData) {
|
|
2443
|
+
// To-one relation
|
|
2444
|
+
result[key] = flattenRelations(flattenItem(relationData as StrapiV4RawItem<unknown>));
|
|
2445
|
+
} else {
|
|
2446
|
+
result[key] = flattenRelations(value);
|
|
2447
|
+
}
|
|
2448
|
+
} else {
|
|
2449
|
+
result[key] = flattenRelations(value);
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
return result as T;
|
|
2453
|
+
}
|
|
2454
|
+
return data;
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
// Helper to get typed collection
|
|
2458
|
+
export function collection<T>(pluralName: string) {
|
|
2459
|
+
return {
|
|
2460
|
+
async find(params?: Record<string, unknown>): Promise<{ data: T[]; meta: { pagination: StrapiPagination } }> {
|
|
2461
|
+
const response = await strapi.find(pluralName, params) as unknown as StrapiV4RawListResponse<T>;
|
|
2462
|
+
const flattenedData = Array.isArray(response.data)
|
|
2463
|
+
? response.data.map(item => flattenRelations(flattenItem<T>(item)))
|
|
2464
|
+
: [];
|
|
2465
|
+
return {
|
|
2466
|
+
data: flattenedData,
|
|
2467
|
+
meta: {
|
|
2468
|
+
pagination: response.meta?.pagination || defaultPagination,
|
|
2469
|
+
},
|
|
2470
|
+
};
|
|
2471
|
+
},
|
|
2472
|
+
async findOne(id: number | string, params?: Record<string, unknown>): Promise<{ data: T }> {
|
|
2473
|
+
const response = await strapi.findOne(pluralName, String(id), params) as unknown as StrapiV4RawSingleResponse<T>;
|
|
2474
|
+
return { data: flattenRelations(flattenItem<T>(response.data)) };
|
|
2475
|
+
},
|
|
2476
|
+
async create(data: { data: Partial<T> }): Promise<{ data: T }> {
|
|
2477
|
+
const response = await strapi.create(pluralName, data.data as Record<string, unknown>) as unknown as StrapiV4RawSingleResponse<T>;
|
|
2478
|
+
return { data: flattenRelations(flattenItem<T>(response.data)) };
|
|
2479
|
+
},
|
|
2480
|
+
async update(id: number | string, data: { data: Partial<T> }): Promise<{ data: T }> {
|
|
2481
|
+
const response = await strapi.update(pluralName, String(id), data.data as Record<string, unknown>) as unknown as StrapiV4RawSingleResponse<T>;
|
|
2482
|
+
return { data: flattenRelations(flattenItem<T>(response.data)) };
|
|
2483
|
+
},
|
|
2484
|
+
async delete(id: number | string): Promise<void> {
|
|
2485
|
+
await strapi.delete(pluralName, String(id));
|
|
2486
|
+
},
|
|
2487
|
+
};
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
// Helper to get typed single type
|
|
2491
|
+
export function single<T>(singularName: string) {
|
|
2492
|
+
return {
|
|
2493
|
+
async find(params?: Record<string, unknown>): Promise<{ data: T }> {
|
|
2494
|
+
const response = await strapi.find(singularName, params) as unknown as StrapiV4RawSingleResponse<T>;
|
|
2495
|
+
return { data: flattenRelations(flattenItem<T>(response.data)) };
|
|
2496
|
+
},
|
|
2497
|
+
async update(data: { data: Partial<T> }): Promise<{ data: T }> {
|
|
2498
|
+
const response = await strapi.update(singularName, 1 as unknown as string, data.data as Record<string, unknown>) as unknown as StrapiV4RawSingleResponse<T>;
|
|
2499
|
+
return { data: flattenRelations(flattenItem<T>(response.data)) };
|
|
2500
|
+
},
|
|
2501
|
+
async delete(): Promise<void> {
|
|
2502
|
+
await strapi.delete(singularName, 1 as unknown as string);
|
|
2503
|
+
},
|
|
2504
|
+
};
|
|
2505
|
+
}
|
|
2506
|
+
`;
|
|
2507
|
+
}
|
|
2508
|
+
return `/**
|
|
2509
|
+
* Strapi Client (v5)
|
|
2510
|
+
* Generated by strapi2front
|
|
2511
|
+
*/
|
|
2512
|
+
|
|
2513
|
+
import Strapi from 'strapi-sdk-js';
|
|
2514
|
+
import type { StrapiPagination } from './utils';
|
|
2515
|
+
|
|
2516
|
+
// Initialize the Strapi client
|
|
2517
|
+
const strapiUrl = import.meta.env.STRAPI_URL || process.env.STRAPI_URL || 'http://localhost:1337';
|
|
2518
|
+
const strapiToken = import.meta.env.STRAPI_TOKEN || process.env.STRAPI_TOKEN;
|
|
2519
|
+
const strapiApiPrefix = import.meta.env.STRAPI_API_PREFIX || process.env.STRAPI_API_PREFIX || '${normalizedPrefix}';
|
|
2520
|
+
|
|
2521
|
+
export const strapi = new Strapi({
|
|
2522
|
+
url: strapiUrl,
|
|
2523
|
+
prefix: strapiApiPrefix,
|
|
2524
|
+
axiosOptions: {
|
|
2525
|
+
headers: strapiToken ? {
|
|
2526
|
+
Authorization: \`Bearer \${strapiToken}\`,
|
|
2527
|
+
} : {},
|
|
2528
|
+
},
|
|
2529
|
+
});
|
|
2530
|
+
|
|
2531
|
+
// Default pagination for fallback
|
|
2532
|
+
const defaultPagination: StrapiPagination = {
|
|
2533
|
+
page: 1,
|
|
2534
|
+
pageSize: 25,
|
|
2535
|
+
pageCount: 1,
|
|
2536
|
+
total: 0,
|
|
2537
|
+
};
|
|
2538
|
+
|
|
2539
|
+
// Response types from strapi-sdk-js
|
|
2540
|
+
interface StrapiListResponse<T> {
|
|
2541
|
+
data: T[];
|
|
2542
|
+
meta: {
|
|
2543
|
+
pagination?: StrapiPagination;
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
interface StrapiSingleResponse<T> {
|
|
2548
|
+
data: T;
|
|
2549
|
+
meta?: Record<string, unknown>;
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
// Helper to get typed collection
|
|
2553
|
+
export function collection<T>(pluralName: string) {
|
|
2554
|
+
return {
|
|
2555
|
+
async find(params?: Record<string, unknown>): Promise<{ data: T[]; meta: { pagination: StrapiPagination } }> {
|
|
2556
|
+
const response = await strapi.find(pluralName, params) as unknown as StrapiListResponse<T>;
|
|
2557
|
+
return {
|
|
2558
|
+
data: Array.isArray(response.data) ? response.data : [],
|
|
2559
|
+
meta: {
|
|
2560
|
+
pagination: response.meta?.pagination || defaultPagination,
|
|
2561
|
+
},
|
|
2562
|
+
};
|
|
2563
|
+
},
|
|
2564
|
+
async findOne(documentId: string, params?: Record<string, unknown>): Promise<{ data: T }> {
|
|
2565
|
+
const response = await strapi.findOne(pluralName, documentId, params) as unknown as StrapiSingleResponse<T>;
|
|
2566
|
+
return { data: response.data };
|
|
2567
|
+
},
|
|
2568
|
+
async create(data: { data: Partial<T> }): Promise<{ data: T }> {
|
|
2569
|
+
const response = await strapi.create(pluralName, data.data as Record<string, unknown>) as unknown as StrapiSingleResponse<T>;
|
|
2570
|
+
return { data: response.data };
|
|
2571
|
+
},
|
|
2572
|
+
async update(documentId: string, data: { data: Partial<T> }): Promise<{ data: T }> {
|
|
2573
|
+
const response = await strapi.update(pluralName, documentId, data.data as Record<string, unknown>) as unknown as StrapiSingleResponse<T>;
|
|
2574
|
+
return { data: response.data };
|
|
2575
|
+
},
|
|
2576
|
+
async delete(documentId: string): Promise<void> {
|
|
2577
|
+
await strapi.delete(pluralName, documentId);
|
|
2578
|
+
},
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
// Helper to get typed single type
|
|
2583
|
+
export function single<T>(singularName: string) {
|
|
2584
|
+
return {
|
|
2585
|
+
async find(params?: Record<string, unknown>): Promise<{ data: T }> {
|
|
2586
|
+
const response = await strapi.find(singularName, params) as unknown as StrapiSingleResponse<T>;
|
|
2587
|
+
return { data: response.data };
|
|
2588
|
+
},
|
|
2589
|
+
async update(data: { data: Partial<T> }): Promise<{ data: T }> {
|
|
2590
|
+
const response = await strapi.update(singularName, 1 as unknown as string, data.data as Record<string, unknown>) as unknown as StrapiSingleResponse<T>;
|
|
2591
|
+
return { data: response.data };
|
|
2592
|
+
},
|
|
2593
|
+
async delete(): Promise<void> {
|
|
2594
|
+
await strapi.delete(singularName, 1 as unknown as string);
|
|
2595
|
+
},
|
|
2596
|
+
};
|
|
2597
|
+
}
|
|
2598
|
+
`;
|
|
2599
|
+
}
|
|
2600
|
+
function generateLocalesFile2(locales) {
|
|
2601
|
+
if (locales.length === 0) {
|
|
2602
|
+
return `/**
|
|
2603
|
+
* Strapi locales
|
|
2604
|
+
* Generated by strapi2front
|
|
2605
|
+
* Note: i18n is not enabled in Strapi
|
|
2606
|
+
*/
|
|
2607
|
+
|
|
2608
|
+
export const locales = [] as const;
|
|
2609
|
+
export type Locale = string;
|
|
2610
|
+
export const defaultLocale: Locale = 'en';
|
|
2611
|
+
export const localeNames: Record<string, string> = {};
|
|
2612
|
+
|
|
2613
|
+
export function isValidLocale(_code: string): _code is Locale {
|
|
2614
|
+
return true;
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
export function getLocaleName(code: string): string {
|
|
2618
|
+
return code;
|
|
2619
|
+
}
|
|
2620
|
+
`;
|
|
2621
|
+
}
|
|
2622
|
+
const localeCodes = locales.map((l) => l.code);
|
|
2623
|
+
const defaultLocale = locales.find((l) => l.isDefault)?.code || locales[0]?.code || "en";
|
|
2624
|
+
return `/**
|
|
2625
|
+
* Strapi locales
|
|
2626
|
+
* Generated by strapi2front
|
|
2627
|
+
*/
|
|
2628
|
+
|
|
2629
|
+
export const locales = [${localeCodes.map((c) => `'${c}'`).join(", ")}] as const;
|
|
2630
|
+
|
|
2631
|
+
export type Locale = typeof locales[number];
|
|
2632
|
+
|
|
2633
|
+
export const defaultLocale: Locale = '${defaultLocale}';
|
|
2634
|
+
|
|
2635
|
+
export const localeNames: Record<Locale, string> = {
|
|
2636
|
+
${locales.map((l) => ` '${l.code}': '${l.name}'`).join(",\n")}
|
|
2637
|
+
};
|
|
2638
|
+
|
|
2639
|
+
export function isValidLocale(code: string): code is Locale {
|
|
2640
|
+
return locales.includes(code as Locale);
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
export function getLocaleName(code: Locale): string {
|
|
2644
|
+
return localeNames[code] || code;
|
|
2645
|
+
}
|
|
2646
|
+
`;
|
|
2647
|
+
}
|
|
2648
|
+
function generateCollectionTypes(collection, schema) {
|
|
2649
|
+
const typeName = toPascalCase(collection.singularName);
|
|
2650
|
+
const attributes = generateAttributes2(collection.attributes);
|
|
2651
|
+
const imports = generateTypeImports(collection.attributes, schema, "collection");
|
|
2652
|
+
return `/**
|
|
2653
|
+
* ${collection.displayName}
|
|
2654
|
+
* ${collection.description || ""}
|
|
2655
|
+
* Generated by strapi2front
|
|
2656
|
+
*/
|
|
2657
|
+
|
|
2658
|
+
${imports}
|
|
2659
|
+
|
|
2660
|
+
export interface ${typeName} extends StrapiBaseEntity {
|
|
2661
|
+
${attributes}
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
export interface ${typeName}Filters {
|
|
2665
|
+
id?: number | { $eq?: number; $ne?: number; $in?: number[]; $notIn?: number[] };
|
|
2666
|
+
documentId?: string | { $eq?: string; $ne?: string };
|
|
2667
|
+
createdAt?: string | { $eq?: string; $gt?: string; $gte?: string; $lt?: string; $lte?: string };
|
|
2668
|
+
updatedAt?: string | { $eq?: string; $gt?: string; $gte?: string; $lt?: string; $lte?: string };
|
|
2669
|
+
publishedAt?: string | null | { $eq?: string; $ne?: string; $null?: boolean };
|
|
2670
|
+
$and?: ${typeName}Filters[];
|
|
2671
|
+
$or?: ${typeName}Filters[];
|
|
2672
|
+
$not?: ${typeName}Filters;
|
|
2673
|
+
}
|
|
2674
|
+
`;
|
|
2675
|
+
}
|
|
2676
|
+
function generateSingleTypes(single, schema) {
|
|
2677
|
+
const typeName = toPascalCase(single.singularName);
|
|
2678
|
+
const attributes = generateAttributes2(single.attributes);
|
|
2679
|
+
const imports = generateTypeImports(single.attributes, schema, "single");
|
|
2680
|
+
return `/**
|
|
2681
|
+
* ${single.displayName}
|
|
2682
|
+
* ${single.description || ""}
|
|
2683
|
+
* Generated by strapi2front
|
|
2684
|
+
*/
|
|
2685
|
+
|
|
2686
|
+
${imports}
|
|
2687
|
+
|
|
2688
|
+
export interface ${typeName} extends StrapiBaseEntity {
|
|
2689
|
+
${attributes}
|
|
2690
|
+
}
|
|
2691
|
+
`;
|
|
2692
|
+
}
|
|
2693
|
+
function generateComponentTypes(component, schema) {
|
|
2694
|
+
const typeName = toPascalCase(component.name);
|
|
2695
|
+
const attributes = generateAttributes2(component.attributes);
|
|
2696
|
+
const imports = generateTypeImports(component.attributes, schema, "component");
|
|
2697
|
+
return `/**
|
|
2698
|
+
* ${component.displayName} component
|
|
2699
|
+
* Category: ${component.category}
|
|
2700
|
+
* ${component.description || ""}
|
|
2701
|
+
* Generated by strapi2front
|
|
2702
|
+
*/
|
|
2703
|
+
|
|
2704
|
+
${imports}
|
|
2705
|
+
|
|
2706
|
+
export interface ${typeName} {
|
|
2707
|
+
id: number;
|
|
2708
|
+
${attributes}
|
|
2709
|
+
}
|
|
2710
|
+
`;
|
|
2711
|
+
}
|
|
2712
|
+
function generateTypeImports(attributes, schema, context) {
|
|
2713
|
+
const utilsImports = [];
|
|
2714
|
+
const relationImports = /* @__PURE__ */ new Map();
|
|
2715
|
+
const componentImports = /* @__PURE__ */ new Map();
|
|
2716
|
+
const attributesStr = JSON.stringify(attributes);
|
|
2717
|
+
if (context !== "component") {
|
|
2718
|
+
utilsImports.push("StrapiBaseEntity");
|
|
2719
|
+
}
|
|
2720
|
+
if (attributesStr.includes('"type":"media"')) {
|
|
2721
|
+
utilsImports.push("StrapiMedia");
|
|
2722
|
+
}
|
|
2723
|
+
if (attributesStr.includes('"type":"blocks"')) {
|
|
2724
|
+
utilsImports.push("BlocksContent");
|
|
2725
|
+
}
|
|
2726
|
+
const relativePrefix = context === "component" ? ".." : "../..";
|
|
2727
|
+
for (const attr of Object.values(attributes)) {
|
|
2728
|
+
if (attr.type === "relation" && "target" in attr && attr.target) {
|
|
2729
|
+
const targetName = attr.target.split(".").pop() || "";
|
|
2730
|
+
if (targetName) {
|
|
2731
|
+
const typeName = toPascalCase(targetName);
|
|
2732
|
+
const fileName = toKebabCase(targetName);
|
|
2733
|
+
const isCollection = schema.collections.some((c) => c.singularName === targetName);
|
|
2734
|
+
const isSingle = schema.singles.some((s) => s.singularName === targetName);
|
|
2735
|
+
if (isCollection) {
|
|
2736
|
+
relationImports.set(typeName, `${relativePrefix}/collections/${fileName}/types`);
|
|
2737
|
+
} else if (isSingle) {
|
|
2738
|
+
relationImports.set(typeName, `${relativePrefix}/singles/${fileName}/types`);
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
if (attr.type === "component" && "component" in attr && attr.component) {
|
|
2743
|
+
const componentName = attr.component.split(".").pop() || "";
|
|
2744
|
+
if (componentName) {
|
|
2745
|
+
const typeName = toPascalCase(componentName);
|
|
2746
|
+
const fileName = toKebabCase(componentName);
|
|
2747
|
+
if (context === "component") {
|
|
2748
|
+
componentImports.set(typeName, `./${fileName}`);
|
|
2749
|
+
} else {
|
|
2750
|
+
componentImports.set(typeName, `../../components/${fileName}`);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
if (attr.type === "dynamiczone" && "components" in attr && attr.components) {
|
|
2755
|
+
for (const comp of attr.components) {
|
|
2756
|
+
const componentName = comp.split(".").pop() || "";
|
|
2757
|
+
if (componentName) {
|
|
2758
|
+
const typeName = toPascalCase(componentName);
|
|
2759
|
+
const fileName = toKebabCase(componentName);
|
|
2760
|
+
if (context === "component") {
|
|
2761
|
+
componentImports.set(typeName, `./${fileName}`);
|
|
2762
|
+
} else {
|
|
2763
|
+
componentImports.set(typeName, `../../components/${fileName}`);
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
const lines = [];
|
|
2770
|
+
if (utilsImports.length > 0) {
|
|
2771
|
+
const utilsPath = context === "component" ? "../shared/utils" : "../../shared/utils";
|
|
2772
|
+
lines.push(`import type { ${utilsImports.join(", ")} } from '${utilsPath}';`);
|
|
2773
|
+
}
|
|
2774
|
+
for (const [typeName, importPath] of relationImports) {
|
|
2775
|
+
lines.push(`import type { ${typeName} } from '${importPath}';`);
|
|
2776
|
+
}
|
|
2777
|
+
for (const [typeName, importPath] of componentImports) {
|
|
2778
|
+
lines.push(`import type { ${typeName} } from '${importPath}';`);
|
|
2779
|
+
}
|
|
2780
|
+
return lines.join("\n");
|
|
2781
|
+
}
|
|
2782
|
+
function generateAttributes2(attributes) {
|
|
2783
|
+
const lines = [];
|
|
2784
|
+
for (const [name, attr] of Object.entries(attributes)) {
|
|
2785
|
+
const tsType = attributeToTsType(attr);
|
|
2786
|
+
const optional = attr.required ? "" : "?";
|
|
2787
|
+
lines.push(` ${name}${optional}: ${tsType};`);
|
|
2788
|
+
}
|
|
2789
|
+
return lines.join("\n");
|
|
2790
|
+
}
|
|
2791
|
+
function attributeToTsType(attr) {
|
|
2792
|
+
switch (attr.type) {
|
|
2793
|
+
case "string":
|
|
2794
|
+
case "text":
|
|
2795
|
+
case "richtext":
|
|
2796
|
+
case "email":
|
|
2797
|
+
case "password":
|
|
2798
|
+
case "uid":
|
|
2799
|
+
return "string";
|
|
2800
|
+
case "blocks":
|
|
2801
|
+
return "BlocksContent";
|
|
2802
|
+
case "integer":
|
|
2803
|
+
case "biginteger":
|
|
2804
|
+
case "float":
|
|
2805
|
+
case "decimal":
|
|
2806
|
+
return "number";
|
|
2807
|
+
case "boolean":
|
|
2808
|
+
return "boolean";
|
|
2809
|
+
case "date":
|
|
2810
|
+
case "time":
|
|
2811
|
+
case "datetime":
|
|
2812
|
+
case "timestamp":
|
|
2813
|
+
return "string";
|
|
2814
|
+
case "json":
|
|
2815
|
+
return "unknown";
|
|
2816
|
+
case "enumeration":
|
|
2817
|
+
if ("enum" in attr && attr.enum) {
|
|
2818
|
+
return attr.enum.map((v) => `'${v}'`).join(" | ");
|
|
2819
|
+
}
|
|
2820
|
+
return "string";
|
|
2821
|
+
case "media":
|
|
2822
|
+
if ("multiple" in attr && attr.multiple) {
|
|
2823
|
+
return "StrapiMedia[]";
|
|
2824
|
+
}
|
|
2825
|
+
return "StrapiMedia | null";
|
|
2826
|
+
case "relation":
|
|
2827
|
+
if ("target" in attr && attr.target) {
|
|
2828
|
+
const targetName = toPascalCase(attr.target.split(".").pop() || "unknown");
|
|
2829
|
+
const isMany = attr.relation === "oneToMany" || attr.relation === "manyToMany";
|
|
2830
|
+
return isMany ? `${targetName}[]` : `${targetName} | null`;
|
|
2831
|
+
}
|
|
2832
|
+
return "unknown";
|
|
2833
|
+
case "component":
|
|
2834
|
+
if ("component" in attr && attr.component) {
|
|
2835
|
+
const componentName = toPascalCase(attr.component.split(".").pop() || "unknown");
|
|
2836
|
+
if ("repeatable" in attr && attr.repeatable) {
|
|
2837
|
+
return `${componentName}[]`;
|
|
2838
|
+
}
|
|
2839
|
+
return `${componentName} | null`;
|
|
2840
|
+
}
|
|
2841
|
+
return "unknown";
|
|
2842
|
+
case "dynamiczone":
|
|
2843
|
+
if ("components" in attr && attr.components) {
|
|
2844
|
+
const types = attr.components.map((c) => toPascalCase(c.split(".").pop() || "unknown"));
|
|
2845
|
+
return `(${types.join(" | ")})[]`;
|
|
2846
|
+
}
|
|
2847
|
+
return "unknown[]";
|
|
2848
|
+
default:
|
|
2849
|
+
return "unknown";
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
function generateCollectionService3(collection, strapiVersion) {
|
|
2853
|
+
const typeName = toPascalCase(collection.singularName);
|
|
2854
|
+
const serviceName = toCamelCase(collection.singularName) + "Service";
|
|
2855
|
+
const endpoint = collection.pluralName;
|
|
2856
|
+
const hasSlug = "slug" in collection.attributes;
|
|
2857
|
+
const { localized, draftAndPublish } = collection;
|
|
2858
|
+
const isV4 = strapiVersion === "v4";
|
|
2859
|
+
const idParam = isV4 ? "id: number" : "documentId: string";
|
|
2860
|
+
const idName = isV4 ? "id" : "documentId";
|
|
2861
|
+
const omitFields = isV4 ? "'id' | 'createdAt' | 'updatedAt' | 'publishedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt' | 'publishedAt'";
|
|
2862
|
+
const omitFieldsUpdate = isV4 ? "'id' | 'createdAt' | 'updatedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt'";
|
|
2863
|
+
const imports = [
|
|
2864
|
+
`import { collection } from '../../shared/client';`,
|
|
2865
|
+
`import type { ${typeName}, ${typeName}Filters } from './types';`,
|
|
2866
|
+
`import type { StrapiPagination } from '../../shared/utils';`
|
|
2867
|
+
];
|
|
2868
|
+
if (localized) {
|
|
2869
|
+
imports.push(`import type { Locale } from '../../shared/locales';`);
|
|
2870
|
+
}
|
|
2871
|
+
const paginationFields = `
|
|
2872
|
+
/** Page number (1-indexed) - use with pageSize */
|
|
2873
|
+
page?: number;
|
|
2874
|
+
/** Number of items per page (default: 25) - use with page */
|
|
2875
|
+
pageSize?: number;
|
|
2876
|
+
/** Offset to start from (0-indexed) - use with limit */
|
|
2877
|
+
start?: number;
|
|
2878
|
+
/** Maximum number of items to return - use with start */
|
|
2879
|
+
limit?: number;`;
|
|
2880
|
+
let findManyOptionsFields = ` filters?: ${typeName}Filters;
|
|
2881
|
+
pagination?: {${paginationFields}
|
|
2882
|
+
};
|
|
2883
|
+
sort?: string | string[];
|
|
2884
|
+
populate?: string | string[] | Record<string, unknown>;`;
|
|
2885
|
+
let findOneOptionsFields = ` populate?: string | string[] | Record<string, unknown>;`;
|
|
2886
|
+
if (localized) {
|
|
2887
|
+
findManyOptionsFields += `
|
|
2888
|
+
locale?: Locale;`;
|
|
2889
|
+
findOneOptionsFields += `
|
|
2890
|
+
locale?: Locale;`;
|
|
2891
|
+
}
|
|
2892
|
+
if (draftAndPublish) {
|
|
2893
|
+
findManyOptionsFields += `
|
|
2894
|
+
status?: 'draft' | 'published';`;
|
|
2895
|
+
findOneOptionsFields += `
|
|
2896
|
+
status?: 'draft' | 'published';`;
|
|
2897
|
+
}
|
|
2898
|
+
let findParams = ` filters: options.filters,
|
|
2899
|
+
pagination: options.pagination,
|
|
2900
|
+
sort: options.sort,
|
|
2901
|
+
populate: options.populate,`;
|
|
2902
|
+
let findOneParams = ` populate: options.populate,`;
|
|
2903
|
+
if (localized) {
|
|
2904
|
+
findParams += `
|
|
2905
|
+
locale: options.locale,`;
|
|
2906
|
+
findOneParams += `
|
|
2907
|
+
locale: options.locale,`;
|
|
2908
|
+
}
|
|
2909
|
+
if (draftAndPublish) {
|
|
2910
|
+
findParams += `
|
|
2911
|
+
status: options.status,`;
|
|
2912
|
+
findOneParams += `
|
|
2913
|
+
status: options.status,`;
|
|
2914
|
+
}
|
|
2915
|
+
return `/**
|
|
2916
|
+
* ${collection.displayName} Service
|
|
2917
|
+
* ${collection.description || ""}
|
|
2918
|
+
* Generated by strapi2front
|
|
2919
|
+
* Strapi version: ${strapiVersion}
|
|
2920
|
+
*/
|
|
2921
|
+
|
|
2922
|
+
${imports.join("\n")}
|
|
2923
|
+
|
|
2924
|
+
export interface FindManyOptions {
|
|
2925
|
+
${findManyOptionsFields}
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
export interface FindOneOptions {
|
|
2929
|
+
${findOneOptionsFields}
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
// Create typed collection helper
|
|
2933
|
+
const ${toCamelCase(collection.singularName)}Collection = collection<${typeName}>('${endpoint}');
|
|
2934
|
+
|
|
2935
|
+
export const ${serviceName} = {
|
|
2936
|
+
async findMany(options: FindManyOptions = {}): Promise<{ data: ${typeName}[]; pagination: StrapiPagination }> {
|
|
2937
|
+
const response = await ${toCamelCase(collection.singularName)}Collection.find({
|
|
2938
|
+
${findParams}
|
|
2939
|
+
});
|
|
2940
|
+
|
|
2941
|
+
return {
|
|
2942
|
+
data: response.data,
|
|
2943
|
+
pagination: response.meta.pagination,
|
|
2944
|
+
};
|
|
2945
|
+
},
|
|
2946
|
+
|
|
2947
|
+
async findAll(options: Omit<FindManyOptions, 'pagination'> = {}): Promise<${typeName}[]> {
|
|
2948
|
+
const allItems: ${typeName}[] = [];
|
|
2949
|
+
let page = 1;
|
|
2950
|
+
let hasMore = true;
|
|
2951
|
+
|
|
2952
|
+
while (hasMore) {
|
|
2953
|
+
const { data, pagination } = await this.findMany({
|
|
2954
|
+
...options,
|
|
2955
|
+
pagination: { page, pageSize: 100 },
|
|
2956
|
+
});
|
|
2957
|
+
|
|
2958
|
+
allItems.push(...data);
|
|
2959
|
+
hasMore = page < pagination.pageCount;
|
|
2960
|
+
page++;
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
return allItems;
|
|
2964
|
+
},
|
|
2965
|
+
|
|
2966
|
+
async findOne(${idParam}, options: FindOneOptions = {}): Promise<${typeName} | null> {
|
|
2967
|
+
try {
|
|
2968
|
+
const response = await ${toCamelCase(collection.singularName)}Collection.findOne(${idName}, {
|
|
2969
|
+
${findOneParams}
|
|
2970
|
+
});
|
|
2971
|
+
|
|
2972
|
+
return response.data;
|
|
2973
|
+
} catch (error) {
|
|
2974
|
+
if (error instanceof Error && error.message.includes('404')) {
|
|
2975
|
+
return null;
|
|
2976
|
+
}
|
|
2977
|
+
throw error;
|
|
2978
|
+
}
|
|
2979
|
+
},
|
|
2980
|
+
${hasSlug ? `
|
|
2981
|
+
async findBySlug(slug: string, options: FindOneOptions = {}): Promise<${typeName} | null> {
|
|
2982
|
+
const { data } = await this.findMany({
|
|
2983
|
+
filters: { slug: { $eq: slug } } as ${typeName}Filters,
|
|
2984
|
+
pagination: { pageSize: 1 },
|
|
2985
|
+
populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
|
|
2986
|
+
});
|
|
2987
|
+
|
|
2988
|
+
return data[0] || null;
|
|
2989
|
+
},
|
|
2990
|
+
` : ""}
|
|
2991
|
+
async create(data: Partial<Omit<${typeName}, ${omitFields}>>): Promise<${typeName}> {
|
|
2992
|
+
const response = await ${toCamelCase(collection.singularName)}Collection.create({ data });
|
|
2993
|
+
return response.data;
|
|
2994
|
+
},
|
|
2995
|
+
|
|
2996
|
+
async update(${idParam}, data: Partial<Omit<${typeName}, ${omitFieldsUpdate}>>): Promise<${typeName}> {
|
|
2997
|
+
const response = await ${toCamelCase(collection.singularName)}Collection.update(${idName}, { data });
|
|
2998
|
+
return response.data;
|
|
2999
|
+
},
|
|
3000
|
+
|
|
3001
|
+
async delete(${idParam}): Promise<void> {
|
|
3002
|
+
await ${toCamelCase(collection.singularName)}Collection.delete(${idName});
|
|
3003
|
+
},
|
|
3004
|
+
|
|
3005
|
+
async count(filters?: ${typeName}Filters): Promise<number> {
|
|
3006
|
+
const { pagination } = await this.findMany({
|
|
3007
|
+
filters,
|
|
3008
|
+
pagination: { pageSize: 1 },
|
|
3009
|
+
});
|
|
3010
|
+
|
|
3011
|
+
return pagination.total;
|
|
3012
|
+
},
|
|
3013
|
+
};
|
|
3014
|
+
`;
|
|
3015
|
+
}
|
|
3016
|
+
function generateSingleService3(single, strapiVersion) {
|
|
3017
|
+
const typeName = toPascalCase(single.singularName);
|
|
3018
|
+
const serviceName = toCamelCase(single.singularName) + "Service";
|
|
3019
|
+
const endpoint = single.singularName;
|
|
3020
|
+
const { localized, draftAndPublish } = single;
|
|
3021
|
+
const isV4 = strapiVersion === "v4";
|
|
3022
|
+
const omitFields = isV4 ? "'id' | 'createdAt' | 'updatedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt'";
|
|
3023
|
+
const imports = [
|
|
3024
|
+
`import { single } from '../../shared/client';`,
|
|
3025
|
+
`import type { ${typeName} } from './types';`
|
|
3026
|
+
];
|
|
3027
|
+
if (localized) {
|
|
3028
|
+
imports.push(`import type { Locale } from '../../shared/locales';`);
|
|
3029
|
+
}
|
|
3030
|
+
let findOptionsFields = ` populate?: string | string[] | Record<string, unknown>;`;
|
|
3031
|
+
if (localized) {
|
|
3032
|
+
findOptionsFields += `
|
|
3033
|
+
locale?: Locale;`;
|
|
3034
|
+
}
|
|
3035
|
+
if (draftAndPublish) {
|
|
3036
|
+
findOptionsFields += `
|
|
3037
|
+
status?: 'draft' | 'published';`;
|
|
3038
|
+
}
|
|
3039
|
+
let findParams = ` populate: options.populate,`;
|
|
3040
|
+
if (localized) {
|
|
3041
|
+
findParams += `
|
|
3042
|
+
locale: options.locale,`;
|
|
3043
|
+
}
|
|
3044
|
+
if (draftAndPublish) {
|
|
3045
|
+
findParams += `
|
|
3046
|
+
status: options.status,`;
|
|
3047
|
+
}
|
|
3048
|
+
return `/**
|
|
3049
|
+
* ${single.displayName} Service (Single Type)
|
|
3050
|
+
* ${single.description || ""}
|
|
3051
|
+
* Generated by strapi2front
|
|
3052
|
+
* Strapi version: ${strapiVersion}
|
|
3053
|
+
*/
|
|
3054
|
+
|
|
3055
|
+
${imports.join("\n")}
|
|
3056
|
+
|
|
3057
|
+
export interface FindOptions {
|
|
3058
|
+
${findOptionsFields}
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
// Create typed single helper
|
|
3062
|
+
const ${toCamelCase(single.singularName)}Single = single<${typeName}>('${endpoint}');
|
|
3063
|
+
|
|
3064
|
+
export const ${serviceName} = {
|
|
3065
|
+
async find(options: FindOptions = {}): Promise<${typeName} | null> {
|
|
3066
|
+
try {
|
|
3067
|
+
const response = await ${toCamelCase(single.singularName)}Single.find({
|
|
3068
|
+
${findParams}
|
|
3069
|
+
});
|
|
3070
|
+
|
|
3071
|
+
return response.data;
|
|
3072
|
+
} catch (error) {
|
|
3073
|
+
if (error instanceof Error && error.message.includes('404')) {
|
|
3074
|
+
return null;
|
|
3075
|
+
}
|
|
3076
|
+
throw error;
|
|
3077
|
+
}
|
|
3078
|
+
},
|
|
3079
|
+
|
|
3080
|
+
async update(data: Partial<Omit<${typeName}, ${omitFields}>>): Promise<${typeName}> {
|
|
3081
|
+
const response = await ${toCamelCase(single.singularName)}Single.update({ data });
|
|
3082
|
+
return response.data;
|
|
3083
|
+
},
|
|
3084
|
+
|
|
3085
|
+
async delete(): Promise<void> {
|
|
3086
|
+
await ${toCamelCase(single.singularName)}Single.delete();
|
|
3087
|
+
},
|
|
3088
|
+
};
|
|
3089
|
+
`;
|
|
3090
|
+
}
|
|
3091
|
+
function generateCollectionActions2(collection, strapiVersion) {
|
|
3092
|
+
const typeName = toPascalCase(collection.singularName);
|
|
3093
|
+
const serviceName = toCamelCase(collection.singularName) + "Service";
|
|
3094
|
+
const actionPrefix = toCamelCase(collection.singularName);
|
|
3095
|
+
const isV4 = strapiVersion === "v4";
|
|
3096
|
+
const idInputSchema = isV4 ? "z.number().int().positive()" : "z.string()";
|
|
3097
|
+
const idParamName = isV4 ? "id" : "documentId";
|
|
3098
|
+
return `/**
|
|
3099
|
+
* ${collection.displayName} Astro Actions
|
|
3100
|
+
* ${collection.description || ""}
|
|
3101
|
+
* Generated by strapi2front
|
|
3102
|
+
* Strapi version: ${strapiVersion}
|
|
3103
|
+
*/
|
|
3104
|
+
|
|
3105
|
+
import { defineAction } from 'astro:actions';
|
|
3106
|
+
import { z } from 'astro:schema';
|
|
3107
|
+
import { ${serviceName} } from './service';
|
|
3108
|
+
import type { ${typeName} } from './types';
|
|
3109
|
+
|
|
3110
|
+
export const ${actionPrefix}Actions = {
|
|
3111
|
+
getMany: defineAction({
|
|
3112
|
+
input: z.object({
|
|
3113
|
+
page: z.number().optional(),
|
|
3114
|
+
pageSize: z.number().optional(),
|
|
3115
|
+
sort: z.string().optional(),
|
|
3116
|
+
}).optional(),
|
|
3117
|
+
handler: async (input) => {
|
|
3118
|
+
const { data, pagination } = await ${serviceName}.findMany({
|
|
3119
|
+
pagination: input ? { page: input.page, pageSize: input.pageSize } : undefined,
|
|
3120
|
+
sort: input?.sort,
|
|
3121
|
+
});
|
|
3122
|
+
return { data, pagination };
|
|
3123
|
+
},
|
|
3124
|
+
}),
|
|
3125
|
+
|
|
3126
|
+
getOne: defineAction({
|
|
3127
|
+
input: z.object({
|
|
3128
|
+
${idParamName}: ${idInputSchema},
|
|
3129
|
+
}),
|
|
3130
|
+
handler: async (input) => {
|
|
3131
|
+
const data = await ${serviceName}.findOne(input.${idParamName});
|
|
3132
|
+
return { data };
|
|
3133
|
+
},
|
|
3134
|
+
}),
|
|
3135
|
+
|
|
3136
|
+
create: defineAction({
|
|
3137
|
+
input: z.object({
|
|
3138
|
+
data: z.record(z.unknown()),
|
|
3139
|
+
}),
|
|
3140
|
+
handler: async (input) => {
|
|
3141
|
+
const data = await ${serviceName}.create(input.data as Partial<${typeName}>);
|
|
3142
|
+
return { data };
|
|
3143
|
+
},
|
|
3144
|
+
}),
|
|
1477
3145
|
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
3146
|
+
update: defineAction({
|
|
3147
|
+
input: z.object({
|
|
3148
|
+
${idParamName}: ${idInputSchema},
|
|
3149
|
+
data: z.record(z.unknown()),
|
|
3150
|
+
}),
|
|
3151
|
+
handler: async (input) => {
|
|
3152
|
+
const data = await ${serviceName}.update(input.${idParamName}, input.data as Partial<${typeName}>);
|
|
3153
|
+
return { data };
|
|
3154
|
+
},
|
|
3155
|
+
}),
|
|
1484
3156
|
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
}
|
|
3157
|
+
delete: defineAction({
|
|
3158
|
+
input: z.object({
|
|
3159
|
+
${idParamName}: ${idInputSchema},
|
|
3160
|
+
}),
|
|
3161
|
+
handler: async (input) => {
|
|
3162
|
+
await ${serviceName}.delete(input.${idParamName});
|
|
3163
|
+
return { success: true };
|
|
3164
|
+
},
|
|
3165
|
+
}),
|
|
3166
|
+
};
|
|
1491
3167
|
`;
|
|
1492
3168
|
}
|
|
1493
|
-
|
|
1494
|
-
const { outputDir, features, blocksRendererInstalled = false, strapiVersion = "v5" } = options;
|
|
1495
|
-
const generatedFiles = [];
|
|
1496
|
-
await ensureDir(path7.join(outputDir, "collections"));
|
|
1497
|
-
await ensureDir(path7.join(outputDir, "singles"));
|
|
1498
|
-
await ensureDir(path7.join(outputDir, "components"));
|
|
1499
|
-
await ensureDir(path7.join(outputDir, "shared"));
|
|
1500
|
-
const sharedDir = path7.join(outputDir, "shared");
|
|
1501
|
-
const utilsPath = path7.join(sharedDir, "utils.ts");
|
|
1502
|
-
await writeFile(utilsPath, await formatCode(generateUtilityTypes2(blocksRendererInstalled, strapiVersion)));
|
|
1503
|
-
generatedFiles.push(utilsPath);
|
|
1504
|
-
const clientPath = path7.join(sharedDir, "client.ts");
|
|
1505
|
-
await writeFile(clientPath, await formatCode(generateClient2(strapiVersion)));
|
|
1506
|
-
generatedFiles.push(clientPath);
|
|
1507
|
-
const localesPath = path7.join(sharedDir, "locales.ts");
|
|
1508
|
-
await writeFile(localesPath, await formatCode(generateLocalesFile2(locales)));
|
|
1509
|
-
generatedFiles.push(localesPath);
|
|
1510
|
-
for (const collection of schema.collections) {
|
|
1511
|
-
const featureDir = path7.join(outputDir, "collections", toKebabCase(collection.singularName));
|
|
1512
|
-
await ensureDir(featureDir);
|
|
1513
|
-
if (features.types) {
|
|
1514
|
-
const typesPath = path7.join(featureDir, "types.ts");
|
|
1515
|
-
await writeFile(typesPath, await formatCode(generateCollectionTypes(collection, schema)));
|
|
1516
|
-
generatedFiles.push(typesPath);
|
|
1517
|
-
}
|
|
1518
|
-
if (features.services) {
|
|
1519
|
-
const servicePath = path7.join(featureDir, "service.ts");
|
|
1520
|
-
await writeFile(servicePath, await formatCode(generateCollectionService2(collection, strapiVersion)));
|
|
1521
|
-
generatedFiles.push(servicePath);
|
|
1522
|
-
}
|
|
1523
|
-
if (features.actions) {
|
|
1524
|
-
const actionsPath = path7.join(featureDir, "actions.ts");
|
|
1525
|
-
await writeFile(actionsPath, await formatCode(generateCollectionActions2(collection, strapiVersion)));
|
|
1526
|
-
generatedFiles.push(actionsPath);
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1529
|
-
for (const single of schema.singles) {
|
|
1530
|
-
const featureDir = path7.join(outputDir, "singles", toKebabCase(single.singularName));
|
|
1531
|
-
await ensureDir(featureDir);
|
|
1532
|
-
if (features.types) {
|
|
1533
|
-
const typesPath = path7.join(featureDir, "types.ts");
|
|
1534
|
-
await writeFile(typesPath, await formatCode(generateSingleTypes(single, schema)));
|
|
1535
|
-
generatedFiles.push(typesPath);
|
|
1536
|
-
}
|
|
1537
|
-
if (features.services) {
|
|
1538
|
-
const servicePath = path7.join(featureDir, "service.ts");
|
|
1539
|
-
await writeFile(servicePath, await formatCode(generateSingleService2(single, strapiVersion)));
|
|
1540
|
-
generatedFiles.push(servicePath);
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
for (const component of schema.components) {
|
|
1544
|
-
const componentPath = path7.join(outputDir, "components", `${toKebabCase(component.name)}.ts`);
|
|
1545
|
-
await writeFile(componentPath, await formatCode(generateComponentTypes(component, schema)));
|
|
1546
|
-
generatedFiles.push(componentPath);
|
|
1547
|
-
}
|
|
1548
|
-
return generatedFiles;
|
|
1549
|
-
}
|
|
1550
|
-
function generateUtilityTypes2(blocksRendererInstalled, strapiVersion) {
|
|
3169
|
+
function generateUtilityTypesJSDoc(blocksRendererInstalled, strapiVersion) {
|
|
1551
3170
|
const isV4 = strapiVersion === "v4";
|
|
1552
3171
|
const blocksContentType = isV4 ? `/**
|
|
1553
3172
|
* Rich text content (Strapi v4)
|
|
1554
3173
|
* Can be markdown string or custom JSON structure
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
* Blocks content type (Strapi v5 rich text)
|
|
1558
|
-
* Re-exported from @strapi/blocks-react-renderer
|
|
1559
|
-
*/
|
|
1560
|
-
export type { BlocksContent } from '@strapi/blocks-react-renderer';` : `/**
|
|
3174
|
+
* @typedef {string} RichTextContent
|
|
3175
|
+
*/` : blocksRendererInstalled ? `// BlocksContent - import from '@strapi/blocks-react-renderer' for full type support` : `/**
|
|
1561
3176
|
* Blocks content type (Strapi v5 rich text)
|
|
1562
3177
|
*
|
|
1563
3178
|
* For full type support and rendering, install:
|
|
1564
3179
|
* npm install @strapi/blocks-react-renderer
|
|
1565
3180
|
*
|
|
1566
3181
|
* Then re-run: npx strapi2front sync
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
const baseEntity = isV4 ?
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
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
|
-
}
|
|
3182
|
+
* @typedef {Array<Object>} BlocksContent
|
|
3183
|
+
*/`;
|
|
3184
|
+
const baseEntity = isV4 ? `/**
|
|
3185
|
+
* @typedef {Object} StrapiBaseEntity
|
|
3186
|
+
* @property {number} id
|
|
3187
|
+
* @property {string} createdAt
|
|
3188
|
+
* @property {string} updatedAt
|
|
3189
|
+
* @property {string|null} publishedAt
|
|
3190
|
+
*/` : `/**
|
|
3191
|
+
* @typedef {Object} StrapiBaseEntity
|
|
3192
|
+
* @property {number} id
|
|
3193
|
+
* @property {string} documentId
|
|
3194
|
+
* @property {string} createdAt
|
|
3195
|
+
* @property {string} updatedAt
|
|
3196
|
+
* @property {string|null} publishedAt
|
|
3197
|
+
*/`;
|
|
3198
|
+
const mediaType = isV4 ? `/**
|
|
3199
|
+
* @typedef {Object} StrapiMedia
|
|
3200
|
+
* @property {number} id
|
|
3201
|
+
* @property {string} name
|
|
3202
|
+
* @property {string|null} alternativeText
|
|
3203
|
+
* @property {string|null} caption
|
|
3204
|
+
* @property {number} width
|
|
3205
|
+
* @property {number} height
|
|
3206
|
+
* @property {Object|null} formats
|
|
3207
|
+
* @property {StrapiMediaFormat} [formats.thumbnail]
|
|
3208
|
+
* @property {StrapiMediaFormat} [formats.small]
|
|
3209
|
+
* @property {StrapiMediaFormat} [formats.medium]
|
|
3210
|
+
* @property {StrapiMediaFormat} [formats.large]
|
|
3211
|
+
* @property {string} hash
|
|
3212
|
+
* @property {string} ext
|
|
3213
|
+
* @property {string} mime
|
|
3214
|
+
* @property {number} size
|
|
3215
|
+
* @property {string} url
|
|
3216
|
+
* @property {string|null} previewUrl
|
|
3217
|
+
* @property {string} provider
|
|
3218
|
+
* @property {string} createdAt
|
|
3219
|
+
* @property {string} updatedAt
|
|
3220
|
+
*/` : `/**
|
|
3221
|
+
* @typedef {Object} StrapiMedia
|
|
3222
|
+
* @property {number} id
|
|
3223
|
+
* @property {string} documentId
|
|
3224
|
+
* @property {string} name
|
|
3225
|
+
* @property {string|null} alternativeText
|
|
3226
|
+
* @property {string|null} caption
|
|
3227
|
+
* @property {number} width
|
|
3228
|
+
* @property {number} height
|
|
3229
|
+
* @property {Object|null} formats
|
|
3230
|
+
* @property {StrapiMediaFormat} [formats.thumbnail]
|
|
3231
|
+
* @property {StrapiMediaFormat} [formats.small]
|
|
3232
|
+
* @property {StrapiMediaFormat} [formats.medium]
|
|
3233
|
+
* @property {StrapiMediaFormat} [formats.large]
|
|
3234
|
+
* @property {string} hash
|
|
3235
|
+
* @property {string} ext
|
|
3236
|
+
* @property {string} mime
|
|
3237
|
+
* @property {number} size
|
|
3238
|
+
* @property {string} url
|
|
3239
|
+
* @property {string|null} previewUrl
|
|
3240
|
+
* @property {string} provider
|
|
3241
|
+
* @property {string} createdAt
|
|
3242
|
+
* @property {string} updatedAt
|
|
3243
|
+
*/`;
|
|
1627
3244
|
const v4RawResponseTypes = isV4 ? `
|
|
1628
3245
|
|
|
1629
3246
|
/**
|
|
1630
|
-
* Strapi v4 raw API response (with nested attributes)
|
|
3247
|
+
* Strapi v4 raw API response item (with nested attributes)
|
|
3248
|
+
* @template T
|
|
3249
|
+
* @typedef {Object} StrapiV4RawItem
|
|
3250
|
+
* @property {number} id
|
|
3251
|
+
* @property {Omit<T, 'id'>} attributes
|
|
1631
3252
|
*/
|
|
1632
|
-
export interface StrapiV4RawItem<T> {
|
|
1633
|
-
id: number;
|
|
1634
|
-
attributes: Omit<T, 'id'>;
|
|
1635
|
-
}
|
|
1636
3253
|
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
}
|
|
3254
|
+
/**
|
|
3255
|
+
* Strapi v4 raw API response
|
|
3256
|
+
* @template T
|
|
3257
|
+
* @typedef {Object} StrapiV4RawResponse
|
|
3258
|
+
* @property {StrapiV4RawItem<T>} data
|
|
3259
|
+
* @property {Object} meta
|
|
3260
|
+
*/
|
|
1641
3261
|
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
}
|
|
3262
|
+
/**
|
|
3263
|
+
* Strapi v4 raw list response
|
|
3264
|
+
* @template T
|
|
3265
|
+
* @typedef {Object} StrapiV4RawListResponse
|
|
3266
|
+
* @property {StrapiV4RawItem<T>[]} data
|
|
3267
|
+
* @property {Object} meta
|
|
3268
|
+
* @property {StrapiPagination} meta.pagination
|
|
3269
|
+
*/
|
|
1648
3270
|
|
|
1649
3271
|
/**
|
|
1650
3272
|
* Flatten Strapi v4 response item
|
|
3273
|
+
* @template T
|
|
3274
|
+
* @param {StrapiV4RawItem<T>} item
|
|
3275
|
+
* @returns {T}
|
|
1651
3276
|
*/
|
|
1652
|
-
|
|
1653
|
-
return { id: item.id, ...item.attributes }
|
|
3277
|
+
function flattenV4Response(item) {
|
|
3278
|
+
return { id: item.id, ...item.attributes };
|
|
1654
3279
|
}
|
|
1655
3280
|
|
|
1656
3281
|
/**
|
|
1657
3282
|
* Flatten Strapi v4 list response
|
|
3283
|
+
* @template T
|
|
3284
|
+
* @param {StrapiV4RawItem<T>[]} items
|
|
3285
|
+
* @returns {T[]}
|
|
1658
3286
|
*/
|
|
1659
|
-
|
|
1660
|
-
return items.map(item => flattenV4Response
|
|
1661
|
-
}
|
|
1662
|
-
|
|
3287
|
+
function flattenV4ListResponse(items) {
|
|
3288
|
+
return items.map(item => flattenV4Response(item));
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
module.exports = { flattenV4Response, flattenV4ListResponse };` : "";
|
|
3292
|
+
return `// @ts-check
|
|
3293
|
+
/**
|
|
1663
3294
|
* Strapi utility types
|
|
1664
3295
|
* Generated by strapi2front
|
|
1665
3296
|
* Strapi version: ${strapiVersion}
|
|
@@ -1667,60 +3298,69 @@ export function flattenV4ListResponse<T>(items: StrapiV4RawItem<T>[]): T[] {
|
|
|
1667
3298
|
|
|
1668
3299
|
${mediaType}
|
|
1669
3300
|
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
}
|
|
3301
|
+
/**
|
|
3302
|
+
* @typedef {Object} StrapiMediaFormat
|
|
3303
|
+
* @property {string} name
|
|
3304
|
+
* @property {string} hash
|
|
3305
|
+
* @property {string} ext
|
|
3306
|
+
* @property {string} mime
|
|
3307
|
+
* @property {number} width
|
|
3308
|
+
* @property {number} height
|
|
3309
|
+
* @property {number} size
|
|
3310
|
+
* @property {string} url
|
|
3311
|
+
*/
|
|
1680
3312
|
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
}
|
|
3313
|
+
/**
|
|
3314
|
+
* @typedef {Object} StrapiPagination
|
|
3315
|
+
* @property {number} page
|
|
3316
|
+
* @property {number} pageSize
|
|
3317
|
+
* @property {number} pageCount
|
|
3318
|
+
* @property {number} total
|
|
3319
|
+
*/
|
|
1687
3320
|
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
}
|
|
3321
|
+
/**
|
|
3322
|
+
* @template T
|
|
3323
|
+
* @typedef {Object} StrapiResponse
|
|
3324
|
+
* @property {T} data
|
|
3325
|
+
* @property {Object} meta
|
|
3326
|
+
* @property {StrapiPagination} [meta.pagination]
|
|
3327
|
+
*/
|
|
1694
3328
|
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
}
|
|
3329
|
+
/**
|
|
3330
|
+
* @template T
|
|
3331
|
+
* @typedef {Object} StrapiListResponse
|
|
3332
|
+
* @property {T[]} data
|
|
3333
|
+
* @property {Object} meta
|
|
3334
|
+
* @property {StrapiPagination} meta.pagination
|
|
3335
|
+
*/
|
|
1701
3336
|
|
|
1702
3337
|
${baseEntity}
|
|
1703
3338
|
${v4RawResponseTypes}
|
|
1704
3339
|
${blocksContentType}
|
|
3340
|
+
|
|
3341
|
+
module.exports = {};
|
|
1705
3342
|
`;
|
|
1706
3343
|
}
|
|
1707
|
-
function
|
|
3344
|
+
function generateClientJSDoc(strapiVersion, apiPrefix = "/api") {
|
|
1708
3345
|
const isV4 = strapiVersion === "v4";
|
|
3346
|
+
const normalizedPrefix = apiPrefix.startsWith("/") ? apiPrefix : "/" + apiPrefix;
|
|
1709
3347
|
if (isV4) {
|
|
1710
|
-
return
|
|
3348
|
+
return `// @ts-check
|
|
3349
|
+
/**
|
|
1711
3350
|
* Strapi Client (v4)
|
|
1712
3351
|
* Generated by strapi2front
|
|
1713
3352
|
*/
|
|
1714
3353
|
|
|
1715
|
-
|
|
1716
|
-
import type { StrapiPagination } from './utils';
|
|
3354
|
+
const Strapi = require('strapi-sdk-js').default;
|
|
1717
3355
|
|
|
1718
3356
|
// Initialize the Strapi client
|
|
1719
|
-
const strapiUrl =
|
|
1720
|
-
const strapiToken =
|
|
3357
|
+
const strapiUrl = process.env.STRAPI_URL || 'http://localhost:1337';
|
|
3358
|
+
const strapiToken = process.env.STRAPI_TOKEN;
|
|
3359
|
+
const strapiApiPrefix = process.env.STRAPI_API_PREFIX || '${normalizedPrefix}';
|
|
1721
3360
|
|
|
1722
|
-
|
|
3361
|
+
const strapi = new Strapi({
|
|
1723
3362
|
url: strapiUrl,
|
|
3363
|
+
prefix: strapiApiPrefix,
|
|
1724
3364
|
axiosOptions: {
|
|
1725
3365
|
headers: strapiToken ? {
|
|
1726
3366
|
Authorization: \`Bearer \${strapiToken}\`,
|
|
@@ -1728,63 +3368,49 @@ export const strapi = new Strapi({
|
|
|
1728
3368
|
},
|
|
1729
3369
|
});
|
|
1730
3370
|
|
|
1731
|
-
|
|
1732
|
-
const defaultPagination
|
|
3371
|
+
/** @type {import('./utils').StrapiPagination} */
|
|
3372
|
+
const defaultPagination = {
|
|
1733
3373
|
page: 1,
|
|
1734
3374
|
pageSize: 25,
|
|
1735
3375
|
pageCount: 1,
|
|
1736
3376
|
total: 0,
|
|
1737
3377
|
};
|
|
1738
3378
|
|
|
1739
|
-
// Strapi v4 raw response types (with nested attributes)
|
|
1740
|
-
interface StrapiV4RawItem<T> {
|
|
1741
|
-
id: number;
|
|
1742
|
-
attributes: Omit<T, 'id'>;
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
interface StrapiV4RawListResponse<T> {
|
|
1746
|
-
data: StrapiV4RawItem<T>[];
|
|
1747
|
-
meta: {
|
|
1748
|
-
pagination?: StrapiPagination;
|
|
1749
|
-
};
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
interface StrapiV4RawSingleResponse<T> {
|
|
1753
|
-
data: StrapiV4RawItem<T>;
|
|
1754
|
-
meta?: Record<string, unknown>;
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
3379
|
/**
|
|
1758
3380
|
* Flatten a Strapi v4 response item (merges id with attributes)
|
|
3381
|
+
* @template T
|
|
3382
|
+
* @param {{ id: number, attributes: Omit<T, 'id'> }} item
|
|
3383
|
+
* @returns {T}
|
|
1759
3384
|
*/
|
|
1760
|
-
function flattenItem
|
|
1761
|
-
return { id: item.id, ...item.attributes }
|
|
3385
|
+
function flattenItem(item) {
|
|
3386
|
+
return { id: item.id, ...item.attributes };
|
|
1762
3387
|
}
|
|
1763
3388
|
|
|
1764
3389
|
/**
|
|
1765
3390
|
* Recursively flatten nested relations in Strapi v4 response
|
|
3391
|
+
* @template T
|
|
3392
|
+
* @param {T} data
|
|
3393
|
+
* @returns {T}
|
|
1766
3394
|
*/
|
|
1767
|
-
function flattenRelations
|
|
3395
|
+
function flattenRelations(data) {
|
|
1768
3396
|
if (data === null || data === undefined) return data;
|
|
1769
3397
|
if (Array.isArray(data)) {
|
|
1770
|
-
return data.map(item => flattenRelations(item))
|
|
3398
|
+
return /** @type {T} */ (data.map(item => flattenRelations(item)));
|
|
1771
3399
|
}
|
|
1772
3400
|
if (typeof data === 'object') {
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
3401
|
+
/** @type {Record<string, unknown>} */
|
|
3402
|
+
const result = {};
|
|
3403
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1776
3404
|
if (value && typeof value === 'object' && 'data' in value) {
|
|
1777
|
-
const relationData =
|
|
3405
|
+
const relationData = /** @type {{ data: unknown }} */ (value).data;
|
|
1778
3406
|
if (relationData === null) {
|
|
1779
3407
|
result[key] = null;
|
|
1780
3408
|
} else if (Array.isArray(relationData)) {
|
|
1781
|
-
|
|
1782
|
-
result[key] = relationData.map((item: StrapiV4RawItem<unknown>) =>
|
|
3409
|
+
result[key] = relationData.map((item) =>
|
|
1783
3410
|
flattenRelations(flattenItem(item))
|
|
1784
3411
|
);
|
|
1785
3412
|
} else if (typeof relationData === 'object' && 'id' in relationData && 'attributes' in relationData) {
|
|
1786
|
-
|
|
1787
|
-
result[key] = flattenRelations(flattenItem(relationData as StrapiV4RawItem<unknown>));
|
|
3413
|
+
result[key] = flattenRelations(flattenItem(/** @type {{ id: number, attributes: object }} */ (relationData)));
|
|
1788
3414
|
} else {
|
|
1789
3415
|
result[key] = flattenRelations(value);
|
|
1790
3416
|
}
|
|
@@ -1792,76 +3418,150 @@ function flattenRelations<T>(data: T): T {
|
|
|
1792
3418
|
result[key] = flattenRelations(value);
|
|
1793
3419
|
}
|
|
1794
3420
|
}
|
|
1795
|
-
return
|
|
3421
|
+
return /** @type {T} */ (result);
|
|
1796
3422
|
}
|
|
1797
3423
|
return data;
|
|
1798
3424
|
}
|
|
1799
3425
|
|
|
1800
|
-
|
|
1801
|
-
|
|
3426
|
+
/**
|
|
3427
|
+
* Helper to get typed collection
|
|
3428
|
+
* @template T
|
|
3429
|
+
* @param {string} pluralName
|
|
3430
|
+
*/
|
|
3431
|
+
function collection(pluralName) {
|
|
1802
3432
|
return {
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
3433
|
+
/**
|
|
3434
|
+
* @param {Record<string, unknown>} [params]
|
|
3435
|
+
* @returns {Promise<{ data: T[], meta: { pagination: import('./utils').StrapiPagination } }>}
|
|
3436
|
+
*/
|
|
3437
|
+
async find(params) {
|
|
3438
|
+
const response = await strapi.find(pluralName, params);
|
|
3439
|
+
/** @type {any} */
|
|
3440
|
+
const rawData = response.data;
|
|
3441
|
+
/** @type {T[]} */
|
|
3442
|
+
const data = Array.isArray(rawData)
|
|
3443
|
+
? rawData.map(item => flattenRelations(flattenItem(item)))
|
|
1807
3444
|
: [];
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
3445
|
+
/** @type {any} */
|
|
3446
|
+
const rawMeta = response.meta;
|
|
3447
|
+
/** @type {any} */
|
|
3448
|
+
const rawPag = rawMeta?.pagination;
|
|
3449
|
+
/** @type {import('./utils').StrapiPagination} */
|
|
3450
|
+
const pagination = {
|
|
3451
|
+
page: rawPag?.page ?? defaultPagination.page,
|
|
3452
|
+
pageSize: rawPag?.pageSize ?? defaultPagination.pageSize,
|
|
3453
|
+
pageCount: rawPag?.pageCount ?? defaultPagination.pageCount,
|
|
3454
|
+
total: rawPag?.total ?? defaultPagination.total,
|
|
1813
3455
|
};
|
|
3456
|
+
return { data, meta: { pagination } };
|
|
1814
3457
|
},
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
3458
|
+
/**
|
|
3459
|
+
* @param {number|string} id
|
|
3460
|
+
* @param {Record<string, unknown>} [params]
|
|
3461
|
+
* @returns {Promise<{ data: T }>}
|
|
3462
|
+
*/
|
|
3463
|
+
async findOne(id, params) {
|
|
3464
|
+
const response = await strapi.findOne(pluralName, String(id), params);
|
|
3465
|
+
/** @type {any} */
|
|
3466
|
+
const rawData = response.data;
|
|
3467
|
+
/** @type {T} */
|
|
3468
|
+
const data = flattenRelations(flattenItem(rawData));
|
|
3469
|
+
return { data };
|
|
1818
3470
|
},
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
3471
|
+
/**
|
|
3472
|
+
* @param {{ data: Partial<T> }} data
|
|
3473
|
+
* @returns {Promise<{ data: T }>}
|
|
3474
|
+
*/
|
|
3475
|
+
async create(data) {
|
|
3476
|
+
const response = await strapi.create(pluralName, data.data);
|
|
3477
|
+
/** @type {any} */
|
|
3478
|
+
const rawData = response.data;
|
|
3479
|
+
/** @type {T} */
|
|
3480
|
+
const result = flattenRelations(flattenItem(rawData));
|
|
3481
|
+
return { data: result };
|
|
1822
3482
|
},
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
3483
|
+
/**
|
|
3484
|
+
* @param {number|string} id
|
|
3485
|
+
* @param {{ data: Partial<T> }} data
|
|
3486
|
+
* @returns {Promise<{ data: T }>}
|
|
3487
|
+
*/
|
|
3488
|
+
async update(id, data) {
|
|
3489
|
+
const response = await strapi.update(pluralName, String(id), data.data);
|
|
3490
|
+
/** @type {any} */
|
|
3491
|
+
const rawData = response.data;
|
|
3492
|
+
/** @type {T} */
|
|
3493
|
+
const result = flattenRelations(flattenItem(rawData));
|
|
3494
|
+
return { data: result };
|
|
1826
3495
|
},
|
|
1827
|
-
|
|
3496
|
+
/**
|
|
3497
|
+
* @param {number|string} id
|
|
3498
|
+
* @returns {Promise<void>}
|
|
3499
|
+
*/
|
|
3500
|
+
async delete(id) {
|
|
1828
3501
|
await strapi.delete(pluralName, String(id));
|
|
1829
3502
|
},
|
|
1830
3503
|
};
|
|
1831
3504
|
}
|
|
1832
3505
|
|
|
1833
|
-
|
|
1834
|
-
|
|
3506
|
+
/**
|
|
3507
|
+
* Helper to get typed single type
|
|
3508
|
+
* @template T
|
|
3509
|
+
* @param {string} singularName
|
|
3510
|
+
*/
|
|
3511
|
+
function single(singularName) {
|
|
1835
3512
|
return {
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
3513
|
+
/**
|
|
3514
|
+
* @param {Record<string, unknown>} [params]
|
|
3515
|
+
* @returns {Promise<{ data: T }>}
|
|
3516
|
+
*/
|
|
3517
|
+
async find(params) {
|
|
3518
|
+
const response = await strapi.find(singularName, params);
|
|
3519
|
+
/** @type {any} */
|
|
3520
|
+
const rawData = response.data;
|
|
3521
|
+
/** @type {T} */
|
|
3522
|
+
const data = flattenRelations(flattenItem(rawData));
|
|
3523
|
+
return { data };
|
|
1839
3524
|
},
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
3525
|
+
/**
|
|
3526
|
+
* @param {{ data: Partial<T> }} data
|
|
3527
|
+
* @returns {Promise<{ data: T }>}
|
|
3528
|
+
*/
|
|
3529
|
+
async update(data) {
|
|
3530
|
+
const response = await strapi.update(singularName, String(1), data.data);
|
|
3531
|
+
/** @type {any} */
|
|
3532
|
+
const rawData = response.data;
|
|
3533
|
+
/** @type {T} */
|
|
3534
|
+
const result = flattenRelations(flattenItem(rawData));
|
|
3535
|
+
return { data: result };
|
|
1843
3536
|
},
|
|
1844
|
-
|
|
1845
|
-
|
|
3537
|
+
/**
|
|
3538
|
+
* @returns {Promise<void>}
|
|
3539
|
+
*/
|
|
3540
|
+
async delete() {
|
|
3541
|
+
await strapi.delete(singularName, String(1));
|
|
1846
3542
|
},
|
|
1847
3543
|
};
|
|
1848
3544
|
}
|
|
3545
|
+
|
|
3546
|
+
module.exports = { strapi, collection, single };
|
|
1849
3547
|
`;
|
|
1850
3548
|
}
|
|
1851
|
-
return
|
|
3549
|
+
return `// @ts-check
|
|
3550
|
+
/**
|
|
1852
3551
|
* Strapi Client (v5)
|
|
1853
3552
|
* Generated by strapi2front
|
|
1854
3553
|
*/
|
|
1855
3554
|
|
|
1856
|
-
|
|
1857
|
-
import type { StrapiPagination } from './utils';
|
|
3555
|
+
const Strapi = require('strapi-sdk-js').default;
|
|
1858
3556
|
|
|
1859
3557
|
// Initialize the Strapi client
|
|
1860
|
-
const strapiUrl =
|
|
1861
|
-
const strapiToken =
|
|
3558
|
+
const strapiUrl = process.env.STRAPI_URL || 'http://localhost:1337';
|
|
3559
|
+
const strapiToken = process.env.STRAPI_TOKEN;
|
|
3560
|
+
const strapiApiPrefix = process.env.STRAPI_API_PREFIX || '${normalizedPrefix}';
|
|
1862
3561
|
|
|
1863
|
-
|
|
3562
|
+
const strapi = new Strapi({
|
|
1864
3563
|
url: strapiUrl,
|
|
3564
|
+
prefix: strapiApiPrefix,
|
|
1865
3565
|
axiosOptions: {
|
|
1866
3566
|
headers: strapiToken ? {
|
|
1867
3567
|
Authorization: \`Bearer \${strapiToken}\`,
|
|
@@ -1869,267 +3569,304 @@ export const strapi = new Strapi({
|
|
|
1869
3569
|
},
|
|
1870
3570
|
});
|
|
1871
3571
|
|
|
1872
|
-
|
|
1873
|
-
const defaultPagination
|
|
3572
|
+
/** @type {import('./utils').StrapiPagination} */
|
|
3573
|
+
const defaultPagination = {
|
|
1874
3574
|
page: 1,
|
|
1875
3575
|
pageSize: 25,
|
|
1876
3576
|
pageCount: 1,
|
|
1877
3577
|
total: 0,
|
|
1878
3578
|
};
|
|
1879
3579
|
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
}
|
|
1887
|
-
|
|
1888
|
-
interface StrapiSingleResponse<T> {
|
|
1889
|
-
data: T;
|
|
1890
|
-
meta?: Record<string, unknown>;
|
|
1891
|
-
}
|
|
1892
|
-
|
|
1893
|
-
// Helper to get typed collection
|
|
1894
|
-
export function collection<T>(pluralName: string) {
|
|
3580
|
+
/**
|
|
3581
|
+
* Helper to get typed collection
|
|
3582
|
+
* @template T
|
|
3583
|
+
* @param {string} pluralName
|
|
3584
|
+
*/
|
|
3585
|
+
function collection(pluralName) {
|
|
1895
3586
|
return {
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
3587
|
+
/**
|
|
3588
|
+
* @param {Record<string, unknown>} [params]
|
|
3589
|
+
* @returns {Promise<{ data: T[], meta: { pagination: import('./utils').StrapiPagination } }>}
|
|
3590
|
+
*/
|
|
3591
|
+
async find(params) {
|
|
3592
|
+
const response = await strapi.find(pluralName, params);
|
|
3593
|
+
/** @type {any} */
|
|
3594
|
+
const rawMeta = response.meta;
|
|
3595
|
+
/** @type {any} */
|
|
3596
|
+
const rawPag = rawMeta?.pagination;
|
|
3597
|
+
/** @type {import('./utils').StrapiPagination} */
|
|
3598
|
+
const pagination = {
|
|
3599
|
+
page: rawPag?.page ?? defaultPagination.page,
|
|
3600
|
+
pageSize: rawPag?.pageSize ?? defaultPagination.pageSize,
|
|
3601
|
+
pageCount: rawPag?.pageCount ?? defaultPagination.pageCount,
|
|
3602
|
+
total: rawPag?.total ?? defaultPagination.total,
|
|
1903
3603
|
};
|
|
3604
|
+
/** @type {any} */
|
|
3605
|
+
const rawData = response.data;
|
|
3606
|
+
/** @type {T[]} */
|
|
3607
|
+
const data = Array.isArray(rawData) ? rawData : [];
|
|
3608
|
+
return { data, meta: { pagination } };
|
|
1904
3609
|
},
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
3610
|
+
/**
|
|
3611
|
+
* @param {string} documentId
|
|
3612
|
+
* @param {Record<string, unknown>} [params]
|
|
3613
|
+
* @returns {Promise<{ data: T }>}
|
|
3614
|
+
*/
|
|
3615
|
+
async findOne(documentId, params) {
|
|
3616
|
+
const response = await strapi.findOne(pluralName, documentId, params);
|
|
3617
|
+
/** @type {any} */
|
|
3618
|
+
const rawData = response.data;
|
|
3619
|
+
/** @type {T} */
|
|
3620
|
+
const data = rawData;
|
|
3621
|
+
return { data };
|
|
1908
3622
|
},
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
3623
|
+
/**
|
|
3624
|
+
* @param {{ data: Partial<T> }} data
|
|
3625
|
+
* @returns {Promise<{ data: T }>}
|
|
3626
|
+
*/
|
|
3627
|
+
async create(data) {
|
|
3628
|
+
const response = await strapi.create(pluralName, data.data);
|
|
3629
|
+
/** @type {any} */
|
|
3630
|
+
const rawData = response.data;
|
|
3631
|
+
/** @type {T} */
|
|
3632
|
+
const result = rawData;
|
|
3633
|
+
return { data: result };
|
|
1912
3634
|
},
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
3635
|
+
/**
|
|
3636
|
+
* @param {string} documentId
|
|
3637
|
+
* @param {{ data: Partial<T> }} data
|
|
3638
|
+
* @returns {Promise<{ data: T }>}
|
|
3639
|
+
*/
|
|
3640
|
+
async update(documentId, data) {
|
|
3641
|
+
const response = await strapi.update(pluralName, documentId, data.data);
|
|
3642
|
+
/** @type {any} */
|
|
3643
|
+
const rawData = response.data;
|
|
3644
|
+
/** @type {T} */
|
|
3645
|
+
const result = rawData;
|
|
3646
|
+
return { data: result };
|
|
1916
3647
|
},
|
|
1917
|
-
|
|
3648
|
+
/**
|
|
3649
|
+
* @param {string} documentId
|
|
3650
|
+
* @returns {Promise<void>}
|
|
3651
|
+
*/
|
|
3652
|
+
async delete(documentId) {
|
|
1918
3653
|
await strapi.delete(pluralName, documentId);
|
|
1919
3654
|
},
|
|
1920
3655
|
};
|
|
1921
3656
|
}
|
|
1922
3657
|
|
|
1923
|
-
|
|
1924
|
-
|
|
3658
|
+
/**
|
|
3659
|
+
* Helper to get typed single type
|
|
3660
|
+
* @template T
|
|
3661
|
+
* @param {string} singularName
|
|
3662
|
+
*/
|
|
3663
|
+
function single(singularName) {
|
|
1925
3664
|
return {
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
3665
|
+
/**
|
|
3666
|
+
* @param {Record<string, unknown>} [params]
|
|
3667
|
+
* @returns {Promise<{ data: T }>}
|
|
3668
|
+
*/
|
|
3669
|
+
async find(params) {
|
|
3670
|
+
const response = await strapi.find(singularName, params);
|
|
3671
|
+
/** @type {any} */
|
|
3672
|
+
const rawData = response.data;
|
|
3673
|
+
/** @type {T} */
|
|
3674
|
+
const data = rawData;
|
|
3675
|
+
return { data };
|
|
1929
3676
|
},
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
3677
|
+
/**
|
|
3678
|
+
* @param {{ data: Partial<T> }} data
|
|
3679
|
+
* @returns {Promise<{ data: T }>}
|
|
3680
|
+
*/
|
|
3681
|
+
async update(data) {
|
|
3682
|
+
const response = await strapi.update(singularName, String(1), data.data);
|
|
3683
|
+
/** @type {any} */
|
|
3684
|
+
const rawData = response.data;
|
|
3685
|
+
/** @type {T} */
|
|
3686
|
+
const result = rawData;
|
|
3687
|
+
return { data: result };
|
|
1933
3688
|
},
|
|
1934
|
-
|
|
1935
|
-
|
|
3689
|
+
/**
|
|
3690
|
+
* @returns {Promise<void>}
|
|
3691
|
+
*/
|
|
3692
|
+
async delete() {
|
|
3693
|
+
await strapi.delete(singularName, String(1));
|
|
1936
3694
|
},
|
|
1937
3695
|
};
|
|
1938
3696
|
}
|
|
3697
|
+
|
|
3698
|
+
module.exports = { strapi, collection, single };
|
|
1939
3699
|
`;
|
|
1940
3700
|
}
|
|
1941
|
-
function
|
|
3701
|
+
function generateLocalesFileJSDoc(locales) {
|
|
1942
3702
|
if (locales.length === 0) {
|
|
1943
|
-
return
|
|
3703
|
+
return `// @ts-check
|
|
3704
|
+
/**
|
|
1944
3705
|
* Strapi locales
|
|
1945
3706
|
* Generated by strapi2front
|
|
1946
3707
|
* Note: i18n is not enabled in Strapi
|
|
1947
3708
|
*/
|
|
1948
3709
|
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
export const defaultLocale: Locale = 'en';
|
|
1952
|
-
export const localeNames: Record<string, string> = {};
|
|
3710
|
+
/** @type {readonly string[]} */
|
|
3711
|
+
const locales = [];
|
|
1953
3712
|
|
|
1954
|
-
|
|
3713
|
+
/** @typedef {string} Locale */
|
|
3714
|
+
|
|
3715
|
+
/** @type {string} */
|
|
3716
|
+
const defaultLocale = 'en';
|
|
3717
|
+
|
|
3718
|
+
/** @type {Record<string, string>} */
|
|
3719
|
+
const localeNames = {};
|
|
3720
|
+
|
|
3721
|
+
/**
|
|
3722
|
+
* @param {string} _code
|
|
3723
|
+
* @returns {boolean}
|
|
3724
|
+
*/
|
|
3725
|
+
function isValidLocale(_code) {
|
|
1955
3726
|
return true;
|
|
1956
3727
|
}
|
|
1957
3728
|
|
|
1958
|
-
|
|
3729
|
+
/**
|
|
3730
|
+
* @param {string} code
|
|
3731
|
+
* @returns {string}
|
|
3732
|
+
*/
|
|
3733
|
+
function getLocaleName(code) {
|
|
1959
3734
|
return code;
|
|
1960
3735
|
}
|
|
3736
|
+
|
|
3737
|
+
module.exports = { locales, defaultLocale, localeNames, isValidLocale, getLocaleName };
|
|
1961
3738
|
`;
|
|
1962
3739
|
}
|
|
1963
3740
|
const localeCodes = locales.map((l) => l.code);
|
|
1964
3741
|
const defaultLocale = locales.find((l) => l.isDefault)?.code || locales[0]?.code || "en";
|
|
1965
|
-
return
|
|
3742
|
+
return `// @ts-check
|
|
3743
|
+
/**
|
|
1966
3744
|
* Strapi locales
|
|
1967
3745
|
* Generated by strapi2front
|
|
1968
3746
|
*/
|
|
1969
3747
|
|
|
1970
|
-
|
|
3748
|
+
/** @type {readonly [${localeCodes.map((c) => `'${c}'`).join(", ")}]} */
|
|
3749
|
+
const locales = [${localeCodes.map((c) => `'${c}'`).join(", ")}];
|
|
1971
3750
|
|
|
1972
|
-
|
|
3751
|
+
/** @typedef {${localeCodes.map((c) => `'${c}'`).join(" | ")}} Locale */
|
|
1973
3752
|
|
|
1974
|
-
|
|
3753
|
+
/** @type {Locale} */
|
|
3754
|
+
const defaultLocale = '${defaultLocale}';
|
|
1975
3755
|
|
|
1976
|
-
|
|
3756
|
+
/** @type {Record<Locale, string>} */
|
|
3757
|
+
const localeNames = {
|
|
1977
3758
|
${locales.map((l) => ` '${l.code}': '${l.name}'`).join(",\n")}
|
|
1978
3759
|
};
|
|
1979
3760
|
|
|
1980
|
-
|
|
1981
|
-
|
|
3761
|
+
/**
|
|
3762
|
+
* @param {string} code
|
|
3763
|
+
* @returns {boolean}
|
|
3764
|
+
*/
|
|
3765
|
+
function isValidLocale(code) {
|
|
3766
|
+
/** @type {readonly string[]} */
|
|
3767
|
+
const localeList = locales;
|
|
3768
|
+
return localeList.includes(code);
|
|
1982
3769
|
}
|
|
1983
3770
|
|
|
1984
|
-
|
|
3771
|
+
/**
|
|
3772
|
+
* @param {Locale} code
|
|
3773
|
+
* @returns {string}
|
|
3774
|
+
*/
|
|
3775
|
+
function getLocaleName(code) {
|
|
1985
3776
|
return localeNames[code] || code;
|
|
1986
3777
|
}
|
|
3778
|
+
|
|
3779
|
+
module.exports = { locales, defaultLocale, localeNames, isValidLocale, getLocaleName };
|
|
1987
3780
|
`;
|
|
1988
3781
|
}
|
|
1989
|
-
function
|
|
3782
|
+
function generateCollectionTypesJSDoc(collection, schema) {
|
|
1990
3783
|
const typeName = toPascalCase(collection.singularName);
|
|
1991
|
-
const attributes =
|
|
1992
|
-
|
|
1993
|
-
|
|
3784
|
+
const attributes = generateJSDocAttributes(collection.attributes, schema, "collection");
|
|
3785
|
+
return `// @ts-check
|
|
3786
|
+
/**
|
|
1994
3787
|
* ${collection.displayName}
|
|
1995
3788
|
* ${collection.description || ""}
|
|
1996
3789
|
* Generated by strapi2front
|
|
1997
3790
|
*/
|
|
1998
3791
|
|
|
1999
|
-
|
|
3792
|
+
/**
|
|
3793
|
+
* @typedef {import('../../shared/utils').StrapiBaseEntity & ${typeName}Attributes} ${typeName}
|
|
3794
|
+
*/
|
|
2000
3795
|
|
|
2001
|
-
|
|
3796
|
+
/**
|
|
3797
|
+
* @typedef {Object} ${typeName}Attributes
|
|
2002
3798
|
${attributes}
|
|
2003
|
-
|
|
3799
|
+
*/
|
|
2004
3800
|
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
}
|
|
3801
|
+
/**
|
|
3802
|
+
* @typedef {Object} ${typeName}Filters
|
|
3803
|
+
* @property {number|{$eq?: number, $ne?: number, $in?: number[], $notIn?: number[]}} [id]
|
|
3804
|
+
* @property {string|{$eq?: string, $ne?: string}} [documentId]
|
|
3805
|
+
* @property {string|{$eq?: string, $gt?: string, $gte?: string, $lt?: string, $lte?: string}} [createdAt]
|
|
3806
|
+
* @property {string|{$eq?: string, $gt?: string, $gte?: string, $lt?: string, $lte?: string}} [updatedAt]
|
|
3807
|
+
* @property {string|null|{$eq?: string, $ne?: string, $null?: boolean}} [publishedAt]
|
|
3808
|
+
* @property {${typeName}Filters[]} [$and]
|
|
3809
|
+
* @property {${typeName}Filters[]} [$or]
|
|
3810
|
+
* @property {${typeName}Filters} [$not]
|
|
3811
|
+
*/
|
|
3812
|
+
|
|
3813
|
+
module.exports = {};
|
|
2015
3814
|
`;
|
|
2016
3815
|
}
|
|
2017
|
-
function
|
|
3816
|
+
function generateSingleTypesJSDoc(single, schema) {
|
|
2018
3817
|
const typeName = toPascalCase(single.singularName);
|
|
2019
|
-
const attributes =
|
|
2020
|
-
|
|
2021
|
-
|
|
3818
|
+
const attributes = generateJSDocAttributes(single.attributes, schema, "single");
|
|
3819
|
+
return `// @ts-check
|
|
3820
|
+
/**
|
|
2022
3821
|
* ${single.displayName}
|
|
2023
3822
|
* ${single.description || ""}
|
|
2024
3823
|
* Generated by strapi2front
|
|
2025
3824
|
*/
|
|
2026
3825
|
|
|
2027
|
-
|
|
3826
|
+
/**
|
|
3827
|
+
* @typedef {import('../../shared/utils').StrapiBaseEntity & ${typeName}Attributes} ${typeName}
|
|
3828
|
+
*/
|
|
2028
3829
|
|
|
2029
|
-
|
|
3830
|
+
/**
|
|
3831
|
+
* @typedef {Object} ${typeName}Attributes
|
|
2030
3832
|
${attributes}
|
|
2031
|
-
|
|
3833
|
+
*/
|
|
3834
|
+
|
|
3835
|
+
module.exports = {};
|
|
2032
3836
|
`;
|
|
2033
3837
|
}
|
|
2034
|
-
function
|
|
3838
|
+
function generateComponentTypesJSDoc(component, schema) {
|
|
2035
3839
|
const typeName = toPascalCase(component.name);
|
|
2036
|
-
const attributes =
|
|
2037
|
-
|
|
2038
|
-
|
|
3840
|
+
const attributes = generateJSDocAttributes(component.attributes, schema, "component");
|
|
3841
|
+
return `// @ts-check
|
|
3842
|
+
/**
|
|
2039
3843
|
* ${component.displayName} component
|
|
2040
3844
|
* Category: ${component.category}
|
|
2041
3845
|
* ${component.description || ""}
|
|
2042
3846
|
* Generated by strapi2front
|
|
2043
3847
|
*/
|
|
2044
3848
|
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
id: number;
|
|
3849
|
+
/**
|
|
3850
|
+
* @typedef {Object} ${typeName}
|
|
3851
|
+
* @property {number} id
|
|
2049
3852
|
${attributes}
|
|
2050
|
-
|
|
3853
|
+
*/
|
|
3854
|
+
|
|
3855
|
+
module.exports = {};
|
|
2051
3856
|
`;
|
|
2052
3857
|
}
|
|
2053
|
-
function
|
|
2054
|
-
const utilsImports = [];
|
|
2055
|
-
const relationImports = /* @__PURE__ */ new Map();
|
|
2056
|
-
const componentImports = /* @__PURE__ */ new Map();
|
|
2057
|
-
const attributesStr = JSON.stringify(attributes);
|
|
2058
|
-
if (context !== "component") {
|
|
2059
|
-
utilsImports.push("StrapiBaseEntity");
|
|
2060
|
-
}
|
|
2061
|
-
if (attributesStr.includes('"type":"media"')) {
|
|
2062
|
-
utilsImports.push("StrapiMedia");
|
|
2063
|
-
}
|
|
2064
|
-
if (attributesStr.includes('"type":"blocks"')) {
|
|
2065
|
-
utilsImports.push("BlocksContent");
|
|
2066
|
-
}
|
|
2067
|
-
const relativePrefix = context === "component" ? ".." : "../..";
|
|
2068
|
-
for (const attr of Object.values(attributes)) {
|
|
2069
|
-
if (attr.type === "relation" && "target" in attr && attr.target) {
|
|
2070
|
-
const targetName = attr.target.split(".").pop() || "";
|
|
2071
|
-
if (targetName) {
|
|
2072
|
-
const typeName = toPascalCase(targetName);
|
|
2073
|
-
const fileName = toKebabCase(targetName);
|
|
2074
|
-
const isCollection = schema.collections.some((c) => c.singularName === targetName);
|
|
2075
|
-
const isSingle = schema.singles.some((s) => s.singularName === targetName);
|
|
2076
|
-
if (isCollection) {
|
|
2077
|
-
relationImports.set(typeName, `${relativePrefix}/collections/${fileName}/types`);
|
|
2078
|
-
} else if (isSingle) {
|
|
2079
|
-
relationImports.set(typeName, `${relativePrefix}/singles/${fileName}/types`);
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2082
|
-
}
|
|
2083
|
-
if (attr.type === "component" && "component" in attr && attr.component) {
|
|
2084
|
-
const componentName = attr.component.split(".").pop() || "";
|
|
2085
|
-
if (componentName) {
|
|
2086
|
-
const typeName = toPascalCase(componentName);
|
|
2087
|
-
const fileName = toKebabCase(componentName);
|
|
2088
|
-
if (context === "component") {
|
|
2089
|
-
componentImports.set(typeName, `./${fileName}`);
|
|
2090
|
-
} else {
|
|
2091
|
-
componentImports.set(typeName, `../../components/${fileName}`);
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
if (attr.type === "dynamiczone" && "components" in attr && attr.components) {
|
|
2096
|
-
for (const comp of attr.components) {
|
|
2097
|
-
const componentName = comp.split(".").pop() || "";
|
|
2098
|
-
if (componentName) {
|
|
2099
|
-
const typeName = toPascalCase(componentName);
|
|
2100
|
-
const fileName = toKebabCase(componentName);
|
|
2101
|
-
if (context === "component") {
|
|
2102
|
-
componentImports.set(typeName, `./${fileName}`);
|
|
2103
|
-
} else {
|
|
2104
|
-
componentImports.set(typeName, `../../components/${fileName}`);
|
|
2105
|
-
}
|
|
2106
|
-
}
|
|
2107
|
-
}
|
|
2108
|
-
}
|
|
2109
|
-
}
|
|
2110
|
-
const lines = [];
|
|
2111
|
-
if (utilsImports.length > 0) {
|
|
2112
|
-
const utilsPath = context === "component" ? "../shared/utils" : "../../shared/utils";
|
|
2113
|
-
lines.push(`import type { ${utilsImports.join(", ")} } from '${utilsPath}';`);
|
|
2114
|
-
}
|
|
2115
|
-
for (const [typeName, importPath] of relationImports) {
|
|
2116
|
-
lines.push(`import type { ${typeName} } from '${importPath}';`);
|
|
2117
|
-
}
|
|
2118
|
-
for (const [typeName, importPath] of componentImports) {
|
|
2119
|
-
lines.push(`import type { ${typeName} } from '${importPath}';`);
|
|
2120
|
-
}
|
|
2121
|
-
return lines.join("\n");
|
|
2122
|
-
}
|
|
2123
|
-
function generateAttributes2(attributes) {
|
|
3858
|
+
function generateJSDocAttributes(attributes, schema, context) {
|
|
2124
3859
|
const lines = [];
|
|
3860
|
+
const relativePrefix = context === "component" ? ".." : "../..";
|
|
2125
3861
|
for (const [name, attr] of Object.entries(attributes)) {
|
|
2126
|
-
const
|
|
2127
|
-
const optional = attr.required ? "" : "
|
|
2128
|
-
|
|
3862
|
+
const jsType = attributeToJSDocType(attr, schema, relativePrefix);
|
|
3863
|
+
const optional = attr.required ? "" : "[";
|
|
3864
|
+
const optionalEnd = attr.required ? "" : "]";
|
|
3865
|
+
lines.push(` * @property {${jsType}} ${optional}${name}${optionalEnd}`);
|
|
2129
3866
|
}
|
|
2130
3867
|
return lines.join("\n");
|
|
2131
3868
|
}
|
|
2132
|
-
function
|
|
3869
|
+
function attributeToJSDocType(attr, schema, relativePrefix) {
|
|
2133
3870
|
switch (attr.type) {
|
|
2134
3871
|
case "string":
|
|
2135
3872
|
case "text":
|
|
@@ -2139,7 +3876,7 @@ function attributeToTsType2(attr) {
|
|
|
2139
3876
|
case "uid":
|
|
2140
3877
|
return "string";
|
|
2141
3878
|
case "blocks":
|
|
2142
|
-
return "
|
|
3879
|
+
return "Array<Object>";
|
|
2143
3880
|
case "integer":
|
|
2144
3881
|
case "biginteger":
|
|
2145
3882
|
case "float":
|
|
@@ -2153,130 +3890,116 @@ function attributeToTsType2(attr) {
|
|
|
2153
3890
|
case "timestamp":
|
|
2154
3891
|
return "string";
|
|
2155
3892
|
case "json":
|
|
2156
|
-
return "
|
|
3893
|
+
return "Object";
|
|
2157
3894
|
case "enumeration":
|
|
2158
3895
|
if ("enum" in attr && attr.enum) {
|
|
2159
|
-
return attr.enum.map((v) => `'${v}'`).join("
|
|
3896
|
+
return attr.enum.map((v) => `'${v}'`).join("|");
|
|
2160
3897
|
}
|
|
2161
3898
|
return "string";
|
|
2162
3899
|
case "media":
|
|
2163
3900
|
if ("multiple" in attr && attr.multiple) {
|
|
2164
|
-
return "StrapiMedia[]
|
|
3901
|
+
return `import("${relativePrefix}/shared/utils").StrapiMedia[]`;
|
|
2165
3902
|
}
|
|
2166
|
-
return "StrapiMedia
|
|
3903
|
+
return `import("${relativePrefix}/shared/utils").StrapiMedia|null`;
|
|
2167
3904
|
case "relation":
|
|
2168
3905
|
if ("target" in attr && attr.target) {
|
|
2169
|
-
const targetName =
|
|
3906
|
+
const targetName = attr.target.split(".").pop() || "unknown";
|
|
3907
|
+
const typeName = toPascalCase(targetName);
|
|
3908
|
+
const fileName = toKebabCase(targetName);
|
|
2170
3909
|
const isMany = attr.relation === "oneToMany" || attr.relation === "manyToMany";
|
|
2171
|
-
|
|
3910
|
+
const isCollection = schema.collections.some((c) => c.singularName === targetName);
|
|
3911
|
+
const isSingle = schema.singles.some((s) => s.singularName === targetName);
|
|
3912
|
+
let importPath;
|
|
3913
|
+
if (isCollection) {
|
|
3914
|
+
importPath = `${relativePrefix}/collections/${fileName}/types`;
|
|
3915
|
+
} else if (isSingle) {
|
|
3916
|
+
importPath = `${relativePrefix}/singles/${fileName}/types`;
|
|
3917
|
+
} else {
|
|
3918
|
+
importPath = `${relativePrefix}/collections/${fileName}/types`;
|
|
3919
|
+
}
|
|
3920
|
+
const importType = `import("${importPath}").${typeName}`;
|
|
3921
|
+
return isMany ? `${importType}[]` : `${importType}|null`;
|
|
2172
3922
|
}
|
|
2173
|
-
return "
|
|
3923
|
+
return "Object";
|
|
2174
3924
|
case "component":
|
|
2175
3925
|
if ("component" in attr && attr.component) {
|
|
2176
|
-
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}`;
|
|
2177
3931
|
if ("repeatable" in attr && attr.repeatable) {
|
|
2178
|
-
return `${
|
|
3932
|
+
return `${importType}[]`;
|
|
2179
3933
|
}
|
|
2180
|
-
return `${
|
|
3934
|
+
return `${importType}|null`;
|
|
2181
3935
|
}
|
|
2182
|
-
return "
|
|
3936
|
+
return "Object";
|
|
2183
3937
|
case "dynamiczone":
|
|
2184
3938
|
if ("components" in attr && attr.components) {
|
|
2185
|
-
const types = attr.components.map((
|
|
2186
|
-
|
|
3939
|
+
const types = attr.components.map((comp) => {
|
|
3940
|
+
const componentName = comp.split(".").pop() || "unknown";
|
|
3941
|
+
const typeName = toPascalCase(componentName);
|
|
3942
|
+
const fileName = toKebabCase(componentName);
|
|
3943
|
+
const importPath = `${relativePrefix}/components/${fileName}`;
|
|
3944
|
+
return `import("${importPath}").${typeName}`;
|
|
3945
|
+
});
|
|
3946
|
+
return `(${types.join("|")})[]`;
|
|
2187
3947
|
}
|
|
2188
|
-
return "
|
|
3948
|
+
return "Object[]";
|
|
2189
3949
|
default:
|
|
2190
|
-
return "
|
|
3950
|
+
return "Object";
|
|
2191
3951
|
}
|
|
2192
3952
|
}
|
|
2193
|
-
function
|
|
3953
|
+
function generateCollectionServiceJSDoc(collection, strapiVersion) {
|
|
2194
3954
|
const typeName = toPascalCase(collection.singularName);
|
|
2195
3955
|
const serviceName = toCamelCase(collection.singularName) + "Service";
|
|
2196
3956
|
const endpoint = collection.pluralName;
|
|
2197
3957
|
const hasSlug = "slug" in collection.attributes;
|
|
2198
3958
|
const { localized, draftAndPublish } = collection;
|
|
2199
3959
|
const isV4 = strapiVersion === "v4";
|
|
2200
|
-
const idParam = isV4 ? "id
|
|
2201
|
-
const
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
const imports = [
|
|
2205
|
-
`import { collection } from '../../shared/client';`,
|
|
2206
|
-
`import type { ${typeName}, ${typeName}Filters } from './types';`,
|
|
2207
|
-
`import type { StrapiPagination } from '../../shared/utils';`
|
|
2208
|
-
];
|
|
2209
|
-
if (localized) {
|
|
2210
|
-
imports.push(`import type { Locale } from '../../shared/locales';`);
|
|
2211
|
-
}
|
|
2212
|
-
const paginationFields = `
|
|
2213
|
-
/** Page number (1-indexed) - use with pageSize */
|
|
2214
|
-
page?: number;
|
|
2215
|
-
/** Number of items per page (default: 25) - use with page */
|
|
2216
|
-
pageSize?: number;
|
|
2217
|
-
/** Offset to start from (0-indexed) - use with limit */
|
|
2218
|
-
start?: number;
|
|
2219
|
-
/** Maximum number of items to return - use with start */
|
|
2220
|
-
limit?: number;`;
|
|
2221
|
-
let findManyOptionsFields = ` filters?: ${typeName}Filters;
|
|
2222
|
-
pagination?: {${paginationFields}
|
|
2223
|
-
};
|
|
2224
|
-
sort?: string | string[];
|
|
2225
|
-
populate?: string | string[] | Record<string, unknown>;`;
|
|
2226
|
-
let findOneOptionsFields = ` populate?: string | string[] | Record<string, unknown>;`;
|
|
2227
|
-
if (localized) {
|
|
2228
|
-
findManyOptionsFields += `
|
|
2229
|
-
locale?: Locale;`;
|
|
2230
|
-
findOneOptionsFields += `
|
|
2231
|
-
locale?: Locale;`;
|
|
2232
|
-
}
|
|
2233
|
-
if (draftAndPublish) {
|
|
2234
|
-
findManyOptionsFields += `
|
|
2235
|
-
status?: 'draft' | 'published';`;
|
|
2236
|
-
findOneOptionsFields += `
|
|
2237
|
-
status?: 'draft' | 'published';`;
|
|
2238
|
-
}
|
|
2239
|
-
let findParams = ` filters: options.filters,
|
|
2240
|
-
pagination: options.pagination,
|
|
2241
|
-
sort: options.sort,
|
|
2242
|
-
populate: options.populate,`;
|
|
2243
|
-
let findOneParams = ` populate: options.populate,`;
|
|
2244
|
-
if (localized) {
|
|
2245
|
-
findParams += `
|
|
2246
|
-
locale: options.locale,`;
|
|
2247
|
-
findOneParams += `
|
|
2248
|
-
locale: options.locale,`;
|
|
2249
|
-
}
|
|
2250
|
-
if (draftAndPublish) {
|
|
2251
|
-
findParams += `
|
|
2252
|
-
status: options.status,`;
|
|
2253
|
-
findOneParams += `
|
|
2254
|
-
status: options.status,`;
|
|
2255
|
-
}
|
|
2256
|
-
return `/**
|
|
3960
|
+
const idParam = isV4 ? "id" : "documentId";
|
|
3961
|
+
const idType = isV4 ? "number" : "string";
|
|
3962
|
+
return `// @ts-check
|
|
3963
|
+
/**
|
|
2257
3964
|
* ${collection.displayName} Service
|
|
2258
3965
|
* ${collection.description || ""}
|
|
2259
3966
|
* Generated by strapi2front
|
|
2260
3967
|
* Strapi version: ${strapiVersion}
|
|
2261
3968
|
*/
|
|
2262
3969
|
|
|
2263
|
-
|
|
3970
|
+
const { collection } = require('../../shared/client');
|
|
2264
3971
|
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
}
|
|
3972
|
+
/**
|
|
3973
|
+
* @typedef {Object} FindManyOptions
|
|
3974
|
+
* @property {import('./types').${typeName}Filters} [filters]
|
|
3975
|
+
* @property {Object} [pagination]
|
|
3976
|
+
* @property {number} [pagination.page]
|
|
3977
|
+
* @property {number} [pagination.pageSize]
|
|
3978
|
+
* @property {number} [pagination.start]
|
|
3979
|
+
* @property {number} [pagination.limit]
|
|
3980
|
+
* @property {string|string[]} [sort]
|
|
3981
|
+
* @property {string|string[]|Record<string, unknown>} [populate]${localized ? "\n * @property {string} [locale]" : ""}${draftAndPublish ? "\n * @property {'draft'|'published'} [status]" : ""}
|
|
3982
|
+
*/
|
|
2268
3983
|
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
}
|
|
3984
|
+
/**
|
|
3985
|
+
* @typedef {Object} FindOneOptions
|
|
3986
|
+
* @property {string|string[]|Record<string, unknown>} [populate]${localized ? "\n * @property {string} [locale]" : ""}${draftAndPublish ? "\n * @property {'draft'|'published'} [status]" : ""}
|
|
3987
|
+
*/
|
|
2272
3988
|
|
|
2273
|
-
|
|
2274
|
-
const ${toCamelCase(collection.singularName)}Collection = collection
|
|
3989
|
+
/** @type {ReturnType<typeof collection<import('./types').${typeName}>>} */
|
|
3990
|
+
const ${toCamelCase(collection.singularName)}Collection = collection('${endpoint}');
|
|
2275
3991
|
|
|
2276
|
-
|
|
2277
|
-
|
|
3992
|
+
const ${serviceName} = {
|
|
3993
|
+
/**
|
|
3994
|
+
* @param {FindManyOptions} [options]
|
|
3995
|
+
* @returns {Promise<{ data: import('./types').${typeName}[], pagination: import('../../shared/utils').StrapiPagination }>}
|
|
3996
|
+
*/
|
|
3997
|
+
async findMany(options = {}) {
|
|
2278
3998
|
const response = await ${toCamelCase(collection.singularName)}Collection.find({
|
|
2279
|
-
|
|
3999
|
+
filters: options.filters,
|
|
4000
|
+
pagination: options.pagination,
|
|
4001
|
+
sort: options.sort,
|
|
4002
|
+
populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
|
|
2280
4003
|
});
|
|
2281
4004
|
|
|
2282
4005
|
return {
|
|
@@ -2285,8 +4008,13 @@ ${findParams}
|
|
|
2285
4008
|
};
|
|
2286
4009
|
},
|
|
2287
4010
|
|
|
2288
|
-
|
|
2289
|
-
|
|
4011
|
+
/**
|
|
4012
|
+
* @param {Omit<FindManyOptions, 'pagination'>} [options]
|
|
4013
|
+
* @returns {Promise<import('./types').${typeName}[]>}
|
|
4014
|
+
*/
|
|
4015
|
+
async findAll(options = {}) {
|
|
4016
|
+
/** @type {import('./types').${typeName}[]} */
|
|
4017
|
+
const allItems = [];
|
|
2290
4018
|
let page = 1;
|
|
2291
4019
|
let hasMore = true;
|
|
2292
4020
|
|
|
@@ -2304,10 +4032,15 @@ ${findParams}
|
|
|
2304
4032
|
return allItems;
|
|
2305
4033
|
},
|
|
2306
4034
|
|
|
2307
|
-
|
|
4035
|
+
/**
|
|
4036
|
+
* @param {${idType}} ${idParam}
|
|
4037
|
+
* @param {FindOneOptions} [options]
|
|
4038
|
+
* @returns {Promise<import('./types').${typeName}|null>}
|
|
4039
|
+
*/
|
|
4040
|
+
async findOne(${idParam}, options = {}) {
|
|
2308
4041
|
try {
|
|
2309
|
-
const response = await ${toCamelCase(collection.singularName)}Collection.findOne(${
|
|
2310
|
-
${
|
|
4042
|
+
const response = await ${toCamelCase(collection.singularName)}Collection.findOne(${idParam}, {
|
|
4043
|
+
populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
|
|
2311
4044
|
});
|
|
2312
4045
|
|
|
2313
4046
|
return response.data;
|
|
@@ -2319,9 +4052,14 @@ ${findOneParams}
|
|
|
2319
4052
|
}
|
|
2320
4053
|
},
|
|
2321
4054
|
${hasSlug ? `
|
|
2322
|
-
|
|
4055
|
+
/**
|
|
4056
|
+
* @param {string} slug
|
|
4057
|
+
* @param {FindOneOptions} [options]
|
|
4058
|
+
* @returns {Promise<import('./types').${typeName}|null>}
|
|
4059
|
+
*/
|
|
4060
|
+
async findBySlug(slug, options = {}) {
|
|
2323
4061
|
const { data } = await this.findMany({
|
|
2324
|
-
filters: { slug: { $eq: slug } }
|
|
4062
|
+
filters: /** @type {import('./types').${typeName}Filters} */ ({ slug: { $eq: slug } }),
|
|
2325
4063
|
pagination: { pageSize: 1 },
|
|
2326
4064
|
populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
|
|
2327
4065
|
});
|
|
@@ -2329,21 +4067,38 @@ ${hasSlug ? `
|
|
|
2329
4067
|
return data[0] || null;
|
|
2330
4068
|
},
|
|
2331
4069
|
` : ""}
|
|
2332
|
-
|
|
4070
|
+
/**
|
|
4071
|
+
* @param {Partial<import('./types').${typeName}>} data
|
|
4072
|
+
* @returns {Promise<import('./types').${typeName}>}
|
|
4073
|
+
*/
|
|
4074
|
+
async create(data) {
|
|
2333
4075
|
const response = await ${toCamelCase(collection.singularName)}Collection.create({ data });
|
|
2334
4076
|
return response.data;
|
|
2335
4077
|
},
|
|
2336
4078
|
|
|
2337
|
-
|
|
2338
|
-
|
|
4079
|
+
/**
|
|
4080
|
+
* @param {${idType}} ${idParam}
|
|
4081
|
+
* @param {Partial<import('./types').${typeName}>} data
|
|
4082
|
+
* @returns {Promise<import('./types').${typeName}>}
|
|
4083
|
+
*/
|
|
4084
|
+
async update(${idParam}, data) {
|
|
4085
|
+
const response = await ${toCamelCase(collection.singularName)}Collection.update(${idParam}, { data });
|
|
2339
4086
|
return response.data;
|
|
2340
4087
|
},
|
|
2341
4088
|
|
|
2342
|
-
|
|
2343
|
-
|
|
4089
|
+
/**
|
|
4090
|
+
* @param {${idType}} ${idParam}
|
|
4091
|
+
* @returns {Promise<void>}
|
|
4092
|
+
*/
|
|
4093
|
+
async delete(${idParam}) {
|
|
4094
|
+
await ${toCamelCase(collection.singularName)}Collection.delete(${idParam});
|
|
2344
4095
|
},
|
|
2345
4096
|
|
|
2346
|
-
|
|
4097
|
+
/**
|
|
4098
|
+
* @param {import('./types').${typeName}Filters} [filters]
|
|
4099
|
+
* @returns {Promise<number>}
|
|
4100
|
+
*/
|
|
4101
|
+
async count(filters) {
|
|
2347
4102
|
const { pagination } = await this.findMany({
|
|
2348
4103
|
filters,
|
|
2349
4104
|
pagination: { pageSize: 1 },
|
|
@@ -2352,61 +4107,42 @@ ${hasSlug ? `
|
|
|
2352
4107
|
return pagination.total;
|
|
2353
4108
|
},
|
|
2354
4109
|
};
|
|
4110
|
+
|
|
4111
|
+
module.exports = { ${serviceName} };
|
|
2355
4112
|
`;
|
|
2356
4113
|
}
|
|
2357
|
-
function
|
|
4114
|
+
function generateSingleServiceJSDoc(single, strapiVersion) {
|
|
2358
4115
|
const typeName = toPascalCase(single.singularName);
|
|
2359
4116
|
const serviceName = toCamelCase(single.singularName) + "Service";
|
|
2360
4117
|
const endpoint = single.singularName;
|
|
2361
4118
|
const { localized, draftAndPublish } = single;
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
const imports = [
|
|
2365
|
-
`import { single } from '../../shared/client';`,
|
|
2366
|
-
`import type { ${typeName} } from './types';`
|
|
2367
|
-
];
|
|
2368
|
-
if (localized) {
|
|
2369
|
-
imports.push(`import type { Locale } from '../../shared/locales';`);
|
|
2370
|
-
}
|
|
2371
|
-
let findOptionsFields = ` populate?: string | string[] | Record<string, unknown>;`;
|
|
2372
|
-
if (localized) {
|
|
2373
|
-
findOptionsFields += `
|
|
2374
|
-
locale?: Locale;`;
|
|
2375
|
-
}
|
|
2376
|
-
if (draftAndPublish) {
|
|
2377
|
-
findOptionsFields += `
|
|
2378
|
-
status?: 'draft' | 'published';`;
|
|
2379
|
-
}
|
|
2380
|
-
let findParams = ` populate: options.populate,`;
|
|
2381
|
-
if (localized) {
|
|
2382
|
-
findParams += `
|
|
2383
|
-
locale: options.locale,`;
|
|
2384
|
-
}
|
|
2385
|
-
if (draftAndPublish) {
|
|
2386
|
-
findParams += `
|
|
2387
|
-
status: options.status,`;
|
|
2388
|
-
}
|
|
2389
|
-
return `/**
|
|
4119
|
+
return `// @ts-check
|
|
4120
|
+
/**
|
|
2390
4121
|
* ${single.displayName} Service (Single Type)
|
|
2391
4122
|
* ${single.description || ""}
|
|
2392
4123
|
* Generated by strapi2front
|
|
2393
4124
|
* Strapi version: ${strapiVersion}
|
|
2394
4125
|
*/
|
|
2395
4126
|
|
|
2396
|
-
|
|
4127
|
+
const { single } = require('../../shared/client');
|
|
2397
4128
|
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
}
|
|
4129
|
+
/**
|
|
4130
|
+
* @typedef {Object} FindOptions
|
|
4131
|
+
* @property {string|string[]|Record<string, unknown>} [populate]${localized ? "\n * @property {string} [locale]" : ""}${draftAndPublish ? "\n * @property {'draft'|'published'} [status]" : ""}
|
|
4132
|
+
*/
|
|
2401
4133
|
|
|
2402
|
-
|
|
2403
|
-
const ${toCamelCase(single.singularName)}Single = single
|
|
4134
|
+
/** @type {ReturnType<typeof single<import('./types').${typeName}>>} */
|
|
4135
|
+
const ${toCamelCase(single.singularName)}Single = single('${endpoint}');
|
|
2404
4136
|
|
|
2405
|
-
|
|
2406
|
-
|
|
4137
|
+
const ${serviceName} = {
|
|
4138
|
+
/**
|
|
4139
|
+
* @param {FindOptions} [options]
|
|
4140
|
+
* @returns {Promise<import('./types').${typeName}|null>}
|
|
4141
|
+
*/
|
|
4142
|
+
async find(options = {}) {
|
|
2407
4143
|
try {
|
|
2408
4144
|
const response = await ${toCamelCase(single.singularName)}Single.find({
|
|
2409
|
-
${
|
|
4145
|
+
populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
|
|
2410
4146
|
});
|
|
2411
4147
|
|
|
2412
4148
|
return response.data;
|
|
@@ -2418,96 +4154,60 @@ ${findParams}
|
|
|
2418
4154
|
}
|
|
2419
4155
|
},
|
|
2420
4156
|
|
|
2421
|
-
|
|
4157
|
+
/**
|
|
4158
|
+
* @param {Partial<import('./types').${typeName}>} data
|
|
4159
|
+
* @returns {Promise<import('./types').${typeName}>}
|
|
4160
|
+
*/
|
|
4161
|
+
async update(data) {
|
|
2422
4162
|
const response = await ${toCamelCase(single.singularName)}Single.update({ data });
|
|
2423
4163
|
return response.data;
|
|
2424
4164
|
},
|
|
2425
4165
|
|
|
2426
|
-
|
|
4166
|
+
/**
|
|
4167
|
+
* @returns {Promise<void>}
|
|
4168
|
+
*/
|
|
4169
|
+
async delete() {
|
|
2427
4170
|
await ${toCamelCase(single.singularName)}Single.delete();
|
|
2428
4171
|
},
|
|
2429
4172
|
};
|
|
4173
|
+
|
|
4174
|
+
module.exports = { ${serviceName} };
|
|
2430
4175
|
`;
|
|
2431
4176
|
}
|
|
2432
|
-
function generateCollectionActions2(collection, strapiVersion) {
|
|
2433
|
-
const typeName = toPascalCase(collection.singularName);
|
|
2434
|
-
const serviceName = toCamelCase(collection.singularName) + "Service";
|
|
2435
|
-
const actionPrefix = toCamelCase(collection.singularName);
|
|
2436
|
-
const isV4 = strapiVersion === "v4";
|
|
2437
|
-
const idInputSchema = isV4 ? "z.number().int().positive()" : "z.string()";
|
|
2438
|
-
const idParamName = isV4 ? "id" : "documentId";
|
|
2439
|
-
return `/**
|
|
2440
|
-
* ${collection.displayName} Astro Actions
|
|
2441
|
-
* ${collection.description || ""}
|
|
2442
|
-
* Generated by strapi2front
|
|
2443
|
-
* Strapi version: ${strapiVersion}
|
|
2444
|
-
*/
|
|
2445
|
-
|
|
2446
|
-
import { defineAction } from 'astro:actions';
|
|
2447
|
-
import { z } from 'astro:schema';
|
|
2448
|
-
import { ${serviceName} } from './service';
|
|
2449
|
-
import type { ${typeName} } from './types';
|
|
2450
|
-
|
|
2451
|
-
export const ${actionPrefix}Actions = {
|
|
2452
|
-
getMany: defineAction({
|
|
2453
|
-
input: z.object({
|
|
2454
|
-
page: z.number().optional(),
|
|
2455
|
-
pageSize: z.number().optional(),
|
|
2456
|
-
sort: z.string().optional(),
|
|
2457
|
-
}).optional(),
|
|
2458
|
-
handler: async (input) => {
|
|
2459
|
-
const { data, pagination } = await ${serviceName}.findMany({
|
|
2460
|
-
pagination: input ? { page: input.page, pageSize: input.pageSize } : undefined,
|
|
2461
|
-
sort: input?.sort,
|
|
2462
|
-
});
|
|
2463
|
-
return { data, pagination };
|
|
2464
|
-
},
|
|
2465
|
-
}),
|
|
2466
|
-
|
|
2467
|
-
getOne: defineAction({
|
|
2468
|
-
input: z.object({
|
|
2469
|
-
${idParamName}: ${idInputSchema},
|
|
2470
|
-
}),
|
|
2471
|
-
handler: async (input) => {
|
|
2472
|
-
const data = await ${serviceName}.findOne(input.${idParamName});
|
|
2473
|
-
return { data };
|
|
2474
|
-
},
|
|
2475
|
-
}),
|
|
2476
4177
|
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
4178
|
+
// src/frameworks/nextjs/actions.ts
|
|
4179
|
+
async function generateNextJsActions(_schema, _options) {
|
|
4180
|
+
throw new Error(
|
|
4181
|
+
"Next.js Server Actions generator is not yet implemented. Coming soon! For now, you can use the generated services directly."
|
|
4182
|
+
);
|
|
4183
|
+
}
|
|
4184
|
+
function isNextJsActionsSupported(nextVersion) {
|
|
4185
|
+
if (!nextVersion) return false;
|
|
4186
|
+
const match = nextVersion.replace(/^[\^~]/, "").match(/^(\d+)/);
|
|
4187
|
+
const majorVersion = match ? parseInt(match[1], 10) : null;
|
|
4188
|
+
return majorVersion !== null && majorVersion >= 14;
|
|
4189
|
+
}
|
|
2486
4190
|
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
4191
|
+
// src/frameworks/nuxt/server-routes.ts
|
|
4192
|
+
async function generateNuxtServerRoutes(_schema, _options) {
|
|
4193
|
+
throw new Error(
|
|
4194
|
+
"Nuxt Server Routes generator is not yet implemented. Coming soon! For now, you can use the generated services directly."
|
|
4195
|
+
);
|
|
4196
|
+
}
|
|
4197
|
+
function isNuxtServerRoutesSupported(nuxtVersion) {
|
|
4198
|
+
if (!nuxtVersion) return false;
|
|
4199
|
+
const match = nuxtVersion.replace(/^[\^~]/, "").match(/^(\d+)/);
|
|
4200
|
+
const majorVersion = match ? parseInt(match[1], 10) : null;
|
|
4201
|
+
return majorVersion !== null && majorVersion >= 3;
|
|
4202
|
+
}
|
|
2497
4203
|
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
await ${serviceName}.delete(input.${idParamName});
|
|
2504
|
-
return { success: true };
|
|
2505
|
-
},
|
|
2506
|
-
}),
|
|
4204
|
+
// src/frameworks/index.ts
|
|
4205
|
+
var frameworkSupport = {
|
|
4206
|
+
astro: { status: "stable", minVersion: "4.0.0" },
|
|
4207
|
+
nextjs: { status: "coming-soon", minVersion: "14.0.0" },
|
|
4208
|
+
nuxt: { status: "coming-soon", minVersion: "3.0.0" }
|
|
2507
4209
|
};
|
|
2508
|
-
`;
|
|
2509
|
-
}
|
|
2510
4210
|
|
|
2511
|
-
export { deleteFile, ensureDir, fileExists, formatCode, formatJson, generateActions, generateByFeature, generateClient, generateLocales, generateServices, generateTypes, listFiles, pluralize, readFile, toCamelCase, toKebabCase, toPascalCase, writeFile };
|
|
4211
|
+
export { deleteFile, ensureDir, fileExists, formatCode, formatJson, frameworkSupport, generateActions, generateAstroActions, generateByFeature, generateClient, generateJSDocServices, generateJSDocTypes, generateLocales, generateNextJsActions, generateNuxtServerRoutes, generateServices, generateTypeScriptTypes, generateTypes, isAstroActionsSupported, isNextJsActionsSupported, isNuxtServerRoutesSupported, listFiles, pluralize, readFile, toCamelCase, toKebabCase, toPascalCase, writeFile };
|
|
2512
4212
|
//# sourceMappingURL=index.js.map
|
|
2513
4213
|
//# sourceMappingURL=index.js.map
|