@strapi2front/generators 0.4.0 → 0.5.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 +116 -2
- package/dist/index.js +1803 -288
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1476,6 +1476,303 @@ ${findParams.join("\n")}
|
|
|
1476
1476
|
};
|
|
1477
1477
|
`;
|
|
1478
1478
|
}
|
|
1479
|
+
|
|
1480
|
+
// src/shared/blocks-schema.ts
|
|
1481
|
+
function getCompactBlocksSchema() {
|
|
1482
|
+
return `z.array(
|
|
1483
|
+
z.discriminatedUnion('type', [
|
|
1484
|
+
z.object({ type: z.literal('paragraph'), children: z.array(z.object({ type: z.literal('text'), text: z.string() }).passthrough()) }),
|
|
1485
|
+
z.object({ type: z.literal('heading'), level: z.number().int().min(1).max(6), children: z.array(z.object({ type: z.literal('text'), text: z.string() }).passthrough()) }),
|
|
1486
|
+
z.object({ type: z.literal('list'), format: z.enum(['ordered', 'unordered']), children: z.array(z.object({ type: z.literal('list-item'), children: z.array(z.unknown()) })) }),
|
|
1487
|
+
z.object({ type: z.literal('quote'), children: z.array(z.object({ type: z.literal('text'), text: z.string() }).passthrough()) }),
|
|
1488
|
+
z.object({ type: z.literal('code'), children: z.array(z.object({ type: z.literal('text'), text: z.string() })) }),
|
|
1489
|
+
z.object({ type: z.literal('image'), image: z.object({ url: z.string(), width: z.number(), height: z.number() }).passthrough(), children: z.array(z.unknown()) }),
|
|
1490
|
+
])
|
|
1491
|
+
)`;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// src/shared/zod-mapper.ts
|
|
1495
|
+
var SYSTEM_FIELDS = /* @__PURE__ */ new Set([
|
|
1496
|
+
"id",
|
|
1497
|
+
"documentId",
|
|
1498
|
+
"createdAt",
|
|
1499
|
+
"updatedAt",
|
|
1500
|
+
"publishedAt",
|
|
1501
|
+
"createdBy",
|
|
1502
|
+
"updatedBy",
|
|
1503
|
+
"localizations",
|
|
1504
|
+
"locale"
|
|
1505
|
+
]);
|
|
1506
|
+
function isSystemField(fieldName) {
|
|
1507
|
+
return SYSTEM_FIELDS.has(fieldName);
|
|
1508
|
+
}
|
|
1509
|
+
function mapAttributeToZodSchema(attr, options = {}) {
|
|
1510
|
+
const { isUpdate = false } = options;
|
|
1511
|
+
let schema = buildBaseSchema(attr, options);
|
|
1512
|
+
if (!schema) {
|
|
1513
|
+
return { schema: "", skip: true, skipReason: "Unsupported type" };
|
|
1514
|
+
}
|
|
1515
|
+
if (isUpdate || !attr.required) {
|
|
1516
|
+
schema = `${schema}.optional()`;
|
|
1517
|
+
}
|
|
1518
|
+
if (!isUpdate && attr.default !== void 0 && attr.default !== null) {
|
|
1519
|
+
const defaultValue = formatDefaultValue(attr.default, attr.type);
|
|
1520
|
+
if (defaultValue !== null) {
|
|
1521
|
+
schema = `${schema}.default(${defaultValue})`;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
return { schema, skip: false };
|
|
1525
|
+
}
|
|
1526
|
+
function buildBaseSchema(attr, options) {
|
|
1527
|
+
switch (attr.type) {
|
|
1528
|
+
// String types
|
|
1529
|
+
case "string":
|
|
1530
|
+
case "text":
|
|
1531
|
+
case "richtext":
|
|
1532
|
+
case "uid":
|
|
1533
|
+
return buildStringSchema(attr);
|
|
1534
|
+
case "email":
|
|
1535
|
+
return buildEmailSchema(attr);
|
|
1536
|
+
case "password":
|
|
1537
|
+
return buildPasswordSchema(attr);
|
|
1538
|
+
// Blocks (Strapi v5 rich text)
|
|
1539
|
+
// Uses structured schema with all block types: paragraph, heading, list, quote, code, image
|
|
1540
|
+
case "blocks":
|
|
1541
|
+
return getCompactBlocksSchema();
|
|
1542
|
+
// Number types
|
|
1543
|
+
case "integer":
|
|
1544
|
+
case "biginteger":
|
|
1545
|
+
return buildIntegerSchema(attr);
|
|
1546
|
+
case "float":
|
|
1547
|
+
case "decimal":
|
|
1548
|
+
return buildFloatSchema(attr);
|
|
1549
|
+
// Boolean
|
|
1550
|
+
case "boolean":
|
|
1551
|
+
return "z.boolean()";
|
|
1552
|
+
// Date types
|
|
1553
|
+
case "date":
|
|
1554
|
+
return "z.string().date()";
|
|
1555
|
+
case "time":
|
|
1556
|
+
return "z.string().time()";
|
|
1557
|
+
case "datetime":
|
|
1558
|
+
case "timestamp":
|
|
1559
|
+
return "z.string().datetime({ offset: true })";
|
|
1560
|
+
// JSON
|
|
1561
|
+
case "json":
|
|
1562
|
+
return "z.record(z.unknown())";
|
|
1563
|
+
// Enumeration
|
|
1564
|
+
case "enumeration":
|
|
1565
|
+
return buildEnumSchema(attr);
|
|
1566
|
+
// Media - accepts file IDs (numbers) from upload API
|
|
1567
|
+
case "media":
|
|
1568
|
+
return buildMediaSchema(attr);
|
|
1569
|
+
// Relation - accepts documentIds (v5) or ids (v4)
|
|
1570
|
+
case "relation":
|
|
1571
|
+
return buildRelationSchema(attr, options);
|
|
1572
|
+
// Component
|
|
1573
|
+
case "component":
|
|
1574
|
+
return buildComponentSchema(attr, options);
|
|
1575
|
+
// Dynamic zone
|
|
1576
|
+
case "dynamiczone":
|
|
1577
|
+
return buildDynamicZoneSchema(attr, options);
|
|
1578
|
+
default:
|
|
1579
|
+
return "z.unknown()";
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
function buildStringSchema(attr) {
|
|
1583
|
+
const parts = ["z.string()"];
|
|
1584
|
+
if (attr.minLength !== void 0) {
|
|
1585
|
+
parts.push(`.min(${attr.minLength})`);
|
|
1586
|
+
}
|
|
1587
|
+
if (attr.maxLength !== void 0) {
|
|
1588
|
+
parts.push(`.max(${attr.maxLength})`);
|
|
1589
|
+
}
|
|
1590
|
+
if (attr.regex) {
|
|
1591
|
+
const escapedRegex = attr.regex.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1592
|
+
parts.push(`.regex(new RegExp('${escapedRegex}'))`);
|
|
1593
|
+
}
|
|
1594
|
+
return parts.join("");
|
|
1595
|
+
}
|
|
1596
|
+
function buildEmailSchema(attr) {
|
|
1597
|
+
let schema = "z.string().email()";
|
|
1598
|
+
if (attr.minLength !== void 0) {
|
|
1599
|
+
schema += `.min(${attr.minLength})`;
|
|
1600
|
+
}
|
|
1601
|
+
if (attr.maxLength !== void 0) {
|
|
1602
|
+
schema += `.max(${attr.maxLength})`;
|
|
1603
|
+
}
|
|
1604
|
+
return schema;
|
|
1605
|
+
}
|
|
1606
|
+
function buildPasswordSchema(attr) {
|
|
1607
|
+
let schema = "z.string()";
|
|
1608
|
+
const minLength = attr.minLength ?? 6;
|
|
1609
|
+
schema += `.min(${minLength})`;
|
|
1610
|
+
if (attr.maxLength !== void 0) {
|
|
1611
|
+
schema += `.max(${attr.maxLength})`;
|
|
1612
|
+
}
|
|
1613
|
+
return schema;
|
|
1614
|
+
}
|
|
1615
|
+
function buildIntegerSchema(attr) {
|
|
1616
|
+
const parts = ["z.number().int()"];
|
|
1617
|
+
if (attr.min !== void 0) {
|
|
1618
|
+
parts.push(`.min(${attr.min})`);
|
|
1619
|
+
}
|
|
1620
|
+
if (attr.max !== void 0) {
|
|
1621
|
+
parts.push(`.max(${attr.max})`);
|
|
1622
|
+
}
|
|
1623
|
+
return parts.join("");
|
|
1624
|
+
}
|
|
1625
|
+
function buildFloatSchema(attr) {
|
|
1626
|
+
const parts = ["z.number()"];
|
|
1627
|
+
if (attr.min !== void 0) {
|
|
1628
|
+
parts.push(`.min(${attr.min})`);
|
|
1629
|
+
}
|
|
1630
|
+
if (attr.max !== void 0) {
|
|
1631
|
+
parts.push(`.max(${attr.max})`);
|
|
1632
|
+
}
|
|
1633
|
+
return parts.join("");
|
|
1634
|
+
}
|
|
1635
|
+
function buildEnumSchema(attr) {
|
|
1636
|
+
if (!attr.enum || attr.enum.length === 0) {
|
|
1637
|
+
return "z.string()";
|
|
1638
|
+
}
|
|
1639
|
+
const enumValues = attr.enum.map((v) => `'${v}'`).join(", ");
|
|
1640
|
+
return `z.enum([${enumValues}])`;
|
|
1641
|
+
}
|
|
1642
|
+
function buildMediaSchema(attr) {
|
|
1643
|
+
if (attr.multiple) {
|
|
1644
|
+
return "z.array(z.number().int().positive()).default([])";
|
|
1645
|
+
}
|
|
1646
|
+
return "z.number().int().positive().nullable()";
|
|
1647
|
+
}
|
|
1648
|
+
function buildRelationSchema(attr, options = {}) {
|
|
1649
|
+
const isMany = attr.relation === "oneToMany" || attr.relation === "manyToMany";
|
|
1650
|
+
const isV5 = options.strapiVersion !== "v4";
|
|
1651
|
+
const useAdvanced = options.useAdvancedRelations && isV5;
|
|
1652
|
+
const idSchema = isV5 ? "z.string()" : "z.number().int().positive()";
|
|
1653
|
+
if (useAdvanced) {
|
|
1654
|
+
const positionSchema = `z.object({
|
|
1655
|
+
before: z.string().optional(),
|
|
1656
|
+
after: z.string().optional(),
|
|
1657
|
+
start: z.boolean().optional(),
|
|
1658
|
+
end: z.boolean().optional(),
|
|
1659
|
+
}).optional()`;
|
|
1660
|
+
const relationItemSchema = `z.object({
|
|
1661
|
+
documentId: ${idSchema},
|
|
1662
|
+
/** Locale for i18n content types */
|
|
1663
|
+
locale: z.string().optional(),
|
|
1664
|
+
/** Target draft or published version */
|
|
1665
|
+
status: z.enum(['draft', 'published']).optional(),
|
|
1666
|
+
/** Position for ordering */
|
|
1667
|
+
position: ${positionSchema},
|
|
1668
|
+
})`;
|
|
1669
|
+
const itemOrIdSchema = `z.union([${idSchema}, ${relationItemSchema}])`;
|
|
1670
|
+
const disconnectItemSchema = `z.union([
|
|
1671
|
+
${idSchema},
|
|
1672
|
+
z.object({
|
|
1673
|
+
documentId: ${idSchema},
|
|
1674
|
+
locale: z.string().optional(),
|
|
1675
|
+
status: z.enum(['draft', 'published']).optional(),
|
|
1676
|
+
})
|
|
1677
|
+
])`;
|
|
1678
|
+
return `z.union([
|
|
1679
|
+
// Shorthand: array of documentIds (equivalent to set)
|
|
1680
|
+
z.array(${idSchema}),
|
|
1681
|
+
// Longhand: object with connect/disconnect/set
|
|
1682
|
+
z.object({
|
|
1683
|
+
/** Add relations while preserving existing ones */
|
|
1684
|
+
connect: z.array(${itemOrIdSchema}).optional(),
|
|
1685
|
+
/** Remove specific relations */
|
|
1686
|
+
disconnect: z.array(${disconnectItemSchema}).optional(),
|
|
1687
|
+
/** Replace ALL relations (cannot combine with connect/disconnect) */
|
|
1688
|
+
set: z.array(${itemOrIdSchema}).optional(),
|
|
1689
|
+
})
|
|
1690
|
+
]).optional()`;
|
|
1691
|
+
}
|
|
1692
|
+
if (isMany) {
|
|
1693
|
+
return `z.array(${idSchema}).default([])`;
|
|
1694
|
+
}
|
|
1695
|
+
return `${idSchema}.nullable()`;
|
|
1696
|
+
}
|
|
1697
|
+
function buildComponentSchema(attr, options = {}) {
|
|
1698
|
+
const schemaName = options.componentSchemaNames?.get(attr.component);
|
|
1699
|
+
if (schemaName) {
|
|
1700
|
+
if (attr.repeatable) {
|
|
1701
|
+
return `z.array(${schemaName})`;
|
|
1702
|
+
}
|
|
1703
|
+
return `${schemaName}.nullable()`;
|
|
1704
|
+
}
|
|
1705
|
+
if (attr.repeatable) {
|
|
1706
|
+
return "z.array(z.record(z.unknown()))";
|
|
1707
|
+
}
|
|
1708
|
+
return "z.record(z.unknown()).nullable()";
|
|
1709
|
+
}
|
|
1710
|
+
function buildDynamicZoneSchema(attr, options = {}) {
|
|
1711
|
+
if (options.componentSchemaNames && attr.components && attr.components.length > 0) {
|
|
1712
|
+
const members = attr.components.map((uid) => {
|
|
1713
|
+
const schemaName = options.componentSchemaNames.get(uid);
|
|
1714
|
+
if (!schemaName) return null;
|
|
1715
|
+
return `${schemaName}.extend({ __component: z.literal('${uid}') })`;
|
|
1716
|
+
}).filter(Boolean);
|
|
1717
|
+
if (members.length > 0) {
|
|
1718
|
+
return `z.array(
|
|
1719
|
+
z.discriminatedUnion('__component', [
|
|
1720
|
+
${members.join(",\n ")},
|
|
1721
|
+
])
|
|
1722
|
+
)`;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
return "z.array(z.record(z.unknown()))";
|
|
1726
|
+
}
|
|
1727
|
+
function formatDefaultValue(value, type) {
|
|
1728
|
+
if (value === null || value === void 0) {
|
|
1729
|
+
return null;
|
|
1730
|
+
}
|
|
1731
|
+
switch (type) {
|
|
1732
|
+
case "string":
|
|
1733
|
+
case "text":
|
|
1734
|
+
case "richtext":
|
|
1735
|
+
case "email":
|
|
1736
|
+
case "uid":
|
|
1737
|
+
return typeof value === "string" ? `'${value.replace(/'/g, "\\'")}'` : null;
|
|
1738
|
+
case "integer":
|
|
1739
|
+
case "biginteger":
|
|
1740
|
+
case "float":
|
|
1741
|
+
case "decimal":
|
|
1742
|
+
return typeof value === "number" ? String(value) : null;
|
|
1743
|
+
case "boolean":
|
|
1744
|
+
return typeof value === "boolean" ? String(value) : null;
|
|
1745
|
+
case "enumeration":
|
|
1746
|
+
return typeof value === "string" ? `'${value}'` : null;
|
|
1747
|
+
default:
|
|
1748
|
+
return null;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
function generateZodObjectSchema(attributes, options = {}) {
|
|
1752
|
+
const fields = [];
|
|
1753
|
+
const skippedFields = [];
|
|
1754
|
+
for (const [name, attr] of Object.entries(attributes)) {
|
|
1755
|
+
if (isSystemField(name)) {
|
|
1756
|
+
continue;
|
|
1757
|
+
}
|
|
1758
|
+
if (attr.private) {
|
|
1759
|
+
skippedFields.push({ name, reason: "Private field" });
|
|
1760
|
+
continue;
|
|
1761
|
+
}
|
|
1762
|
+
const mapped = mapAttributeToZodSchema(attr, options);
|
|
1763
|
+
if (mapped.skip) {
|
|
1764
|
+
skippedFields.push({ name, reason: mapped.skipReason || "Unknown" });
|
|
1765
|
+
continue;
|
|
1766
|
+
}
|
|
1767
|
+
fields.push(` ${name}: ${mapped.schema},`);
|
|
1768
|
+
}
|
|
1769
|
+
const schema = `z.object({
|
|
1770
|
+
${fields.join("\n")}
|
|
1771
|
+
})`;
|
|
1772
|
+
return { schema, skippedFields };
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// src/frameworks/astro/actions.ts
|
|
1479
1776
|
function isAstroActionsSupported(astroVersion) {
|
|
1480
1777
|
if (!astroVersion) return false;
|
|
1481
1778
|
const match = astroVersion.replace(/^[\^~]/, "").match(/^(\d+)/);
|
|
@@ -1483,26 +1780,26 @@ function isAstroActionsSupported(astroVersion) {
|
|
|
1483
1780
|
return majorVersion !== null && majorVersion >= 4;
|
|
1484
1781
|
}
|
|
1485
1782
|
async function generateAstroActions(schema, options) {
|
|
1486
|
-
const { outputDir, servicesImportPath, strapiVersion = "v5" } = options;
|
|
1783
|
+
const { outputDir, servicesImportPath, strapiVersion = "v5", useTypedSchemas = true } = options;
|
|
1487
1784
|
const generatedFiles = [];
|
|
1488
1785
|
await ensureDir(outputDir);
|
|
1489
1786
|
for (const collection of schema.collections) {
|
|
1490
1787
|
const fileName = `${toKebabCase(collection.singularName)}.ts`;
|
|
1491
1788
|
const filePath = path9.join(outputDir, fileName);
|
|
1492
|
-
const content = generateCollectionActions(collection, servicesImportPath, strapiVersion);
|
|
1789
|
+
const content = generateCollectionActions(collection, servicesImportPath, strapiVersion, useTypedSchemas);
|
|
1493
1790
|
await writeFile(filePath, await formatCode(content));
|
|
1494
1791
|
generatedFiles.push(filePath);
|
|
1495
1792
|
}
|
|
1496
1793
|
for (const single of schema.singles) {
|
|
1497
1794
|
const fileName = `${toKebabCase(single.singularName)}.ts`;
|
|
1498
1795
|
const filePath = path9.join(outputDir, fileName);
|
|
1499
|
-
const content = generateSingleActions(single, servicesImportPath);
|
|
1796
|
+
const content = generateSingleActions(single, servicesImportPath, useTypedSchemas);
|
|
1500
1797
|
await writeFile(filePath, await formatCode(content));
|
|
1501
1798
|
generatedFiles.push(filePath);
|
|
1502
1799
|
}
|
|
1503
1800
|
return generatedFiles;
|
|
1504
1801
|
}
|
|
1505
|
-
function generateCollectionActions(collection, servicesImportPath, strapiVersion) {
|
|
1802
|
+
function generateCollectionActions(collection, servicesImportPath, strapiVersion, useTypedSchemas = false) {
|
|
1506
1803
|
const serviceName = toCamelCase(collection.singularName) + "Service";
|
|
1507
1804
|
const actionsName = toCamelCase(collection.singularName);
|
|
1508
1805
|
const fileName = toKebabCase(collection.singularName);
|
|
@@ -1511,6 +1808,26 @@ function generateCollectionActions(collection, servicesImportPath, strapiVersion
|
|
|
1511
1808
|
const idParamName = isV4 ? "id" : "documentId";
|
|
1512
1809
|
const idComment = isV4 ? "id" : "documentId";
|
|
1513
1810
|
const hasSlug = "slug" in collection.attributes;
|
|
1811
|
+
let createDataSchema = "z.record(z.unknown())";
|
|
1812
|
+
let updateDataSchema = "z.record(z.unknown())";
|
|
1813
|
+
let schemaDefinitions = "";
|
|
1814
|
+
if (useTypedSchemas) {
|
|
1815
|
+
const createResult = generateZodObjectSchema(collection.attributes, { isUpdate: false });
|
|
1816
|
+
const updateResult = generateZodObjectSchema(collection.attributes, { isUpdate: true });
|
|
1817
|
+
schemaDefinitions = `
|
|
1818
|
+
/**
|
|
1819
|
+
* Create schema for ${collection.displayName}
|
|
1820
|
+
*/
|
|
1821
|
+
const createSchema = ${createResult.schema};
|
|
1822
|
+
|
|
1823
|
+
/**
|
|
1824
|
+
* Update schema for ${collection.displayName}
|
|
1825
|
+
*/
|
|
1826
|
+
const updateSchema = ${updateResult.schema};
|
|
1827
|
+
`;
|
|
1828
|
+
createDataSchema = "createSchema";
|
|
1829
|
+
updateDataSchema = "updateSchema";
|
|
1830
|
+
}
|
|
1514
1831
|
return `/**
|
|
1515
1832
|
* ${collection.displayName} Actions
|
|
1516
1833
|
* ${collection.description || ""}
|
|
@@ -1530,6 +1847,7 @@ const paginationSchema = z.object({
|
|
|
1530
1847
|
page: z.number().int().positive().optional().default(1),
|
|
1531
1848
|
pageSize: z.number().int().positive().max(100).optional().default(25),
|
|
1532
1849
|
}).optional();
|
|
1850
|
+
${schemaDefinitions}
|
|
1533
1851
|
|
|
1534
1852
|
/**
|
|
1535
1853
|
* ${collection.displayName} actions
|
|
@@ -1625,7 +1943,7 @@ ${hasSlug ? `
|
|
|
1625
1943
|
*/
|
|
1626
1944
|
create: defineAction({
|
|
1627
1945
|
input: z.object({
|
|
1628
|
-
data:
|
|
1946
|
+
data: ${createDataSchema},
|
|
1629
1947
|
}),
|
|
1630
1948
|
handler: async ({ data }) => {
|
|
1631
1949
|
try {
|
|
@@ -1646,7 +1964,7 @@ ${hasSlug ? `
|
|
|
1646
1964
|
update: defineAction({
|
|
1647
1965
|
input: z.object({
|
|
1648
1966
|
${idParamName}: ${idInputSchema},
|
|
1649
|
-
data:
|
|
1967
|
+
data: ${updateDataSchema},
|
|
1650
1968
|
}),
|
|
1651
1969
|
handler: async ({ ${idParamName}, data }) => {
|
|
1652
1970
|
try {
|
|
@@ -1703,10 +2021,22 @@ ${hasSlug ? `
|
|
|
1703
2021
|
};
|
|
1704
2022
|
`;
|
|
1705
2023
|
}
|
|
1706
|
-
function generateSingleActions(single, servicesImportPath) {
|
|
2024
|
+
function generateSingleActions(single, servicesImportPath, useTypedSchemas = false) {
|
|
1707
2025
|
const serviceName = toCamelCase(single.singularName) + "Service";
|
|
1708
2026
|
const actionsName = toCamelCase(single.singularName);
|
|
1709
2027
|
const fileName = toKebabCase(single.singularName);
|
|
2028
|
+
let updateDataSchema = "z.record(z.unknown())";
|
|
2029
|
+
let schemaDefinitions = "";
|
|
2030
|
+
if (useTypedSchemas) {
|
|
2031
|
+
const updateResult = generateZodObjectSchema(single.attributes, { isUpdate: true });
|
|
2032
|
+
schemaDefinitions = `
|
|
2033
|
+
/**
|
|
2034
|
+
* Update schema for ${single.displayName}
|
|
2035
|
+
*/
|
|
2036
|
+
const updateSchema = ${updateResult.schema};
|
|
2037
|
+
`;
|
|
2038
|
+
updateDataSchema = "updateSchema";
|
|
2039
|
+
}
|
|
1710
2040
|
return `/**
|
|
1711
2041
|
* ${single.displayName} Actions (Single Type)
|
|
1712
2042
|
* ${single.description || ""}
|
|
@@ -1717,7 +2047,7 @@ function generateSingleActions(single, servicesImportPath) {
|
|
|
1717
2047
|
import { defineAction, ActionError } from 'astro:actions';
|
|
1718
2048
|
import { z } from 'astro:schema';
|
|
1719
2049
|
import { ${serviceName} } from '${servicesImportPath}/${fileName}.service';
|
|
1720
|
-
|
|
2050
|
+
${schemaDefinitions}
|
|
1721
2051
|
/**
|
|
1722
2052
|
* ${single.displayName} actions
|
|
1723
2053
|
*/
|
|
@@ -1758,7 +2088,7 @@ export const ${actionsName} = {
|
|
|
1758
2088
|
*/
|
|
1759
2089
|
update: defineAction({
|
|
1760
2090
|
input: z.object({
|
|
1761
|
-
data:
|
|
2091
|
+
data: ${updateDataSchema},
|
|
1762
2092
|
}),
|
|
1763
2093
|
handler: async ({ data }) => {
|
|
1764
2094
|
try {
|
|
@@ -1806,23 +2136,20 @@ function generateV5ClientFile(apiPrefix) {
|
|
|
1806
2136
|
return `/**
|
|
1807
2137
|
* Strapi Client (v5)
|
|
1808
2138
|
* Generated by strapi2front
|
|
2139
|
+
*
|
|
2140
|
+
* Using official @strapi/client
|
|
2141
|
+
* @see https://docs.strapi.io/cms/api/client
|
|
1809
2142
|
*/
|
|
1810
2143
|
|
|
1811
|
-
import
|
|
2144
|
+
import { strapi } from '@strapi/client';
|
|
1812
2145
|
|
|
1813
2146
|
// Initialize the Strapi client
|
|
1814
|
-
const
|
|
1815
|
-
const
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
prefix: strapiApiPrefix,
|
|
1821
|
-
axiosOptions: {
|
|
1822
|
-
headers: strapiToken ? {
|
|
1823
|
-
Authorization: \`Bearer \${strapiToken}\`,
|
|
1824
|
-
} : {},
|
|
1825
|
-
},
|
|
2147
|
+
const baseURL = (import.meta.env.STRAPI_URL || process.env.STRAPI_URL || 'http://localhost:1337') + '${normalizedPrefix}';
|
|
2148
|
+
const authToken = import.meta.env.STRAPI_TOKEN || process.env.STRAPI_TOKEN;
|
|
2149
|
+
|
|
2150
|
+
export const strapiClient = strapi({
|
|
2151
|
+
baseURL,
|
|
2152
|
+
auth: authToken,
|
|
1826
2153
|
});
|
|
1827
2154
|
|
|
1828
2155
|
// Pagination type
|
|
@@ -1833,6 +2160,53 @@ export interface StrapiPagination {
|
|
|
1833
2160
|
total: number;
|
|
1834
2161
|
}
|
|
1835
2162
|
|
|
2163
|
+
// Media types
|
|
2164
|
+
export interface StrapiMediaFormat {
|
|
2165
|
+
name: string;
|
|
2166
|
+
hash: string;
|
|
2167
|
+
ext: string;
|
|
2168
|
+
mime: string;
|
|
2169
|
+
width: number;
|
|
2170
|
+
height: number;
|
|
2171
|
+
size: number;
|
|
2172
|
+
url: string;
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
export interface StrapiMedia {
|
|
2176
|
+
id: number;
|
|
2177
|
+
documentId: string;
|
|
2178
|
+
name: string;
|
|
2179
|
+
alternativeText: string | null;
|
|
2180
|
+
caption: string | null;
|
|
2181
|
+
width: number;
|
|
2182
|
+
height: number;
|
|
2183
|
+
formats: {
|
|
2184
|
+
thumbnail?: StrapiMediaFormat;
|
|
2185
|
+
small?: StrapiMediaFormat;
|
|
2186
|
+
medium?: StrapiMediaFormat;
|
|
2187
|
+
large?: StrapiMediaFormat;
|
|
2188
|
+
} | null;
|
|
2189
|
+
hash: string;
|
|
2190
|
+
ext: string;
|
|
2191
|
+
mime: string;
|
|
2192
|
+
size: number;
|
|
2193
|
+
url: string;
|
|
2194
|
+
previewUrl: string | null;
|
|
2195
|
+
provider: string;
|
|
2196
|
+
createdAt: string;
|
|
2197
|
+
updatedAt: string;
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
/**
|
|
2201
|
+
* File metadata for uploads
|
|
2202
|
+
* @see https://docs.strapi.io/cms/api/client#upload
|
|
2203
|
+
*/
|
|
2204
|
+
export interface StrapiFileInfo {
|
|
2205
|
+
name?: string;
|
|
2206
|
+
alternativeText?: string;
|
|
2207
|
+
caption?: string;
|
|
2208
|
+
}
|
|
2209
|
+
|
|
1836
2210
|
// Default pagination for fallback
|
|
1837
2211
|
const defaultPagination: StrapiPagination = {
|
|
1838
2212
|
page: 1,
|
|
@@ -1841,24 +2215,12 @@ const defaultPagination: StrapiPagination = {
|
|
|
1841
2215
|
total: 0,
|
|
1842
2216
|
};
|
|
1843
2217
|
|
|
1844
|
-
// Response types
|
|
1845
|
-
interface StrapiListResponse<T> {
|
|
1846
|
-
data: T[];
|
|
1847
|
-
meta: {
|
|
1848
|
-
pagination?: StrapiPagination;
|
|
1849
|
-
};
|
|
1850
|
-
}
|
|
1851
|
-
|
|
1852
|
-
interface StrapiSingleResponse<T> {
|
|
1853
|
-
data: T;
|
|
1854
|
-
meta?: Record<string, unknown>;
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
2218
|
// Helper to get typed collection
|
|
1858
2219
|
export function collection<T>(pluralName: string) {
|
|
2220
|
+
const col = strapiClient.collection(pluralName);
|
|
1859
2221
|
return {
|
|
1860
2222
|
async find(params?: Record<string, unknown>): Promise<{ data: T[]; meta: { pagination: StrapiPagination } }> {
|
|
1861
|
-
const response = await
|
|
2223
|
+
const response = await col.find(params) as any;
|
|
1862
2224
|
return {
|
|
1863
2225
|
data: Array.isArray(response.data) ? response.data : [],
|
|
1864
2226
|
meta: {
|
|
@@ -1867,39 +2229,68 @@ export function collection<T>(pluralName: string) {
|
|
|
1867
2229
|
};
|
|
1868
2230
|
},
|
|
1869
2231
|
async findOne(documentId: string, params?: Record<string, unknown>): Promise<{ data: T }> {
|
|
1870
|
-
const response = await
|
|
2232
|
+
const response = await col.findOne(documentId, params) as any;
|
|
1871
2233
|
return { data: response.data };
|
|
1872
2234
|
},
|
|
1873
|
-
async create(data:
|
|
1874
|
-
const response = await
|
|
2235
|
+
async create(data: Partial<T>): Promise<{ data: T }> {
|
|
2236
|
+
const response = await col.create(data as any) as any;
|
|
1875
2237
|
return { data: response.data };
|
|
1876
2238
|
},
|
|
1877
|
-
async update(documentId: string, data:
|
|
1878
|
-
const response = await
|
|
2239
|
+
async update(documentId: string, data: Partial<T>): Promise<{ data: T }> {
|
|
2240
|
+
const response = await col.update(documentId, data as any) as any;
|
|
1879
2241
|
return { data: response.data };
|
|
1880
2242
|
},
|
|
1881
2243
|
async delete(documentId: string): Promise<void> {
|
|
1882
|
-
await
|
|
2244
|
+
await col.delete(documentId);
|
|
1883
2245
|
},
|
|
1884
2246
|
};
|
|
1885
2247
|
}
|
|
1886
2248
|
|
|
1887
2249
|
// Helper to get typed single type
|
|
1888
2250
|
export function single<T>(singularName: string) {
|
|
2251
|
+
const singleType = strapiClient.single(singularName);
|
|
1889
2252
|
return {
|
|
1890
2253
|
async find(params?: Record<string, unknown>): Promise<{ data: T }> {
|
|
1891
|
-
const response = await
|
|
2254
|
+
const response = await singleType.find(params) as any;
|
|
1892
2255
|
return { data: response.data };
|
|
1893
2256
|
},
|
|
1894
|
-
async update(data:
|
|
1895
|
-
const response = await
|
|
2257
|
+
async update(data: Partial<T>): Promise<{ data: T }> {
|
|
2258
|
+
const response = await singleType.update(data as any) as any;
|
|
1896
2259
|
return { data: response.data };
|
|
1897
2260
|
},
|
|
1898
2261
|
async delete(): Promise<void> {
|
|
1899
|
-
await
|
|
2262
|
+
await singleType.delete();
|
|
1900
2263
|
},
|
|
1901
2264
|
};
|
|
1902
2265
|
}
|
|
2266
|
+
|
|
2267
|
+
/**
|
|
2268
|
+
* File management helpers
|
|
2269
|
+
* Wraps @strapi/client file methods with proper typing
|
|
2270
|
+
* @see https://docs.strapi.io/cms/api/client#working-with-files
|
|
2271
|
+
*/
|
|
2272
|
+
export const files = {
|
|
2273
|
+
async upload(file: File | Blob, options?: { fileInfo?: StrapiFileInfo }): Promise<StrapiMedia> {
|
|
2274
|
+
const response = await strapiClient.files.upload(file, options) as any;
|
|
2275
|
+
return response;
|
|
2276
|
+
},
|
|
2277
|
+
async find(params?: Record<string, unknown>): Promise<StrapiMedia[]> {
|
|
2278
|
+
const response = await strapiClient.files.find(params) as any;
|
|
2279
|
+
return Array.isArray(response) ? response : [];
|
|
2280
|
+
},
|
|
2281
|
+
async findOne(fileId: number): Promise<StrapiMedia> {
|
|
2282
|
+
const response = await strapiClient.files.findOne(fileId) as any;
|
|
2283
|
+
return response;
|
|
2284
|
+
},
|
|
2285
|
+
async update(fileId: number, fileInfo: StrapiFileInfo): Promise<StrapiMedia> {
|
|
2286
|
+
const response = await strapiClient.files.update(fileId, fileInfo) as any;
|
|
2287
|
+
return response;
|
|
2288
|
+
},
|
|
2289
|
+
async delete(fileId: number): Promise<StrapiMedia> {
|
|
2290
|
+
const response = await strapiClient.files.delete(fileId) as any;
|
|
2291
|
+
return response;
|
|
2292
|
+
},
|
|
2293
|
+
};
|
|
1903
2294
|
`;
|
|
1904
2295
|
}
|
|
1905
2296
|
function generateV4ClientFile(apiPrefix) {
|
|
@@ -1907,23 +2298,20 @@ function generateV4ClientFile(apiPrefix) {
|
|
|
1907
2298
|
return `/**
|
|
1908
2299
|
* Strapi Client (v4)
|
|
1909
2300
|
* Generated by strapi2front
|
|
2301
|
+
*
|
|
2302
|
+
* Note: @strapi/client officially supports Strapi v5+
|
|
2303
|
+
* This v4 client uses a compatibility layer for the nested attributes structure
|
|
1910
2304
|
*/
|
|
1911
2305
|
|
|
1912
|
-
import
|
|
2306
|
+
import { strapi as createStrapi } from '@strapi/client';
|
|
1913
2307
|
|
|
1914
2308
|
// Initialize the Strapi client
|
|
1915
|
-
const
|
|
1916
|
-
const
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
prefix: strapiApiPrefix,
|
|
1922
|
-
axiosOptions: {
|
|
1923
|
-
headers: strapiToken ? {
|
|
1924
|
-
Authorization: \`Bearer \${strapiToken}\`,
|
|
1925
|
-
} : {},
|
|
1926
|
-
},
|
|
2309
|
+
const baseURL = (import.meta.env.STRAPI_URL || process.env.STRAPI_URL || 'http://localhost:1337') + '${normalizedPrefix}';
|
|
2310
|
+
const authToken = import.meta.env.STRAPI_TOKEN || process.env.STRAPI_TOKEN;
|
|
2311
|
+
|
|
2312
|
+
export const strapiClient = createStrapi({
|
|
2313
|
+
baseURL,
|
|
2314
|
+
auth: authToken,
|
|
1927
2315
|
});
|
|
1928
2316
|
|
|
1929
2317
|
// Pagination type
|
|
@@ -1934,6 +2322,52 @@ export interface StrapiPagination {
|
|
|
1934
2322
|
total: number;
|
|
1935
2323
|
}
|
|
1936
2324
|
|
|
2325
|
+
// Media types
|
|
2326
|
+
export interface StrapiMediaFormat {
|
|
2327
|
+
name: string;
|
|
2328
|
+
hash: string;
|
|
2329
|
+
ext: string;
|
|
2330
|
+
mime: string;
|
|
2331
|
+
width: number;
|
|
2332
|
+
height: number;
|
|
2333
|
+
size: number;
|
|
2334
|
+
url: string;
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
export interface StrapiMedia {
|
|
2338
|
+
id: number;
|
|
2339
|
+
name: string;
|
|
2340
|
+
alternativeText: string | null;
|
|
2341
|
+
caption: string | null;
|
|
2342
|
+
width: number;
|
|
2343
|
+
height: number;
|
|
2344
|
+
formats: {
|
|
2345
|
+
thumbnail?: StrapiMediaFormat;
|
|
2346
|
+
small?: StrapiMediaFormat;
|
|
2347
|
+
medium?: StrapiMediaFormat;
|
|
2348
|
+
large?: StrapiMediaFormat;
|
|
2349
|
+
} | null;
|
|
2350
|
+
hash: string;
|
|
2351
|
+
ext: string;
|
|
2352
|
+
mime: string;
|
|
2353
|
+
size: number;
|
|
2354
|
+
url: string;
|
|
2355
|
+
previewUrl: string | null;
|
|
2356
|
+
provider: string;
|
|
2357
|
+
createdAt: string;
|
|
2358
|
+
updatedAt: string;
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
/**
|
|
2362
|
+
* File metadata for uploads
|
|
2363
|
+
* @see https://docs.strapi.io/cms/api/client#upload
|
|
2364
|
+
*/
|
|
2365
|
+
export interface StrapiFileInfo {
|
|
2366
|
+
name?: string;
|
|
2367
|
+
alternativeText?: string;
|
|
2368
|
+
caption?: string;
|
|
2369
|
+
}
|
|
2370
|
+
|
|
1937
2371
|
// Default pagination for fallback
|
|
1938
2372
|
const defaultPagination: StrapiPagination = {
|
|
1939
2373
|
page: 1,
|
|
@@ -1948,18 +2382,6 @@ interface StrapiV4RawItem<T> {
|
|
|
1948
2382
|
attributes: Omit<T, 'id'>;
|
|
1949
2383
|
}
|
|
1950
2384
|
|
|
1951
|
-
interface StrapiV4RawListResponse<T> {
|
|
1952
|
-
data: StrapiV4RawItem<T>[];
|
|
1953
|
-
meta: {
|
|
1954
|
-
pagination?: StrapiPagination;
|
|
1955
|
-
};
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
interface StrapiV4RawSingleResponse<T> {
|
|
1959
|
-
data: StrapiV4RawItem<T>;
|
|
1960
|
-
meta?: Record<string, unknown>;
|
|
1961
|
-
}
|
|
1962
|
-
|
|
1963
2385
|
/**
|
|
1964
2386
|
* Flatten a Strapi v4 response item (merges id with attributes)
|
|
1965
2387
|
*/
|
|
@@ -2003,55 +2425,83 @@ function flattenRelations<T>(data: T): T {
|
|
|
2003
2425
|
return data;
|
|
2004
2426
|
}
|
|
2005
2427
|
|
|
2006
|
-
// Helper to get typed collection
|
|
2428
|
+
// Helper to get typed collection (v4 compatibility wrapper)
|
|
2007
2429
|
export function collection<T>(pluralName: string) {
|
|
2430
|
+
const col = strapiClient.collection(pluralName);
|
|
2008
2431
|
return {
|
|
2009
2432
|
async find(params?: Record<string, unknown>): Promise<{ data: T[]; meta: { pagination: StrapiPagination } }> {
|
|
2010
|
-
const response = await
|
|
2433
|
+
const response = await col.find(params) as any;
|
|
2011
2434
|
const flattenedData = Array.isArray(response.data)
|
|
2012
|
-
? response.data.map(item => flattenRelations(flattenItem<T>(item)))
|
|
2435
|
+
? response.data.map((item: StrapiV4RawItem<T>) => flattenRelations(flattenItem<T>(item)))
|
|
2013
2436
|
: [];
|
|
2014
2437
|
return {
|
|
2015
2438
|
data: flattenedData,
|
|
2016
|
-
meta: {
|
|
2017
|
-
pagination: response.meta?.pagination || defaultPagination,
|
|
2018
|
-
},
|
|
2439
|
+
meta: { pagination: response.meta?.pagination || defaultPagination },
|
|
2019
2440
|
};
|
|
2020
2441
|
},
|
|
2021
2442
|
async findOne(id: number | string, params?: Record<string, unknown>): Promise<{ data: T }> {
|
|
2022
|
-
const response = await
|
|
2443
|
+
const response = await col.findOne(String(id), params) as any;
|
|
2023
2444
|
return { data: flattenRelations(flattenItem<T>(response.data)) };
|
|
2024
2445
|
},
|
|
2025
|
-
async create(data:
|
|
2026
|
-
const response = await
|
|
2446
|
+
async create(data: Partial<T>): Promise<{ data: T }> {
|
|
2447
|
+
const response = await col.create(data as any) as any;
|
|
2027
2448
|
return { data: flattenRelations(flattenItem<T>(response.data)) };
|
|
2028
2449
|
},
|
|
2029
|
-
async update(id: number | string, data:
|
|
2030
|
-
const response = await
|
|
2450
|
+
async update(id: number | string, data: Partial<T>): Promise<{ data: T }> {
|
|
2451
|
+
const response = await col.update(String(id), data as any) as any;
|
|
2031
2452
|
return { data: flattenRelations(flattenItem<T>(response.data)) };
|
|
2032
2453
|
},
|
|
2033
2454
|
async delete(id: number | string): Promise<void> {
|
|
2034
|
-
await
|
|
2455
|
+
await col.delete(String(id));
|
|
2035
2456
|
},
|
|
2036
2457
|
};
|
|
2037
2458
|
}
|
|
2038
2459
|
|
|
2039
|
-
// Helper to get typed single type
|
|
2460
|
+
// Helper to get typed single type (v4 compatibility wrapper)
|
|
2040
2461
|
export function single<T>(singularName: string) {
|
|
2462
|
+
const singleType = strapiClient.single(singularName);
|
|
2041
2463
|
return {
|
|
2042
2464
|
async find(params?: Record<string, unknown>): Promise<{ data: T }> {
|
|
2043
|
-
const response = await
|
|
2465
|
+
const response = await singleType.find(params) as any;
|
|
2044
2466
|
return { data: flattenRelations(flattenItem<T>(response.data)) };
|
|
2045
2467
|
},
|
|
2046
|
-
async update(data:
|
|
2047
|
-
const response = await
|
|
2468
|
+
async update(data: Partial<T>): Promise<{ data: T }> {
|
|
2469
|
+
const response = await singleType.update(data as any) as any;
|
|
2048
2470
|
return { data: flattenRelations(flattenItem<T>(response.data)) };
|
|
2049
2471
|
},
|
|
2050
2472
|
async delete(): Promise<void> {
|
|
2051
|
-
await
|
|
2473
|
+
await singleType.delete();
|
|
2052
2474
|
},
|
|
2053
2475
|
};
|
|
2054
2476
|
}
|
|
2477
|
+
|
|
2478
|
+
/**
|
|
2479
|
+
* File management helpers
|
|
2480
|
+
* Wraps @strapi/client file methods with proper typing
|
|
2481
|
+
* @see https://docs.strapi.io/cms/api/client#working-with-files
|
|
2482
|
+
*/
|
|
2483
|
+
export const files = {
|
|
2484
|
+
async upload(file: File | Blob, options?: { fileInfo?: StrapiFileInfo }): Promise<StrapiMedia> {
|
|
2485
|
+
const response = await strapiClient.files.upload(file, options) as any;
|
|
2486
|
+
return response;
|
|
2487
|
+
},
|
|
2488
|
+
async find(params?: Record<string, unknown>): Promise<StrapiMedia[]> {
|
|
2489
|
+
const response = await strapiClient.files.find(params) as any;
|
|
2490
|
+
return Array.isArray(response) ? response : [];
|
|
2491
|
+
},
|
|
2492
|
+
async findOne(fileId: number): Promise<StrapiMedia> {
|
|
2493
|
+
const response = await strapiClient.files.findOne(fileId) as any;
|
|
2494
|
+
return response;
|
|
2495
|
+
},
|
|
2496
|
+
async update(fileId: number, fileInfo: StrapiFileInfo): Promise<StrapiMedia> {
|
|
2497
|
+
const response = await strapiClient.files.update(fileId, fileInfo) as any;
|
|
2498
|
+
return response;
|
|
2499
|
+
},
|
|
2500
|
+
async delete(fileId: number): Promise<StrapiMedia> {
|
|
2501
|
+
const response = await strapiClient.files.delete(fileId) as any;
|
|
2502
|
+
return response;
|
|
2503
|
+
},
|
|
2504
|
+
};
|
|
2055
2505
|
`;
|
|
2056
2506
|
}
|
|
2057
2507
|
async function generateLocales(locales, options) {
|
|
@@ -2132,6 +2582,7 @@ async function generateByFeature(schema, locales, options) {
|
|
|
2132
2582
|
const {
|
|
2133
2583
|
outputDir,
|
|
2134
2584
|
features,
|
|
2585
|
+
schemaOptions = {},
|
|
2135
2586
|
blocksRendererInstalled = false,
|
|
2136
2587
|
strapiVersion = "v5",
|
|
2137
2588
|
apiPrefix = "/api",
|
|
@@ -2139,8 +2590,10 @@ async function generateByFeature(schema, locales, options) {
|
|
|
2139
2590
|
moduleType = "commonjs"
|
|
2140
2591
|
} = options;
|
|
2141
2592
|
const generatedFiles = [];
|
|
2593
|
+
const advancedRelations = schemaOptions.advancedRelations ?? false;
|
|
2142
2594
|
const useESM = outputFormat === "jsdoc" && moduleType === "esm";
|
|
2143
2595
|
const ext = outputFormat === "jsdoc" ? "js" : "ts";
|
|
2596
|
+
const generateSchemas = features.schemas ?? outputFormat === "typescript";
|
|
2144
2597
|
await ensureDir(path9.join(outputDir, "collections"));
|
|
2145
2598
|
await ensureDir(path9.join(outputDir, "singles"));
|
|
2146
2599
|
await ensureDir(path9.join(outputDir, "components"));
|
|
@@ -2158,6 +2611,18 @@ async function generateByFeature(schema, locales, options) {
|
|
|
2158
2611
|
const localesContent = outputFormat === "jsdoc" ? generateLocalesFileJSDoc(locales, useESM) : generateLocalesFile2(locales);
|
|
2159
2612
|
await writeFile(localesPath, await formatCode(localesContent));
|
|
2160
2613
|
generatedFiles.push(localesPath);
|
|
2614
|
+
if (features.upload) {
|
|
2615
|
+
const uploadClientPath = path9.join(sharedDir, `upload-client.${ext}`);
|
|
2616
|
+
const uploadClientContent = outputFormat === "jsdoc" ? generateUploadClientJSDoc(useESM) : generateUploadClientTS();
|
|
2617
|
+
await writeFile(uploadClientPath, await formatCode(uploadClientContent));
|
|
2618
|
+
generatedFiles.push(uploadClientPath);
|
|
2619
|
+
if (features.actions && outputFormat === "typescript") {
|
|
2620
|
+
const uploadActionPath = path9.join(sharedDir, "upload-action.ts");
|
|
2621
|
+
const uploadActionContent = generateUploadActionTS();
|
|
2622
|
+
await writeFile(uploadActionPath, await formatCode(uploadActionContent));
|
|
2623
|
+
generatedFiles.push(uploadActionPath);
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2161
2626
|
for (const collection of schema.collections) {
|
|
2162
2627
|
const featureDir = path9.join(outputDir, "collections", toKebabCase(collection.singularName));
|
|
2163
2628
|
await ensureDir(featureDir);
|
|
@@ -2167,6 +2632,12 @@ async function generateByFeature(schema, locales, options) {
|
|
|
2167
2632
|
await writeFile(typesPath, await formatCode(content));
|
|
2168
2633
|
generatedFiles.push(typesPath);
|
|
2169
2634
|
}
|
|
2635
|
+
if (generateSchemas) {
|
|
2636
|
+
const schemasPath = path9.join(featureDir, `schemas.${ext}`);
|
|
2637
|
+
const schemasContent = generateCollectionSchemas(collection, schema, strapiVersion, advancedRelations);
|
|
2638
|
+
await writeFile(schemasPath, await formatCode(schemasContent));
|
|
2639
|
+
generatedFiles.push(schemasPath);
|
|
2640
|
+
}
|
|
2170
2641
|
if (features.services) {
|
|
2171
2642
|
const servicePath = path9.join(featureDir, `service.${ext}`);
|
|
2172
2643
|
const content = outputFormat === "jsdoc" ? generateCollectionServiceJSDoc(collection, strapiVersion, useESM) : generateCollectionService3(collection, strapiVersion);
|
|
@@ -2175,7 +2646,12 @@ async function generateByFeature(schema, locales, options) {
|
|
|
2175
2646
|
}
|
|
2176
2647
|
if (features.actions) {
|
|
2177
2648
|
const actionsPath = path9.join(featureDir, `actions.${ext}`);
|
|
2178
|
-
|
|
2649
|
+
const actionsContent = generateCollectionActions2(collection, {
|
|
2650
|
+
strapiVersion,
|
|
2651
|
+
useExternalSchemas: generateSchemas,
|
|
2652
|
+
draftAndPublish: collection.draftAndPublish
|
|
2653
|
+
});
|
|
2654
|
+
await writeFile(actionsPath, await formatCode(actionsContent));
|
|
2179
2655
|
generatedFiles.push(actionsPath);
|
|
2180
2656
|
}
|
|
2181
2657
|
}
|
|
@@ -2188,6 +2664,12 @@ async function generateByFeature(schema, locales, options) {
|
|
|
2188
2664
|
await writeFile(typesPath, await formatCode(content));
|
|
2189
2665
|
generatedFiles.push(typesPath);
|
|
2190
2666
|
}
|
|
2667
|
+
if (generateSchemas) {
|
|
2668
|
+
const schemasPath = path9.join(featureDir, `schemas.${ext}`);
|
|
2669
|
+
const schemasContent = generateSingleSchemas(single, schema, strapiVersion, advancedRelations);
|
|
2670
|
+
await writeFile(schemasPath, await formatCode(schemasContent));
|
|
2671
|
+
generatedFiles.push(schemasPath);
|
|
2672
|
+
}
|
|
2191
2673
|
if (features.services) {
|
|
2192
2674
|
const servicePath = path9.join(featureDir, `service.${ext}`);
|
|
2193
2675
|
const content = outputFormat === "jsdoc" ? generateSingleServiceJSDoc(single, strapiVersion, useESM) : generateSingleService3(single, strapiVersion);
|
|
@@ -2197,7 +2679,7 @@ async function generateByFeature(schema, locales, options) {
|
|
|
2197
2679
|
}
|
|
2198
2680
|
for (const component of schema.components) {
|
|
2199
2681
|
const componentPath = path9.join(outputDir, "components", `${toKebabCase(component.name)}.${ext}`);
|
|
2200
|
-
const content = outputFormat === "jsdoc" ? generateComponentTypesJSDoc(component, schema, useESM) : generateComponentTypes(component, schema);
|
|
2682
|
+
const content = outputFormat === "jsdoc" ? generateComponentTypesJSDoc(component, schema, useESM) : generateComponentTypes(component, schema, generateSchemas, strapiVersion, advancedRelations);
|
|
2201
2683
|
await writeFile(componentPath, await formatCode(content));
|
|
2202
2684
|
generatedFiles.push(componentPath);
|
|
2203
2685
|
}
|
|
@@ -2334,6 +2816,16 @@ export interface StrapiMediaFormat {
|
|
|
2334
2816
|
url: string;
|
|
2335
2817
|
}
|
|
2336
2818
|
|
|
2819
|
+
/**
|
|
2820
|
+
* File metadata for uploads
|
|
2821
|
+
* @see https://docs.strapi.io/cms/api/client#upload
|
|
2822
|
+
*/
|
|
2823
|
+
export interface StrapiFileInfo {
|
|
2824
|
+
name?: string;
|
|
2825
|
+
alternativeText?: string;
|
|
2826
|
+
caption?: string;
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2337
2829
|
export interface StrapiPagination {
|
|
2338
2830
|
page: number;
|
|
2339
2831
|
pageSize: number;
|
|
@@ -2367,25 +2859,55 @@ function generateClient2(strapiVersion, apiPrefix = "/api") {
|
|
|
2367
2859
|
return `/**
|
|
2368
2860
|
* Strapi Client (v4)
|
|
2369
2861
|
* Generated by strapi2front
|
|
2862
|
+
*
|
|
2863
|
+
* Note: @strapi/client officially supports Strapi v5+
|
|
2864
|
+
* This v4 client uses a compatibility layer for the nested attributes structure
|
|
2370
2865
|
*/
|
|
2371
2866
|
|
|
2372
|
-
import
|
|
2373
|
-
import type { StrapiPagination } from './utils';
|
|
2867
|
+
import { strapi as createStrapi } from '@strapi/client';
|
|
2868
|
+
import type { StrapiPagination, StrapiMedia, StrapiFileInfo } from './utils';
|
|
2374
2869
|
|
|
2375
|
-
//
|
|
2376
|
-
const
|
|
2377
|
-
const
|
|
2378
|
-
const
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2870
|
+
// Default configuration from environment
|
|
2871
|
+
const defaultBaseURL = import.meta.env.STRAPI_URL || process.env.STRAPI_URL || 'http://localhost:1337';
|
|
2872
|
+
const defaultAuthToken = import.meta.env.STRAPI_TOKEN || process.env.STRAPI_TOKEN;
|
|
2873
|
+
const apiPrefix = '${normalizedPrefix}';
|
|
2874
|
+
|
|
2875
|
+
/**
|
|
2876
|
+
* Client options for authentication and connection
|
|
2877
|
+
* @beta This API is in beta and may change
|
|
2878
|
+
*/
|
|
2879
|
+
export interface ClientOptions {
|
|
2880
|
+
/** JWT token or API token for authentication */
|
|
2881
|
+
authToken?: string;
|
|
2882
|
+
/** Base URL of the Strapi instance (without /api prefix) */
|
|
2883
|
+
baseURL?: string;
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
/**
|
|
2887
|
+
* Create a configured Strapi client instance
|
|
2888
|
+
*
|
|
2889
|
+
* @param options - Optional configuration (authToken, baseURL)
|
|
2890
|
+
* @returns Configured Strapi client
|
|
2891
|
+
*
|
|
2892
|
+
* @example
|
|
2893
|
+
* // Default client (uses STRAPI_URL and STRAPI_TOKEN from env)
|
|
2894
|
+
* const client = createStrapiClient();
|
|
2895
|
+
*
|
|
2896
|
+
* @example
|
|
2897
|
+
* // Client with user JWT token
|
|
2898
|
+
* const userClient = createStrapiClient({ authToken: session.jwt });
|
|
2899
|
+
*
|
|
2900
|
+
* @beta This API is in beta and may change
|
|
2901
|
+
*/
|
|
2902
|
+
export function createStrapiClient(options?: ClientOptions) {
|
|
2903
|
+
const baseURL = (options?.baseURL || defaultBaseURL) + apiPrefix;
|
|
2904
|
+
const auth = options?.authToken || defaultAuthToken;
|
|
2905
|
+
|
|
2906
|
+
return createStrapi({ baseURL, auth });
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
// Default client instance (uses environment variables)
|
|
2910
|
+
export const strapiClient = createStrapiClient();
|
|
2389
2911
|
|
|
2390
2912
|
// Default pagination for fallback
|
|
2391
2913
|
const defaultPagination: StrapiPagination = {
|
|
@@ -2401,18 +2923,6 @@ interface StrapiV4RawItem<T> {
|
|
|
2401
2923
|
attributes: Omit<T, 'id'>;
|
|
2402
2924
|
}
|
|
2403
2925
|
|
|
2404
|
-
interface StrapiV4RawListResponse<T> {
|
|
2405
|
-
data: StrapiV4RawItem<T>[];
|
|
2406
|
-
meta: {
|
|
2407
|
-
pagination?: StrapiPagination;
|
|
2408
|
-
};
|
|
2409
|
-
}
|
|
2410
|
-
|
|
2411
|
-
interface StrapiV4RawSingleResponse<T> {
|
|
2412
|
-
data: StrapiV4RawItem<T>;
|
|
2413
|
-
meta?: Record<string, unknown>;
|
|
2414
|
-
}
|
|
2415
|
-
|
|
2416
2926
|
/**
|
|
2417
2927
|
* Flatten a Strapi v4 response item (merges id with attributes)
|
|
2418
2928
|
*/
|
|
@@ -2431,18 +2941,15 @@ function flattenRelations<T>(data: T): T {
|
|
|
2431
2941
|
if (typeof data === 'object') {
|
|
2432
2942
|
const result: Record<string, unknown> = {};
|
|
2433
2943
|
for (const [key, value] of Object.entries(data as Record<string, unknown>)) {
|
|
2434
|
-
// Check if this is a Strapi v4 relation response { data: { id, attributes } }
|
|
2435
2944
|
if (value && typeof value === 'object' && 'data' in value) {
|
|
2436
2945
|
const relationData = (value as { data: unknown }).data;
|
|
2437
2946
|
if (relationData === null) {
|
|
2438
2947
|
result[key] = null;
|
|
2439
2948
|
} else if (Array.isArray(relationData)) {
|
|
2440
|
-
// To-many relation
|
|
2441
2949
|
result[key] = relationData.map((item: StrapiV4RawItem<unknown>) =>
|
|
2442
2950
|
flattenRelations(flattenItem(item))
|
|
2443
2951
|
);
|
|
2444
2952
|
} else if (typeof relationData === 'object' && 'id' in relationData && 'attributes' in relationData) {
|
|
2445
|
-
// To-one relation
|
|
2446
2953
|
result[key] = flattenRelations(flattenItem(relationData as StrapiV4RawItem<unknown>));
|
|
2447
2954
|
} else {
|
|
2448
2955
|
result[key] = flattenRelations(value);
|
|
@@ -2456,79 +2963,187 @@ function flattenRelations<T>(data: T): T {
|
|
|
2456
2963
|
return data;
|
|
2457
2964
|
}
|
|
2458
2965
|
|
|
2459
|
-
|
|
2460
|
-
|
|
2966
|
+
/**
|
|
2967
|
+
* Get or create a Strapi client based on options
|
|
2968
|
+
* @internal
|
|
2969
|
+
*/
|
|
2970
|
+
function getClient(options?: ClientOptions) {
|
|
2971
|
+
if (options?.authToken || options?.baseURL) {
|
|
2972
|
+
return createStrapiClient(options);
|
|
2973
|
+
}
|
|
2974
|
+
return strapiClient;
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
/**
|
|
2978
|
+
* Helper to get typed collection (v4 compatibility wrapper)
|
|
2979
|
+
*
|
|
2980
|
+
* @param pluralName - The plural name of the collection (e.g., 'articles')
|
|
2981
|
+
* @param clientOptions - Optional client configuration
|
|
2982
|
+
*/
|
|
2983
|
+
export function collection<T>(pluralName: string, clientOptions?: ClientOptions) {
|
|
2984
|
+
const client = getClient(clientOptions);
|
|
2985
|
+
const col = client.collection(pluralName);
|
|
2461
2986
|
return {
|
|
2462
2987
|
async find(params?: Record<string, unknown>): Promise<{ data: T[]; meta: { pagination: StrapiPagination } }> {
|
|
2463
|
-
const response = await
|
|
2988
|
+
const response = await col.find(params) as any;
|
|
2464
2989
|
const flattenedData = Array.isArray(response.data)
|
|
2465
|
-
? response.data.map(item => flattenRelations(flattenItem<T>(item)))
|
|
2990
|
+
? response.data.map((item: StrapiV4RawItem<T>) => flattenRelations(flattenItem<T>(item)))
|
|
2466
2991
|
: [];
|
|
2467
2992
|
return {
|
|
2468
2993
|
data: flattenedData,
|
|
2469
|
-
meta: {
|
|
2470
|
-
pagination: response.meta?.pagination || defaultPagination,
|
|
2471
|
-
},
|
|
2994
|
+
meta: { pagination: response.meta?.pagination || defaultPagination },
|
|
2472
2995
|
};
|
|
2473
2996
|
},
|
|
2474
2997
|
async findOne(id: number | string, params?: Record<string, unknown>): Promise<{ data: T }> {
|
|
2475
|
-
const response = await
|
|
2998
|
+
const response = await col.findOne(String(id), params) as any;
|
|
2476
2999
|
return { data: flattenRelations(flattenItem<T>(response.data)) };
|
|
2477
3000
|
},
|
|
2478
|
-
async create(data:
|
|
2479
|
-
const response = await
|
|
3001
|
+
async create(data: Partial<T>): Promise<{ data: T }> {
|
|
3002
|
+
const response = await col.create(data as any) as any;
|
|
2480
3003
|
return { data: flattenRelations(flattenItem<T>(response.data)) };
|
|
2481
3004
|
},
|
|
2482
|
-
async update(id: number | string, data:
|
|
2483
|
-
const response = await
|
|
3005
|
+
async update(id: number | string, data: Partial<T>): Promise<{ data: T }> {
|
|
3006
|
+
const response = await col.update(String(id), data as any) as any;
|
|
2484
3007
|
return { data: flattenRelations(flattenItem<T>(response.data)) };
|
|
2485
3008
|
},
|
|
2486
3009
|
async delete(id: number | string): Promise<void> {
|
|
2487
|
-
await
|
|
3010
|
+
await col.delete(String(id));
|
|
2488
3011
|
},
|
|
2489
3012
|
};
|
|
2490
3013
|
}
|
|
2491
3014
|
|
|
2492
|
-
|
|
2493
|
-
|
|
3015
|
+
/**
|
|
3016
|
+
* Helper to get typed single type (v4 compatibility wrapper)
|
|
3017
|
+
*
|
|
3018
|
+
* @param singularName - The singular name of the single type (e.g., 'homepage')
|
|
3019
|
+
* @param clientOptions - Optional client configuration
|
|
3020
|
+
*/
|
|
3021
|
+
export function single<T>(singularName: string, clientOptions?: ClientOptions) {
|
|
3022
|
+
const client = getClient(clientOptions);
|
|
3023
|
+
const singleType = client.single(singularName);
|
|
2494
3024
|
return {
|
|
2495
3025
|
async find(params?: Record<string, unknown>): Promise<{ data: T }> {
|
|
2496
|
-
const response = await
|
|
3026
|
+
const response = await singleType.find(params) as any;
|
|
2497
3027
|
return { data: flattenRelations(flattenItem<T>(response.data)) };
|
|
2498
3028
|
},
|
|
2499
|
-
async update(data:
|
|
2500
|
-
const response = await
|
|
3029
|
+
async update(data: Partial<T>): Promise<{ data: T }> {
|
|
3030
|
+
const response = await singleType.update(data as any) as any;
|
|
2501
3031
|
return { data: flattenRelations(flattenItem<T>(response.data)) };
|
|
2502
3032
|
},
|
|
2503
3033
|
async delete(): Promise<void> {
|
|
2504
|
-
await
|
|
3034
|
+
await singleType.delete();
|
|
2505
3035
|
},
|
|
2506
3036
|
};
|
|
2507
3037
|
}
|
|
3038
|
+
|
|
3039
|
+
/**
|
|
3040
|
+
* File management helpers
|
|
3041
|
+
* Wraps @strapi/client file methods with proper typing
|
|
3042
|
+
* @see https://docs.strapi.io/cms/api/client#working-with-files
|
|
3043
|
+
*/
|
|
3044
|
+
export const files = {
|
|
3045
|
+
/**
|
|
3046
|
+
* Upload a file to Strapi
|
|
3047
|
+
* @see https://docs.strapi.io/cms/api/client#upload
|
|
3048
|
+
*/
|
|
3049
|
+
async upload(file: File | Blob, options?: { fileInfo?: StrapiFileInfo }): Promise<StrapiMedia> {
|
|
3050
|
+
const response = await strapiClient.files.upload(file, options) as any;
|
|
3051
|
+
return response;
|
|
3052
|
+
},
|
|
3053
|
+
|
|
3054
|
+
/**
|
|
3055
|
+
* Find files with optional filtering and sorting
|
|
3056
|
+
*/
|
|
3057
|
+
async find(params?: Record<string, unknown>): Promise<StrapiMedia[]> {
|
|
3058
|
+
const response = await strapiClient.files.find(params) as any;
|
|
3059
|
+
return Array.isArray(response) ? response : [];
|
|
3060
|
+
},
|
|
3061
|
+
|
|
3062
|
+
/**
|
|
3063
|
+
* Get a single file by ID
|
|
3064
|
+
*/
|
|
3065
|
+
async findOne(fileId: number): Promise<StrapiMedia> {
|
|
3066
|
+
const response = await strapiClient.files.findOne(fileId) as any;
|
|
3067
|
+
return response;
|
|
3068
|
+
},
|
|
3069
|
+
|
|
3070
|
+
/**
|
|
3071
|
+
* Update file metadata (name, alternativeText, caption)
|
|
3072
|
+
*/
|
|
3073
|
+
async update(fileId: number, fileInfo: StrapiFileInfo): Promise<StrapiMedia> {
|
|
3074
|
+
const response = await strapiClient.files.update(fileId, fileInfo) as any;
|
|
3075
|
+
return response;
|
|
3076
|
+
},
|
|
3077
|
+
|
|
3078
|
+
/**
|
|
3079
|
+
* Delete a file by ID
|
|
3080
|
+
*/
|
|
3081
|
+
async delete(fileId: number): Promise<StrapiMedia> {
|
|
3082
|
+
const response = await strapiClient.files.delete(fileId) as any;
|
|
3083
|
+
return response;
|
|
3084
|
+
},
|
|
3085
|
+
};
|
|
2508
3086
|
`;
|
|
2509
3087
|
}
|
|
2510
3088
|
return `/**
|
|
2511
3089
|
* Strapi Client (v5)
|
|
2512
3090
|
* Generated by strapi2front
|
|
3091
|
+
*
|
|
3092
|
+
* Using official @strapi/client
|
|
3093
|
+
* @see https://docs.strapi.io/cms/api/client
|
|
2513
3094
|
*/
|
|
2514
3095
|
|
|
2515
|
-
import
|
|
2516
|
-
import type { StrapiPagination } from './utils';
|
|
3096
|
+
import { strapi } from '@strapi/client';
|
|
3097
|
+
import type { StrapiPagination, StrapiMedia, StrapiFileInfo } from './utils';
|
|
2517
3098
|
|
|
2518
|
-
//
|
|
2519
|
-
const
|
|
2520
|
-
const
|
|
2521
|
-
const
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
3099
|
+
// Default configuration from environment
|
|
3100
|
+
const defaultBaseURL = import.meta.env.STRAPI_URL || process.env.STRAPI_URL || 'http://localhost:1337';
|
|
3101
|
+
const defaultAuthToken = import.meta.env.STRAPI_TOKEN || process.env.STRAPI_TOKEN;
|
|
3102
|
+
const apiPrefix = '${normalizedPrefix}';
|
|
3103
|
+
|
|
3104
|
+
/**
|
|
3105
|
+
* Client options for authentication and connection
|
|
3106
|
+
* @beta This API is in beta and may change
|
|
3107
|
+
*/
|
|
3108
|
+
export interface ClientOptions {
|
|
3109
|
+
/** JWT token or API token for authentication */
|
|
3110
|
+
authToken?: string;
|
|
3111
|
+
/** Base URL of the Strapi instance (without /api prefix) */
|
|
3112
|
+
baseURL?: string;
|
|
3113
|
+
}
|
|
3114
|
+
|
|
3115
|
+
/**
|
|
3116
|
+
* Create a configured Strapi client instance
|
|
3117
|
+
*
|
|
3118
|
+
* @param options - Optional configuration (authToken, baseURL)
|
|
3119
|
+
* @returns Configured Strapi client
|
|
3120
|
+
*
|
|
3121
|
+
* @example
|
|
3122
|
+
* // Default client (uses STRAPI_URL and STRAPI_TOKEN from env)
|
|
3123
|
+
* const client = createStrapiClient();
|
|
3124
|
+
*
|
|
3125
|
+
* @example
|
|
3126
|
+
* // Client with user JWT token
|
|
3127
|
+
* const userClient = createStrapiClient({ authToken: session.jwt });
|
|
3128
|
+
*
|
|
3129
|
+
* @example
|
|
3130
|
+
* // Client with custom URL (multi-tenant)
|
|
3131
|
+
* const tenantClient = createStrapiClient({
|
|
3132
|
+
* baseURL: 'https://tenant.example.com',
|
|
3133
|
+
* authToken: tenantToken
|
|
3134
|
+
* });
|
|
3135
|
+
*
|
|
3136
|
+
* @beta This API is in beta and may change
|
|
3137
|
+
*/
|
|
3138
|
+
export function createStrapiClient(options?: ClientOptions) {
|
|
3139
|
+
const baseURL = (options?.baseURL || defaultBaseURL) + apiPrefix;
|
|
3140
|
+
const auth = options?.authToken || defaultAuthToken;
|
|
3141
|
+
|
|
3142
|
+
return strapi({ baseURL, auth });
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
// Default client instance (uses environment variables)
|
|
3146
|
+
export const strapiClient = createStrapiClient();
|
|
2532
3147
|
|
|
2533
3148
|
// Default pagination for fallback
|
|
2534
3149
|
const defaultPagination: StrapiPagination = {
|
|
@@ -2538,65 +3153,146 @@ const defaultPagination: StrapiPagination = {
|
|
|
2538
3153
|
total: 0,
|
|
2539
3154
|
};
|
|
2540
3155
|
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
data: T;
|
|
2551
|
-
meta?: Record<string, unknown>;
|
|
3156
|
+
/**
|
|
3157
|
+
* Get or create a Strapi client based on options
|
|
3158
|
+
* @internal
|
|
3159
|
+
*/
|
|
3160
|
+
function getClient(options?: ClientOptions) {
|
|
3161
|
+
if (options?.authToken || options?.baseURL) {
|
|
3162
|
+
return createStrapiClient(options);
|
|
3163
|
+
}
|
|
3164
|
+
return strapiClient;
|
|
2552
3165
|
}
|
|
2553
3166
|
|
|
2554
|
-
|
|
2555
|
-
|
|
3167
|
+
/**
|
|
3168
|
+
* Helper to get typed collection
|
|
3169
|
+
* Wraps @strapi/client collection methods with proper typing
|
|
3170
|
+
*
|
|
3171
|
+
* @param pluralName - The plural name of the collection (e.g., 'articles')
|
|
3172
|
+
* @param clientOptions - Optional client configuration
|
|
3173
|
+
*/
|
|
3174
|
+
export function collection<T>(pluralName: string, clientOptions?: ClientOptions) {
|
|
3175
|
+
const client = getClient(clientOptions);
|
|
3176
|
+
const col = client.collection(pluralName);
|
|
2556
3177
|
return {
|
|
2557
3178
|
async find(params?: Record<string, unknown>): Promise<{ data: T[]; meta: { pagination: StrapiPagination } }> {
|
|
2558
|
-
const response = await
|
|
3179
|
+
const response = await col.find(params) as any;
|
|
2559
3180
|
return {
|
|
2560
3181
|
data: Array.isArray(response.data) ? response.data : [],
|
|
2561
|
-
meta: {
|
|
2562
|
-
pagination: response.meta?.pagination || defaultPagination,
|
|
2563
|
-
},
|
|
3182
|
+
meta: { pagination: response.meta?.pagination || defaultPagination },
|
|
2564
3183
|
};
|
|
2565
3184
|
},
|
|
2566
3185
|
async findOne(documentId: string, params?: Record<string, unknown>): Promise<{ data: T }> {
|
|
2567
|
-
const response = await
|
|
3186
|
+
const response = await col.findOne(documentId, params) as any;
|
|
2568
3187
|
return { data: response.data };
|
|
2569
3188
|
},
|
|
2570
|
-
async create(data:
|
|
2571
|
-
const response = await
|
|
3189
|
+
async create(data: Partial<T>): Promise<{ data: T }> {
|
|
3190
|
+
const response = await col.create(data as any) as any;
|
|
2572
3191
|
return { data: response.data };
|
|
2573
3192
|
},
|
|
2574
|
-
async update(documentId: string, data:
|
|
2575
|
-
const response = await
|
|
3193
|
+
async update(documentId: string, data: Partial<T>): Promise<{ data: T }> {
|
|
3194
|
+
const response = await col.update(documentId, data as any) as any;
|
|
2576
3195
|
return { data: response.data };
|
|
2577
3196
|
},
|
|
2578
3197
|
async delete(documentId: string): Promise<void> {
|
|
2579
|
-
await
|
|
3198
|
+
await col.delete(documentId);
|
|
2580
3199
|
},
|
|
2581
3200
|
};
|
|
2582
3201
|
}
|
|
2583
3202
|
|
|
2584
|
-
|
|
2585
|
-
|
|
3203
|
+
/**
|
|
3204
|
+
* Helper to get typed single type
|
|
3205
|
+
* Wraps @strapi/client single methods with proper typing
|
|
3206
|
+
*
|
|
3207
|
+
* @param singularName - The singular name of the single type (e.g., 'homepage')
|
|
3208
|
+
* @param clientOptions - Optional client configuration
|
|
3209
|
+
*/
|
|
3210
|
+
export function single<T>(singularName: string, clientOptions?: ClientOptions) {
|
|
3211
|
+
const client = getClient(clientOptions);
|
|
3212
|
+
const singleType = client.single(singularName);
|
|
2586
3213
|
return {
|
|
2587
3214
|
async find(params?: Record<string, unknown>): Promise<{ data: T }> {
|
|
2588
|
-
const response = await
|
|
3215
|
+
const response = await singleType.find(params) as any;
|
|
2589
3216
|
return { data: response.data };
|
|
2590
3217
|
},
|
|
2591
|
-
async update(data:
|
|
2592
|
-
const response = await
|
|
3218
|
+
async update(data: Partial<T>): Promise<{ data: T }> {
|
|
3219
|
+
const response = await singleType.update(data as any) as any;
|
|
2593
3220
|
return { data: response.data };
|
|
2594
3221
|
},
|
|
2595
3222
|
async delete(): Promise<void> {
|
|
2596
|
-
await
|
|
3223
|
+
await singleType.delete();
|
|
2597
3224
|
},
|
|
2598
3225
|
};
|
|
2599
3226
|
}
|
|
3227
|
+
|
|
3228
|
+
/**
|
|
3229
|
+
* File management helpers
|
|
3230
|
+
* Wraps @strapi/client file methods with proper typing
|
|
3231
|
+
* @see https://docs.strapi.io/cms/api/client#working-with-files
|
|
3232
|
+
*/
|
|
3233
|
+
export const files = {
|
|
3234
|
+
/**
|
|
3235
|
+
* Upload a file to Strapi
|
|
3236
|
+
*
|
|
3237
|
+
* @example
|
|
3238
|
+
* // Browser: upload from file input
|
|
3239
|
+
* const file = fileInput.files[0];
|
|
3240
|
+
* const uploaded = await files.upload(file, {
|
|
3241
|
+
* fileInfo: { alternativeText: 'My image', caption: 'A caption' }
|
|
3242
|
+
* });
|
|
3243
|
+
*
|
|
3244
|
+
* @example
|
|
3245
|
+
* // Use the returned ID to link to an entry
|
|
3246
|
+
* await collection('articles').create({
|
|
3247
|
+
* title: 'My article',
|
|
3248
|
+
* cover: uploaded.id,
|
|
3249
|
+
* });
|
|
3250
|
+
*
|
|
3251
|
+
* @see https://docs.strapi.io/cms/api/client#upload
|
|
3252
|
+
*/
|
|
3253
|
+
async upload(file: File | Blob, options?: { fileInfo?: StrapiFileInfo }): Promise<StrapiMedia> {
|
|
3254
|
+
const response = await strapiClient.files.upload(file, options) as any;
|
|
3255
|
+
return response;
|
|
3256
|
+
},
|
|
3257
|
+
|
|
3258
|
+
/**
|
|
3259
|
+
* Find files with optional filtering and sorting
|
|
3260
|
+
*
|
|
3261
|
+
* @example
|
|
3262
|
+
* const images = await files.find({
|
|
3263
|
+
* filters: { mime: { $contains: 'image' } },
|
|
3264
|
+
* sort: ['name:asc'],
|
|
3265
|
+
* });
|
|
3266
|
+
*/
|
|
3267
|
+
async find(params?: Record<string, unknown>): Promise<StrapiMedia[]> {
|
|
3268
|
+
const response = await strapiClient.files.find(params) as any;
|
|
3269
|
+
return Array.isArray(response) ? response : [];
|
|
3270
|
+
},
|
|
3271
|
+
|
|
3272
|
+
/**
|
|
3273
|
+
* Get a single file by ID
|
|
3274
|
+
*/
|
|
3275
|
+
async findOne(fileId: number): Promise<StrapiMedia> {
|
|
3276
|
+
const response = await strapiClient.files.findOne(fileId) as any;
|
|
3277
|
+
return response;
|
|
3278
|
+
},
|
|
3279
|
+
|
|
3280
|
+
/**
|
|
3281
|
+
* Update file metadata (name, alternativeText, caption)
|
|
3282
|
+
*/
|
|
3283
|
+
async update(fileId: number, fileInfo: StrapiFileInfo): Promise<StrapiMedia> {
|
|
3284
|
+
const response = await strapiClient.files.update(fileId, fileInfo) as any;
|
|
3285
|
+
return response;
|
|
3286
|
+
},
|
|
3287
|
+
|
|
3288
|
+
/**
|
|
3289
|
+
* Delete a file by ID
|
|
3290
|
+
*/
|
|
3291
|
+
async delete(fileId: number): Promise<StrapiMedia> {
|
|
3292
|
+
const response = await strapiClient.files.delete(fileId) as any;
|
|
3293
|
+
return response;
|
|
3294
|
+
},
|
|
3295
|
+
};
|
|
2600
3296
|
`;
|
|
2601
3297
|
}
|
|
2602
3298
|
function generateLocalesFile2(locales) {
|
|
@@ -2692,11 +3388,12 @@ ${attributes}
|
|
|
2692
3388
|
}
|
|
2693
3389
|
`;
|
|
2694
3390
|
}
|
|
2695
|
-
function generateComponentTypes(component, schema) {
|
|
3391
|
+
function generateComponentTypes(component, schema, includeSchemas = false, strapiVersion = "v5", advancedRelations = false) {
|
|
2696
3392
|
const typeName = toPascalCase(component.name);
|
|
3393
|
+
const schemaName = toCamelCase(component.name);
|
|
2697
3394
|
const attributes = generateAttributes2(component.attributes);
|
|
2698
3395
|
const imports = generateTypeImports(component.attributes, schema, "component");
|
|
2699
|
-
|
|
3396
|
+
let content = `/**
|
|
2700
3397
|
* ${component.displayName} component
|
|
2701
3398
|
* Category: ${component.category}
|
|
2702
3399
|
* ${component.description || ""}
|
|
@@ -2710,6 +3407,32 @@ export interface ${typeName} {
|
|
|
2710
3407
|
${attributes}
|
|
2711
3408
|
}
|
|
2712
3409
|
`;
|
|
3410
|
+
if (includeSchemas) {
|
|
3411
|
+
const componentSchemaNames = buildComponentSchemaNames(component.attributes, schema);
|
|
3412
|
+
const schemaImports = generateSchemaImports(component.attributes, schema, "component");
|
|
3413
|
+
const schemaOptions = {
|
|
3414
|
+
isUpdate: false,
|
|
3415
|
+
strapiVersion,
|
|
3416
|
+
useAdvancedRelations: advancedRelations,
|
|
3417
|
+
componentSchemaNames
|
|
3418
|
+
};
|
|
3419
|
+
const schemaResult = generateZodObjectSchema(component.attributes, schemaOptions);
|
|
3420
|
+
content += `
|
|
3421
|
+
import { z } from 'zod';
|
|
3422
|
+
${schemaImports}
|
|
3423
|
+
/**
|
|
3424
|
+
* Zod schema for ${component.displayName} component
|
|
3425
|
+
* Use this for validating component data in forms
|
|
3426
|
+
*/
|
|
3427
|
+
export const ${schemaName}Schema = ${schemaResult.schema};
|
|
3428
|
+
|
|
3429
|
+
/**
|
|
3430
|
+
* Inferred type from schema
|
|
3431
|
+
*/
|
|
3432
|
+
export type ${typeName}Input = z.infer<typeof ${schemaName}Schema>;
|
|
3433
|
+
`;
|
|
3434
|
+
}
|
|
3435
|
+
return content;
|
|
2713
3436
|
}
|
|
2714
3437
|
function generateTypeImports(attributes, schema, context) {
|
|
2715
3438
|
const utilsImports = [];
|
|
@@ -2863,7 +3586,7 @@ function generateCollectionService3(collection, strapiVersion) {
|
|
|
2863
3586
|
const omitFields = isV4 ? "'id' | 'createdAt' | 'updatedAt' | 'publishedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt' | 'publishedAt'";
|
|
2864
3587
|
const omitFieldsUpdate = isV4 ? "'id' | 'createdAt' | 'updatedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt'";
|
|
2865
3588
|
const imports = [
|
|
2866
|
-
`import { collection } from '../../shared/client';`,
|
|
3589
|
+
`import { collection, type ClientOptions } from '../../shared/client';`,
|
|
2867
3590
|
`import type { ${typeName}, ${typeName}Filters } from './types';`,
|
|
2868
3591
|
`import type { StrapiPagination } from '../../shared/utils';`
|
|
2869
3592
|
];
|
|
@@ -2914,6 +3637,18 @@ function generateCollectionService3(collection, strapiVersion) {
|
|
|
2914
3637
|
findOneParams += `
|
|
2915
3638
|
status: options.status,`;
|
|
2916
3639
|
}
|
|
3640
|
+
const createOptionsInterface = draftAndPublish ? `
|
|
3641
|
+
export interface CreateOptions {
|
|
3642
|
+
/** Publish immediately or create as draft. Default: 'draft' */
|
|
3643
|
+
status?: 'draft' | 'published';
|
|
3644
|
+
}
|
|
3645
|
+
` : "";
|
|
3646
|
+
const updateOptionsInterface = draftAndPublish ? `
|
|
3647
|
+
export interface UpdateOptions {
|
|
3648
|
+
/** Change publication status */
|
|
3649
|
+
status?: 'draft' | 'published';
|
|
3650
|
+
}
|
|
3651
|
+
` : "";
|
|
2917
3652
|
return `/**
|
|
2918
3653
|
* ${collection.displayName} Service
|
|
2919
3654
|
* ${collection.description || ""}
|
|
@@ -2930,13 +3665,24 @@ ${findManyOptionsFields}
|
|
|
2930
3665
|
export interface FindOneOptions {
|
|
2931
3666
|
${findOneOptionsFields}
|
|
2932
3667
|
}
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
3668
|
+
${createOptionsInterface}${updateOptionsInterface}
|
|
3669
|
+
/**
|
|
3670
|
+
* Get a typed collection helper, optionally with custom client options
|
|
3671
|
+
* @internal
|
|
3672
|
+
*/
|
|
3673
|
+
function getCollection(clientOptions?: ClientOptions) {
|
|
3674
|
+
return collection<${typeName}>('${endpoint}', clientOptions);
|
|
3675
|
+
}
|
|
2936
3676
|
|
|
2937
3677
|
export const ${serviceName} = {
|
|
2938
|
-
|
|
2939
|
-
|
|
3678
|
+
/**
|
|
3679
|
+
* Find multiple ${collection.displayName} entries
|
|
3680
|
+
* @param options - Query options (filters, pagination, sort, populate)
|
|
3681
|
+
* @param clientOptions - Optional client configuration (authToken, baseURL) - beta
|
|
3682
|
+
*/
|
|
3683
|
+
async findMany(options: FindManyOptions = {}, clientOptions?: ClientOptions): Promise<{ data: ${typeName}[]; pagination: StrapiPagination }> {
|
|
3684
|
+
const col = getCollection(clientOptions);
|
|
3685
|
+
const response = await col.find({
|
|
2940
3686
|
${findParams}
|
|
2941
3687
|
});
|
|
2942
3688
|
|
|
@@ -2946,7 +3692,12 @@ ${findParams}
|
|
|
2946
3692
|
};
|
|
2947
3693
|
},
|
|
2948
3694
|
|
|
2949
|
-
|
|
3695
|
+
/**
|
|
3696
|
+
* Find all ${collection.displayName} entries (handles pagination automatically)
|
|
3697
|
+
* @param options - Query options (filters, sort, populate)
|
|
3698
|
+
* @param clientOptions - Optional client configuration (authToken, baseURL) - beta
|
|
3699
|
+
*/
|
|
3700
|
+
async findAll(options: Omit<FindManyOptions, 'pagination'> = {}, clientOptions?: ClientOptions): Promise<${typeName}[]> {
|
|
2950
3701
|
const allItems: ${typeName}[] = [];
|
|
2951
3702
|
let page = 1;
|
|
2952
3703
|
let hasMore = true;
|
|
@@ -2955,7 +3706,7 @@ ${findParams}
|
|
|
2955
3706
|
const { data, pagination } = await this.findMany({
|
|
2956
3707
|
...options,
|
|
2957
3708
|
pagination: { page, pageSize: 100 },
|
|
2958
|
-
});
|
|
3709
|
+
}, clientOptions);
|
|
2959
3710
|
|
|
2960
3711
|
allItems.push(...data);
|
|
2961
3712
|
hasMore = page < pagination.pageCount;
|
|
@@ -2965,9 +3716,16 @@ ${findParams}
|
|
|
2965
3716
|
return allItems;
|
|
2966
3717
|
},
|
|
2967
3718
|
|
|
2968
|
-
|
|
3719
|
+
/**
|
|
3720
|
+
* Find a single ${collection.displayName} by ID
|
|
3721
|
+
* @param ${idName} - The ${isV4 ? "numeric ID" : "document ID"}
|
|
3722
|
+
* @param options - Query options (populate)
|
|
3723
|
+
* @param clientOptions - Optional client configuration (authToken, baseURL) - beta
|
|
3724
|
+
*/
|
|
3725
|
+
async findOne(${idParam}, options: FindOneOptions = {}, clientOptions?: ClientOptions): Promise<${typeName} | null> {
|
|
2969
3726
|
try {
|
|
2970
|
-
const
|
|
3727
|
+
const col = getCollection(clientOptions);
|
|
3728
|
+
const response = await col.findOne(${idName}, {
|
|
2971
3729
|
${findOneParams}
|
|
2972
3730
|
});
|
|
2973
3731
|
|
|
@@ -2980,35 +3738,65 @@ ${findOneParams}
|
|
|
2980
3738
|
}
|
|
2981
3739
|
},
|
|
2982
3740
|
${hasSlug ? `
|
|
2983
|
-
|
|
3741
|
+
/**
|
|
3742
|
+
* Find a single ${collection.displayName} by slug
|
|
3743
|
+
* @param slug - The slug value
|
|
3744
|
+
* @param options - Query options (populate)
|
|
3745
|
+
* @param clientOptions - Optional client configuration (authToken, baseURL) - beta
|
|
3746
|
+
*/
|
|
3747
|
+
async findBySlug(slug: string, options: FindOneOptions = {}, clientOptions?: ClientOptions): Promise<${typeName} | null> {
|
|
2984
3748
|
const { data } = await this.findMany({
|
|
2985
3749
|
filters: { slug: { $eq: slug } } as ${typeName}Filters,
|
|
2986
3750
|
pagination: { pageSize: 1 },
|
|
2987
3751
|
populate: options.populate,${localized ? "\n locale: options.locale," : ""}${draftAndPublish ? "\n status: options.status," : ""}
|
|
2988
|
-
});
|
|
3752
|
+
}, clientOptions);
|
|
2989
3753
|
|
|
2990
3754
|
return data[0] || null;
|
|
2991
3755
|
},
|
|
2992
3756
|
` : ""}
|
|
2993
|
-
|
|
2994
|
-
|
|
3757
|
+
/**
|
|
3758
|
+
* Create a new ${collection.displayName}
|
|
3759
|
+
* @param data - The data to create
|
|
3760
|
+
* @param clientOptions - Optional client configuration (authToken, baseURL) - beta
|
|
3761
|
+
*/
|
|
3762
|
+
async create(data: Partial<Omit<${typeName}, ${omitFields}>>${draftAndPublish ? ", options: CreateOptions = {}" : ""}, clientOptions?: ClientOptions): Promise<${typeName}> {
|
|
3763
|
+
const col = getCollection(clientOptions);
|
|
3764
|
+
const response = await col.create(${draftAndPublish ? "{ ...data, status: options.status }" : "data"});
|
|
2995
3765
|
return response.data;
|
|
2996
3766
|
},
|
|
2997
3767
|
|
|
2998
|
-
|
|
2999
|
-
|
|
3768
|
+
/**
|
|
3769
|
+
* Update an existing ${collection.displayName}
|
|
3770
|
+
* @param ${idName} - The ${isV4 ? "numeric ID" : "document ID"}
|
|
3771
|
+
* @param data - The data to update
|
|
3772
|
+
* @param clientOptions - Optional client configuration (authToken, baseURL) - beta
|
|
3773
|
+
*/
|
|
3774
|
+
async update(${idParam}, data: Partial<Omit<${typeName}, ${omitFieldsUpdate}>>${draftAndPublish ? ", options: UpdateOptions = {}" : ""}, clientOptions?: ClientOptions): Promise<${typeName}> {
|
|
3775
|
+
const col = getCollection(clientOptions);
|
|
3776
|
+
const response = await col.update(${idName}, ${draftAndPublish ? "{ ...data, status: options.status }" : "data"});
|
|
3000
3777
|
return response.data;
|
|
3001
3778
|
},
|
|
3002
3779
|
|
|
3003
|
-
|
|
3004
|
-
|
|
3780
|
+
/**
|
|
3781
|
+
* Delete a ${collection.displayName}
|
|
3782
|
+
* @param ${idName} - The ${isV4 ? "numeric ID" : "document ID"}
|
|
3783
|
+
* @param clientOptions - Optional client configuration (authToken, baseURL) - beta
|
|
3784
|
+
*/
|
|
3785
|
+
async delete(${idParam}, clientOptions?: ClientOptions): Promise<void> {
|
|
3786
|
+
const col = getCollection(clientOptions);
|
|
3787
|
+
await col.delete(${idName});
|
|
3005
3788
|
},
|
|
3006
3789
|
|
|
3007
|
-
|
|
3790
|
+
/**
|
|
3791
|
+
* Count ${collection.displayName} entries
|
|
3792
|
+
* @param filters - Optional filters
|
|
3793
|
+
* @param clientOptions - Optional client configuration (authToken, baseURL) - beta
|
|
3794
|
+
*/
|
|
3795
|
+
async count(filters?: ${typeName}Filters, clientOptions?: ClientOptions): Promise<number> {
|
|
3008
3796
|
const { pagination } = await this.findMany({
|
|
3009
3797
|
filters,
|
|
3010
3798
|
pagination: { pageSize: 1 },
|
|
3011
|
-
});
|
|
3799
|
+
}, clientOptions);
|
|
3012
3800
|
|
|
3013
3801
|
return pagination.total;
|
|
3014
3802
|
},
|
|
@@ -3023,7 +3811,7 @@ function generateSingleService3(single, strapiVersion) {
|
|
|
3023
3811
|
const isV4 = strapiVersion === "v4";
|
|
3024
3812
|
const omitFields = isV4 ? "'id' | 'createdAt' | 'updatedAt'" : "'id' | 'documentId' | 'createdAt' | 'updatedAt'";
|
|
3025
3813
|
const imports = [
|
|
3026
|
-
`import { single } from '../../shared/client';`,
|
|
3814
|
+
`import { single, type ClientOptions } from '../../shared/client';`,
|
|
3027
3815
|
`import type { ${typeName} } from './types';`
|
|
3028
3816
|
];
|
|
3029
3817
|
if (localized) {
|
|
@@ -3060,43 +3848,208 @@ export interface FindOptions {
|
|
|
3060
3848
|
${findOptionsFields}
|
|
3061
3849
|
}
|
|
3062
3850
|
|
|
3063
|
-
|
|
3064
|
-
|
|
3851
|
+
/**
|
|
3852
|
+
* Get a typed single helper, optionally with custom client options
|
|
3853
|
+
* @internal
|
|
3854
|
+
*/
|
|
3855
|
+
function getSingle(clientOptions?: ClientOptions) {
|
|
3856
|
+
return single<${typeName}>('${endpoint}', clientOptions);
|
|
3857
|
+
}
|
|
3065
3858
|
|
|
3066
3859
|
export const ${serviceName} = {
|
|
3067
|
-
|
|
3860
|
+
/**
|
|
3861
|
+
* Find the ${single.displayName} single type
|
|
3862
|
+
* @param options - Query options (populate)
|
|
3863
|
+
* @param clientOptions - Optional client configuration (authToken, baseURL) - beta
|
|
3864
|
+
*/
|
|
3865
|
+
async find(options: FindOptions = {}, clientOptions?: ClientOptions): Promise<${typeName} | null> {
|
|
3068
3866
|
try {
|
|
3069
|
-
const
|
|
3867
|
+
const s = getSingle(clientOptions);
|
|
3868
|
+
const response = await s.find({
|
|
3070
3869
|
${findParams}
|
|
3071
3870
|
});
|
|
3072
3871
|
|
|
3073
|
-
return response.data;
|
|
3074
|
-
} catch (error) {
|
|
3075
|
-
if (error instanceof Error && error.message.includes('404')) {
|
|
3076
|
-
return null;
|
|
3077
|
-
}
|
|
3078
|
-
throw error;
|
|
3079
|
-
}
|
|
3080
|
-
},
|
|
3872
|
+
return response.data;
|
|
3873
|
+
} catch (error) {
|
|
3874
|
+
if (error instanceof Error && error.message.includes('404')) {
|
|
3875
|
+
return null;
|
|
3876
|
+
}
|
|
3877
|
+
throw error;
|
|
3878
|
+
}
|
|
3879
|
+
},
|
|
3880
|
+
|
|
3881
|
+
/**
|
|
3882
|
+
* Update the ${single.displayName} single type
|
|
3883
|
+
* @param data - The data to update
|
|
3884
|
+
* @param clientOptions - Optional client configuration (authToken, baseURL) - beta
|
|
3885
|
+
*/
|
|
3886
|
+
async update(data: Partial<Omit<${typeName}, ${omitFields}>>, clientOptions?: ClientOptions): Promise<${typeName}> {
|
|
3887
|
+
const s = getSingle(clientOptions);
|
|
3888
|
+
const response = await s.update(data);
|
|
3889
|
+
return response.data;
|
|
3890
|
+
},
|
|
3891
|
+
|
|
3892
|
+
/**
|
|
3893
|
+
* Delete the ${single.displayName} single type
|
|
3894
|
+
* @param clientOptions - Optional client configuration (authToken, baseURL) - beta
|
|
3895
|
+
*/
|
|
3896
|
+
async delete(clientOptions?: ClientOptions): Promise<void> {
|
|
3897
|
+
const s = getSingle(clientOptions);
|
|
3898
|
+
await s.delete();
|
|
3899
|
+
},
|
|
3900
|
+
};
|
|
3901
|
+
`;
|
|
3902
|
+
}
|
|
3903
|
+
function buildComponentSchemaNames(attributes, schema) {
|
|
3904
|
+
const map = /* @__PURE__ */ new Map();
|
|
3905
|
+
for (const attr of Object.values(attributes)) {
|
|
3906
|
+
if (attr.type === "component" && "component" in attr && attr.component) {
|
|
3907
|
+
const uid = attr.component;
|
|
3908
|
+
if (!map.has(uid)) {
|
|
3909
|
+
const componentName = uid.split(".").pop() || "";
|
|
3910
|
+
const exists = schema.components.some((c) => c.name === componentName);
|
|
3911
|
+
if (exists && componentName) {
|
|
3912
|
+
map.set(uid, `${toCamelCase(componentName)}Schema`);
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
if (attr.type === "dynamiczone" && "components" in attr && attr.components) {
|
|
3917
|
+
for (const uid of attr.components) {
|
|
3918
|
+
if (!map.has(uid)) {
|
|
3919
|
+
const componentName = uid.split(".").pop() || "";
|
|
3920
|
+
const exists = schema.components.some((c) => c.name === componentName);
|
|
3921
|
+
if (exists && componentName) {
|
|
3922
|
+
map.set(uid, `${toCamelCase(componentName)}Schema`);
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
return map;
|
|
3929
|
+
}
|
|
3930
|
+
function generateSchemaImports(attributes, schema, context) {
|
|
3931
|
+
const imports = /* @__PURE__ */ new Map();
|
|
3932
|
+
const relativePrefix = context === "component" ? "." : "../../components";
|
|
3933
|
+
for (const attr of Object.values(attributes)) {
|
|
3934
|
+
if (attr.type === "component" && "component" in attr && attr.component) {
|
|
3935
|
+
addComponentImport(attr.component, imports, schema, relativePrefix);
|
|
3936
|
+
}
|
|
3937
|
+
if (attr.type === "dynamiczone" && "components" in attr && attr.components) {
|
|
3938
|
+
for (const uid of attr.components) {
|
|
3939
|
+
addComponentImport(uid, imports, schema, relativePrefix);
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
}
|
|
3943
|
+
if (imports.size === 0) return "";
|
|
3944
|
+
const lines = [];
|
|
3945
|
+
for (const [schemaVarName, importPath] of imports) {
|
|
3946
|
+
lines.push(`import { ${schemaVarName} } from '${importPath}';`);
|
|
3947
|
+
}
|
|
3948
|
+
return lines.join("\n") + "\n";
|
|
3949
|
+
}
|
|
3950
|
+
function addComponentImport(uid, imports, schema, relativePrefix) {
|
|
3951
|
+
const componentName = uid.split(".").pop() || "";
|
|
3952
|
+
if (!componentName) return;
|
|
3953
|
+
const exists = schema.components.some((c) => c.name === componentName);
|
|
3954
|
+
if (!exists) return;
|
|
3955
|
+
const schemaVarName = `${toCamelCase(componentName)}Schema`;
|
|
3956
|
+
if (imports.has(schemaVarName)) return;
|
|
3957
|
+
const fileName = toKebabCase(componentName);
|
|
3958
|
+
imports.set(schemaVarName, `${relativePrefix}/${fileName}`);
|
|
3959
|
+
}
|
|
3960
|
+
function generateCollectionSchemas(collection, schema, strapiVersion = "v5", advancedRelations = false) {
|
|
3961
|
+
const name = toCamelCase(collection.singularName);
|
|
3962
|
+
const pascalName = toPascalCase(collection.singularName);
|
|
3963
|
+
const componentSchemaNames = buildComponentSchemaNames(collection.attributes, schema);
|
|
3964
|
+
const schemaImports = generateSchemaImports(collection.attributes, schema, "collection");
|
|
3965
|
+
const schemaOptions = {
|
|
3966
|
+
isUpdate: false,
|
|
3967
|
+
strapiVersion,
|
|
3968
|
+
useAdvancedRelations: advancedRelations,
|
|
3969
|
+
componentSchemaNames
|
|
3970
|
+
};
|
|
3971
|
+
const createResult = generateZodObjectSchema(collection.attributes, schemaOptions);
|
|
3972
|
+
const updateResult = generateZodObjectSchema(collection.attributes, { ...schemaOptions, isUpdate: true });
|
|
3973
|
+
return `/**
|
|
3974
|
+
* ${collection.displayName} Zod Schemas
|
|
3975
|
+
* ${collection.description || ""}
|
|
3976
|
+
* Generated by strapi2front
|
|
3977
|
+
*
|
|
3978
|
+
* These schemas can be used for:
|
|
3979
|
+
* - Form validation (React Hook Form, TanStack Form, Formik, etc.)
|
|
3980
|
+
* - API request/response validation
|
|
3981
|
+
* - Type inference
|
|
3982
|
+
*/
|
|
3983
|
+
|
|
3984
|
+
import { z } from 'zod';
|
|
3985
|
+
${schemaImports}
|
|
3986
|
+
/**
|
|
3987
|
+
* Schema for creating a new ${collection.displayName}
|
|
3988
|
+
*/
|
|
3989
|
+
export const ${name}CreateSchema = ${createResult.schema};
|
|
3990
|
+
|
|
3991
|
+
/**
|
|
3992
|
+
* Schema for updating a ${collection.displayName}
|
|
3993
|
+
* All fields are optional for partial updates
|
|
3994
|
+
*/
|
|
3995
|
+
export const ${name}UpdateSchema = ${updateResult.schema};
|
|
3081
3996
|
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3997
|
+
/**
|
|
3998
|
+
* Inferred types from schemas
|
|
3999
|
+
* Use these for type-safe form handling
|
|
4000
|
+
*/
|
|
4001
|
+
export type ${pascalName}CreateInput = z.infer<typeof ${name}CreateSchema>;
|
|
4002
|
+
export type ${pascalName}UpdateInput = z.infer<typeof ${name}UpdateSchema>;
|
|
4003
|
+
`;
|
|
4004
|
+
}
|
|
4005
|
+
function generateSingleSchemas(single, schema, strapiVersion = "v5", advancedRelations = false) {
|
|
4006
|
+
const name = toCamelCase(single.singularName);
|
|
4007
|
+
const pascalName = toPascalCase(single.singularName);
|
|
4008
|
+
const componentSchemaNames = buildComponentSchemaNames(single.attributes, schema);
|
|
4009
|
+
const schemaImports = generateSchemaImports(single.attributes, schema, "single");
|
|
4010
|
+
const updateResult = generateZodObjectSchema(single.attributes, {
|
|
4011
|
+
isUpdate: true,
|
|
4012
|
+
strapiVersion,
|
|
4013
|
+
useAdvancedRelations: advancedRelations,
|
|
4014
|
+
componentSchemaNames
|
|
4015
|
+
});
|
|
4016
|
+
return `/**
|
|
4017
|
+
* ${single.displayName} Zod Schemas (Single Type)
|
|
4018
|
+
* ${single.description || ""}
|
|
4019
|
+
* Generated by strapi2front
|
|
4020
|
+
*
|
|
4021
|
+
* These schemas can be used for:
|
|
4022
|
+
* - Form validation (React Hook Form, TanStack Form, Formik, etc.)
|
|
4023
|
+
* - API request/response validation
|
|
4024
|
+
* - Type inference
|
|
4025
|
+
*/
|
|
3086
4026
|
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
}
|
|
4027
|
+
import { z } from 'zod';
|
|
4028
|
+
${schemaImports}
|
|
4029
|
+
/**
|
|
4030
|
+
* Schema for updating ${single.displayName}
|
|
4031
|
+
* All fields are optional for partial updates
|
|
4032
|
+
*/
|
|
4033
|
+
export const ${name}UpdateSchema = ${updateResult.schema};
|
|
4034
|
+
|
|
4035
|
+
/**
|
|
4036
|
+
* Inferred type from schema
|
|
4037
|
+
*/
|
|
4038
|
+
export type ${pascalName}UpdateInput = z.infer<typeof ${name}UpdateSchema>;
|
|
3091
4039
|
`;
|
|
3092
4040
|
}
|
|
3093
|
-
function generateCollectionActions2(collection,
|
|
4041
|
+
function generateCollectionActions2(collection, options) {
|
|
4042
|
+
const { strapiVersion, useExternalSchemas, draftAndPublish = false } = options;
|
|
3094
4043
|
const typeName = toPascalCase(collection.singularName);
|
|
3095
4044
|
const serviceName = toCamelCase(collection.singularName) + "Service";
|
|
3096
4045
|
const actionPrefix = toCamelCase(collection.singularName);
|
|
3097
4046
|
const isV4 = strapiVersion === "v4";
|
|
3098
4047
|
const idInputSchema = isV4 ? "z.number().int().positive()" : "z.string()";
|
|
3099
4048
|
const idParamName = isV4 ? "id" : "documentId";
|
|
4049
|
+
const statusSchema = "z.enum(['draft', 'published']).optional()";
|
|
4050
|
+
const schemaImport = useExternalSchemas ? `import { ${actionPrefix}CreateSchema, ${actionPrefix}UpdateSchema } from './schemas';` : "";
|
|
4051
|
+
const createSchema = useExternalSchemas ? `${actionPrefix}CreateSchema` : "z.record(z.unknown())";
|
|
4052
|
+
const updateSchema = useExternalSchemas ? `${actionPrefix}UpdateSchema` : "z.record(z.unknown())";
|
|
3100
4053
|
return `/**
|
|
3101
4054
|
* ${collection.displayName} Astro Actions
|
|
3102
4055
|
* ${collection.description || ""}
|
|
@@ -3108,6 +4061,7 @@ import { defineAction } from 'astro:actions';
|
|
|
3108
4061
|
import { z } from 'astro:schema';
|
|
3109
4062
|
import { ${serviceName} } from './service';
|
|
3110
4063
|
import type { ${typeName} } from './types';
|
|
4064
|
+
${schemaImport}
|
|
3111
4065
|
|
|
3112
4066
|
export const ${actionPrefix}Actions = {
|
|
3113
4067
|
getMany: defineAction({
|
|
@@ -3137,10 +4091,11 @@ export const ${actionPrefix}Actions = {
|
|
|
3137
4091
|
|
|
3138
4092
|
create: defineAction({
|
|
3139
4093
|
input: z.object({
|
|
3140
|
-
data:
|
|
4094
|
+
data: ${createSchema},${draftAndPublish ? `
|
|
4095
|
+
status: ${statusSchema},` : ""}
|
|
3141
4096
|
}),
|
|
3142
4097
|
handler: async (input) => {
|
|
3143
|
-
const data = await ${serviceName}.create(input.data as Partial<${typeName}
|
|
4098
|
+
const data = await ${serviceName}.create(input.data as Partial<${typeName}>${draftAndPublish ? ", { status: input.status }" : ""});
|
|
3144
4099
|
return { data };
|
|
3145
4100
|
},
|
|
3146
4101
|
}),
|
|
@@ -3148,10 +4103,11 @@ export const ${actionPrefix}Actions = {
|
|
|
3148
4103
|
update: defineAction({
|
|
3149
4104
|
input: z.object({
|
|
3150
4105
|
${idParamName}: ${idInputSchema},
|
|
3151
|
-
data:
|
|
4106
|
+
data: ${updateSchema},${draftAndPublish ? `
|
|
4107
|
+
status: ${statusSchema},` : ""}
|
|
3152
4108
|
}),
|
|
3153
4109
|
handler: async (input) => {
|
|
3154
|
-
const data = await ${serviceName}.update(input.${idParamName}, input.data as Partial<${typeName}
|
|
4110
|
+
const data = await ${serviceName}.update(input.${idParamName}, input.data as Partial<${typeName}>${draftAndPublish ? ", { status: input.status }" : ""});
|
|
3155
4111
|
return { data };
|
|
3156
4112
|
},
|
|
3157
4113
|
}),
|
|
@@ -3314,6 +4270,15 @@ ${mediaType}
|
|
|
3314
4270
|
* @property {string} url
|
|
3315
4271
|
*/
|
|
3316
4272
|
|
|
4273
|
+
/**
|
|
4274
|
+
* File metadata for uploads
|
|
4275
|
+
* @see https://docs.strapi.io/cms/api/client#upload
|
|
4276
|
+
* @typedef {Object} StrapiFileInfo
|
|
4277
|
+
* @property {string} [name]
|
|
4278
|
+
* @property {string} [alternativeText]
|
|
4279
|
+
* @property {string} [caption]
|
|
4280
|
+
*/
|
|
4281
|
+
|
|
3317
4282
|
/**
|
|
3318
4283
|
* @typedef {Object} StrapiPagination
|
|
3319
4284
|
* @property {number} page
|
|
@@ -3348,29 +4313,26 @@ ${useESM ? "export {};" : "module.exports = {};"}
|
|
|
3348
4313
|
function generateClientJSDoc(strapiVersion, apiPrefix = "/api", useESM = false) {
|
|
3349
4314
|
const isV4 = strapiVersion === "v4";
|
|
3350
4315
|
const normalizedPrefix = apiPrefix.startsWith("/") ? apiPrefix : "/" + apiPrefix;
|
|
3351
|
-
const importStatement = useESM ? `import
|
|
4316
|
+
const importStatement = useESM ? `import { strapi as createStrapi } from '@strapi/client';` : `const { strapi: createStrapi } = require('@strapi/client');`;
|
|
3352
4317
|
if (isV4) {
|
|
3353
4318
|
return `// @ts-check
|
|
3354
4319
|
/**
|
|
3355
4320
|
* Strapi Client (v4)
|
|
3356
4321
|
* Generated by strapi2front
|
|
4322
|
+
*
|
|
4323
|
+
* Note: @strapi/client officially supports Strapi v5+
|
|
4324
|
+
* This v4 client uses a compatibility layer for the nested attributes structure
|
|
3357
4325
|
*/
|
|
3358
4326
|
|
|
3359
4327
|
${importStatement}
|
|
3360
4328
|
|
|
3361
4329
|
// Initialize the Strapi client
|
|
3362
|
-
const
|
|
3363
|
-
const
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
prefix: strapiApiPrefix,
|
|
3369
|
-
axiosOptions: {
|
|
3370
|
-
headers: strapiToken ? {
|
|
3371
|
-
Authorization: \`Bearer \${strapiToken}\`,
|
|
3372
|
-
} : {},
|
|
3373
|
-
},
|
|
4330
|
+
const baseURL = (process.env.STRAPI_URL || 'http://localhost:1337') + '${normalizedPrefix}';
|
|
4331
|
+
const authToken = process.env.STRAPI_TOKEN;
|
|
4332
|
+
|
|
4333
|
+
const strapiClient = createStrapi({
|
|
4334
|
+
baseURL,
|
|
4335
|
+
auth: authToken,
|
|
3374
4336
|
});
|
|
3375
4337
|
|
|
3376
4338
|
/** @type {import('./utils').StrapiPagination} */
|
|
@@ -3437,18 +4399,19 @@ function flattenRelations(data) {
|
|
|
3437
4399
|
}
|
|
3438
4400
|
|
|
3439
4401
|
/**
|
|
3440
|
-
* Helper to get typed collection
|
|
4402
|
+
* Helper to get typed collection (v4 compatibility wrapper)
|
|
3441
4403
|
* @template T
|
|
3442
4404
|
* @param {string} pluralName
|
|
3443
4405
|
*/
|
|
3444
4406
|
function collection(pluralName) {
|
|
4407
|
+
const col = strapiClient.collection(pluralName);
|
|
3445
4408
|
return {
|
|
3446
4409
|
/**
|
|
3447
4410
|
* @param {Record<string, unknown>} [params]
|
|
3448
4411
|
* @returns {Promise<{ data: T[], meta: { pagination: import('./utils').StrapiPagination } }>}
|
|
3449
4412
|
*/
|
|
3450
4413
|
async find(params) {
|
|
3451
|
-
const response = await
|
|
4414
|
+
const response = await col.find(params);
|
|
3452
4415
|
/** @type {any} */
|
|
3453
4416
|
const rawData = response.data;
|
|
3454
4417
|
/** @type {T[]} */
|
|
@@ -3474,7 +4437,7 @@ function collection(pluralName) {
|
|
|
3474
4437
|
* @returns {Promise<{ data: T }>}
|
|
3475
4438
|
*/
|
|
3476
4439
|
async findOne(id, params) {
|
|
3477
|
-
const response = await
|
|
4440
|
+
const response = await col.findOne(String(id), params);
|
|
3478
4441
|
/** @type {any} */
|
|
3479
4442
|
const rawData = response.data;
|
|
3480
4443
|
/** @type {T} */
|
|
@@ -3482,11 +4445,11 @@ function collection(pluralName) {
|
|
|
3482
4445
|
return { data };
|
|
3483
4446
|
},
|
|
3484
4447
|
/**
|
|
3485
|
-
* @param {
|
|
4448
|
+
* @param {Partial<T>} data
|
|
3486
4449
|
* @returns {Promise<{ data: T }>}
|
|
3487
4450
|
*/
|
|
3488
4451
|
async create(data) {
|
|
3489
|
-
const response = await
|
|
4452
|
+
const response = await col.create(data);
|
|
3490
4453
|
/** @type {any} */
|
|
3491
4454
|
const rawData = response.data;
|
|
3492
4455
|
/** @type {T} */
|
|
@@ -3495,11 +4458,11 @@ function collection(pluralName) {
|
|
|
3495
4458
|
},
|
|
3496
4459
|
/**
|
|
3497
4460
|
* @param {number|string} id
|
|
3498
|
-
* @param {
|
|
4461
|
+
* @param {Partial<T>} data
|
|
3499
4462
|
* @returns {Promise<{ data: T }>}
|
|
3500
4463
|
*/
|
|
3501
4464
|
async update(id, data) {
|
|
3502
|
-
const response = await
|
|
4465
|
+
const response = await col.update(String(id), data);
|
|
3503
4466
|
/** @type {any} */
|
|
3504
4467
|
const rawData = response.data;
|
|
3505
4468
|
/** @type {T} */
|
|
@@ -3511,24 +4474,25 @@ function collection(pluralName) {
|
|
|
3511
4474
|
* @returns {Promise<void>}
|
|
3512
4475
|
*/
|
|
3513
4476
|
async delete(id) {
|
|
3514
|
-
await
|
|
4477
|
+
await col.delete(String(id));
|
|
3515
4478
|
},
|
|
3516
4479
|
};
|
|
3517
4480
|
}
|
|
3518
4481
|
|
|
3519
4482
|
/**
|
|
3520
|
-
* Helper to get typed single type
|
|
4483
|
+
* Helper to get typed single type (v4 compatibility wrapper)
|
|
3521
4484
|
* @template T
|
|
3522
4485
|
* @param {string} singularName
|
|
3523
4486
|
*/
|
|
3524
4487
|
function single(singularName) {
|
|
4488
|
+
const singleType = strapiClient.single(singularName);
|
|
3525
4489
|
return {
|
|
3526
4490
|
/**
|
|
3527
4491
|
* @param {Record<string, unknown>} [params]
|
|
3528
4492
|
* @returns {Promise<{ data: T }>}
|
|
3529
4493
|
*/
|
|
3530
4494
|
async find(params) {
|
|
3531
|
-
const response = await
|
|
4495
|
+
const response = await singleType.find(params);
|
|
3532
4496
|
/** @type {any} */
|
|
3533
4497
|
const rawData = response.data;
|
|
3534
4498
|
/** @type {T} */
|
|
@@ -3536,11 +4500,11 @@ function single(singularName) {
|
|
|
3536
4500
|
return { data };
|
|
3537
4501
|
},
|
|
3538
4502
|
/**
|
|
3539
|
-
* @param {
|
|
4503
|
+
* @param {Partial<T>} data
|
|
3540
4504
|
* @returns {Promise<{ data: T }>}
|
|
3541
4505
|
*/
|
|
3542
4506
|
async update(data) {
|
|
3543
|
-
const response = await
|
|
4507
|
+
const response = await singleType.update(data);
|
|
3544
4508
|
/** @type {any} */
|
|
3545
4509
|
const rawData = response.data;
|
|
3546
4510
|
/** @type {T} */
|
|
@@ -3551,35 +4515,97 @@ function single(singularName) {
|
|
|
3551
4515
|
* @returns {Promise<void>}
|
|
3552
4516
|
*/
|
|
3553
4517
|
async delete() {
|
|
3554
|
-
await
|
|
4518
|
+
await singleType.delete();
|
|
3555
4519
|
},
|
|
3556
4520
|
};
|
|
3557
4521
|
}
|
|
3558
4522
|
|
|
3559
|
-
|
|
4523
|
+
/**
|
|
4524
|
+
* File management helpers
|
|
4525
|
+
* Wraps @strapi/client file methods with proper typing
|
|
4526
|
+
* @see https://docs.strapi.io/cms/api/client#working-with-files
|
|
4527
|
+
*/
|
|
4528
|
+
const files = {
|
|
4529
|
+
/**
|
|
4530
|
+
* Upload a file to Strapi
|
|
4531
|
+
* @param {File|Blob} file
|
|
4532
|
+
* @param {{ fileInfo?: import('./utils').StrapiFileInfo }} [options]
|
|
4533
|
+
* @returns {Promise<import('./utils').StrapiMedia>}
|
|
4534
|
+
* @see https://docs.strapi.io/cms/api/client#upload
|
|
4535
|
+
*/
|
|
4536
|
+
async upload(file, options) {
|
|
4537
|
+
/** @type {any} */
|
|
4538
|
+
const response = await strapiClient.files.upload(file, options);
|
|
4539
|
+
return response;
|
|
4540
|
+
},
|
|
4541
|
+
|
|
4542
|
+
/**
|
|
4543
|
+
* Find files with optional filtering and sorting
|
|
4544
|
+
* @param {Record<string, unknown>} [params]
|
|
4545
|
+
* @returns {Promise<import('./utils').StrapiMedia[]>}
|
|
4546
|
+
*/
|
|
4547
|
+
async find(params) {
|
|
4548
|
+
/** @type {any} */
|
|
4549
|
+
const response = await strapiClient.files.find(params);
|
|
4550
|
+
return Array.isArray(response) ? response : [];
|
|
4551
|
+
},
|
|
4552
|
+
|
|
4553
|
+
/**
|
|
4554
|
+
* Get a single file by ID
|
|
4555
|
+
* @param {number} fileId
|
|
4556
|
+
* @returns {Promise<import('./utils').StrapiMedia>}
|
|
4557
|
+
*/
|
|
4558
|
+
async findOne(fileId) {
|
|
4559
|
+
/** @type {any} */
|
|
4560
|
+
const response = await strapiClient.files.findOne(fileId);
|
|
4561
|
+
return response;
|
|
4562
|
+
},
|
|
4563
|
+
|
|
4564
|
+
/**
|
|
4565
|
+
* Update file metadata (name, alternativeText, caption)
|
|
4566
|
+
* @param {number} fileId
|
|
4567
|
+
* @param {import('./utils').StrapiFileInfo} fileInfo
|
|
4568
|
+
* @returns {Promise<import('./utils').StrapiMedia>}
|
|
4569
|
+
*/
|
|
4570
|
+
async update(fileId, fileInfo) {
|
|
4571
|
+
/** @type {any} */
|
|
4572
|
+
const response = await strapiClient.files.update(fileId, fileInfo);
|
|
4573
|
+
return response;
|
|
4574
|
+
},
|
|
4575
|
+
|
|
4576
|
+
/**
|
|
4577
|
+
* Delete a file by ID
|
|
4578
|
+
* @param {number} fileId
|
|
4579
|
+
* @returns {Promise<import('./utils').StrapiMedia>}
|
|
4580
|
+
*/
|
|
4581
|
+
async delete(fileId) {
|
|
4582
|
+
/** @type {any} */
|
|
4583
|
+
const response = await strapiClient.files.delete(fileId);
|
|
4584
|
+
return response;
|
|
4585
|
+
},
|
|
4586
|
+
};
|
|
4587
|
+
|
|
4588
|
+
${useESM ? "export { strapiClient, collection, single, files };" : "module.exports = { strapiClient, collection, single, files };"}
|
|
3560
4589
|
`;
|
|
3561
4590
|
}
|
|
3562
4591
|
return `// @ts-check
|
|
3563
4592
|
/**
|
|
3564
4593
|
* Strapi Client (v5)
|
|
3565
4594
|
* Generated by strapi2front
|
|
4595
|
+
*
|
|
4596
|
+
* Using official @strapi/client
|
|
4597
|
+
* @see https://docs.strapi.io/cms/api/client
|
|
3566
4598
|
*/
|
|
3567
4599
|
|
|
3568
4600
|
${importStatement}
|
|
3569
4601
|
|
|
3570
4602
|
// Initialize the Strapi client
|
|
3571
|
-
const
|
|
3572
|
-
const
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
prefix: strapiApiPrefix,
|
|
3578
|
-
axiosOptions: {
|
|
3579
|
-
headers: strapiToken ? {
|
|
3580
|
-
Authorization: \`Bearer \${strapiToken}\`,
|
|
3581
|
-
} : {},
|
|
3582
|
-
},
|
|
4603
|
+
const baseURL = (process.env.STRAPI_URL || 'http://localhost:1337') + '${normalizedPrefix}';
|
|
4604
|
+
const authToken = process.env.STRAPI_TOKEN;
|
|
4605
|
+
|
|
4606
|
+
const strapiClient = createStrapi({
|
|
4607
|
+
baseURL,
|
|
4608
|
+
auth: authToken,
|
|
3583
4609
|
});
|
|
3584
4610
|
|
|
3585
4611
|
/** @type {import('./utils').StrapiPagination} */
|
|
@@ -3596,13 +4622,14 @@ const defaultPagination = {
|
|
|
3596
4622
|
* @param {string} pluralName
|
|
3597
4623
|
*/
|
|
3598
4624
|
function collection(pluralName) {
|
|
4625
|
+
const col = strapiClient.collection(pluralName);
|
|
3599
4626
|
return {
|
|
3600
4627
|
/**
|
|
3601
4628
|
* @param {Record<string, unknown>} [params]
|
|
3602
4629
|
* @returns {Promise<{ data: T[], meta: { pagination: import('./utils').StrapiPagination } }>}
|
|
3603
4630
|
*/
|
|
3604
4631
|
async find(params) {
|
|
3605
|
-
const response = await
|
|
4632
|
+
const response = await col.find(params);
|
|
3606
4633
|
/** @type {any} */
|
|
3607
4634
|
const rawMeta = response.meta;
|
|
3608
4635
|
/** @type {any} */
|
|
@@ -3626,7 +4653,7 @@ function collection(pluralName) {
|
|
|
3626
4653
|
* @returns {Promise<{ data: T }>}
|
|
3627
4654
|
*/
|
|
3628
4655
|
async findOne(documentId, params) {
|
|
3629
|
-
const response = await
|
|
4656
|
+
const response = await col.findOne(documentId, params);
|
|
3630
4657
|
/** @type {any} */
|
|
3631
4658
|
const rawData = response.data;
|
|
3632
4659
|
/** @type {T} */
|
|
@@ -3634,11 +4661,11 @@ function collection(pluralName) {
|
|
|
3634
4661
|
return { data };
|
|
3635
4662
|
},
|
|
3636
4663
|
/**
|
|
3637
|
-
* @param {
|
|
4664
|
+
* @param {Partial<T>} data
|
|
3638
4665
|
* @returns {Promise<{ data: T }>}
|
|
3639
4666
|
*/
|
|
3640
4667
|
async create(data) {
|
|
3641
|
-
const response = await
|
|
4668
|
+
const response = await col.create(data);
|
|
3642
4669
|
/** @type {any} */
|
|
3643
4670
|
const rawData = response.data;
|
|
3644
4671
|
/** @type {T} */
|
|
@@ -3647,11 +4674,11 @@ function collection(pluralName) {
|
|
|
3647
4674
|
},
|
|
3648
4675
|
/**
|
|
3649
4676
|
* @param {string} documentId
|
|
3650
|
-
* @param {
|
|
4677
|
+
* @param {Partial<T>} data
|
|
3651
4678
|
* @returns {Promise<{ data: T }>}
|
|
3652
4679
|
*/
|
|
3653
4680
|
async update(documentId, data) {
|
|
3654
|
-
const response = await
|
|
4681
|
+
const response = await col.update(documentId, data);
|
|
3655
4682
|
/** @type {any} */
|
|
3656
4683
|
const rawData = response.data;
|
|
3657
4684
|
/** @type {T} */
|
|
@@ -3663,7 +4690,7 @@ function collection(pluralName) {
|
|
|
3663
4690
|
* @returns {Promise<void>}
|
|
3664
4691
|
*/
|
|
3665
4692
|
async delete(documentId) {
|
|
3666
|
-
await
|
|
4693
|
+
await col.delete(documentId);
|
|
3667
4694
|
},
|
|
3668
4695
|
};
|
|
3669
4696
|
}
|
|
@@ -3674,13 +4701,14 @@ function collection(pluralName) {
|
|
|
3674
4701
|
* @param {string} singularName
|
|
3675
4702
|
*/
|
|
3676
4703
|
function single(singularName) {
|
|
4704
|
+
const singleType = strapiClient.single(singularName);
|
|
3677
4705
|
return {
|
|
3678
4706
|
/**
|
|
3679
4707
|
* @param {Record<string, unknown>} [params]
|
|
3680
4708
|
* @returns {Promise<{ data: T }>}
|
|
3681
4709
|
*/
|
|
3682
4710
|
async find(params) {
|
|
3683
|
-
const response = await
|
|
4711
|
+
const response = await singleType.find(params);
|
|
3684
4712
|
/** @type {any} */
|
|
3685
4713
|
const rawData = response.data;
|
|
3686
4714
|
/** @type {T} */
|
|
@@ -3688,11 +4716,11 @@ function single(singularName) {
|
|
|
3688
4716
|
return { data };
|
|
3689
4717
|
},
|
|
3690
4718
|
/**
|
|
3691
|
-
* @param {
|
|
4719
|
+
* @param {Partial<T>} data
|
|
3692
4720
|
* @returns {Promise<{ data: T }>}
|
|
3693
4721
|
*/
|
|
3694
4722
|
async update(data) {
|
|
3695
|
-
const response = await
|
|
4723
|
+
const response = await singleType.update(data);
|
|
3696
4724
|
/** @type {any} */
|
|
3697
4725
|
const rawData = response.data;
|
|
3698
4726
|
/** @type {T} */
|
|
@@ -3703,12 +4731,77 @@ function single(singularName) {
|
|
|
3703
4731
|
* @returns {Promise<void>}
|
|
3704
4732
|
*/
|
|
3705
4733
|
async delete() {
|
|
3706
|
-
await
|
|
4734
|
+
await singleType.delete();
|
|
3707
4735
|
},
|
|
3708
4736
|
};
|
|
3709
4737
|
}
|
|
3710
4738
|
|
|
3711
|
-
|
|
4739
|
+
/**
|
|
4740
|
+
* File management helpers
|
|
4741
|
+
* Wraps @strapi/client file methods with proper typing
|
|
4742
|
+
* @see https://docs.strapi.io/cms/api/client#working-with-files
|
|
4743
|
+
*/
|
|
4744
|
+
const files = {
|
|
4745
|
+
/**
|
|
4746
|
+
* Upload a file to Strapi
|
|
4747
|
+
* @param {File|Blob} file
|
|
4748
|
+
* @param {{ fileInfo?: import('./utils').StrapiFileInfo }} [options]
|
|
4749
|
+
* @returns {Promise<import('./utils').StrapiMedia>}
|
|
4750
|
+
* @see https://docs.strapi.io/cms/api/client#upload
|
|
4751
|
+
*/
|
|
4752
|
+
async upload(file, options) {
|
|
4753
|
+
/** @type {any} */
|
|
4754
|
+
const response = await strapiClient.files.upload(file, options);
|
|
4755
|
+
return response;
|
|
4756
|
+
},
|
|
4757
|
+
|
|
4758
|
+
/**
|
|
4759
|
+
* Find files with optional filtering and sorting
|
|
4760
|
+
* @param {Record<string, unknown>} [params]
|
|
4761
|
+
* @returns {Promise<import('./utils').StrapiMedia[]>}
|
|
4762
|
+
*/
|
|
4763
|
+
async find(params) {
|
|
4764
|
+
/** @type {any} */
|
|
4765
|
+
const response = await strapiClient.files.find(params);
|
|
4766
|
+
return Array.isArray(response) ? response : [];
|
|
4767
|
+
},
|
|
4768
|
+
|
|
4769
|
+
/**
|
|
4770
|
+
* Get a single file by ID
|
|
4771
|
+
* @param {number} fileId
|
|
4772
|
+
* @returns {Promise<import('./utils').StrapiMedia>}
|
|
4773
|
+
*/
|
|
4774
|
+
async findOne(fileId) {
|
|
4775
|
+
/** @type {any} */
|
|
4776
|
+
const response = await strapiClient.files.findOne(fileId);
|
|
4777
|
+
return response;
|
|
4778
|
+
},
|
|
4779
|
+
|
|
4780
|
+
/**
|
|
4781
|
+
* Update file metadata (name, alternativeText, caption)
|
|
4782
|
+
* @param {number} fileId
|
|
4783
|
+
* @param {import('./utils').StrapiFileInfo} fileInfo
|
|
4784
|
+
* @returns {Promise<import('./utils').StrapiMedia>}
|
|
4785
|
+
*/
|
|
4786
|
+
async update(fileId, fileInfo) {
|
|
4787
|
+
/** @type {any} */
|
|
4788
|
+
const response = await strapiClient.files.update(fileId, fileInfo);
|
|
4789
|
+
return response;
|
|
4790
|
+
},
|
|
4791
|
+
|
|
4792
|
+
/**
|
|
4793
|
+
* Delete a file by ID
|
|
4794
|
+
* @param {number} fileId
|
|
4795
|
+
* @returns {Promise<import('./utils').StrapiMedia>}
|
|
4796
|
+
*/
|
|
4797
|
+
async delete(fileId) {
|
|
4798
|
+
/** @type {any} */
|
|
4799
|
+
const response = await strapiClient.files.delete(fileId);
|
|
4800
|
+
return response;
|
|
4801
|
+
},
|
|
4802
|
+
};
|
|
4803
|
+
|
|
4804
|
+
${useESM ? "export { strapiClient, collection, single, files };" : "module.exports = { strapiClient, collection, single, files };"}
|
|
3712
4805
|
`;
|
|
3713
4806
|
}
|
|
3714
4807
|
function generateLocalesFileJSDoc(locales, useESM = false) {
|
|
@@ -4086,7 +5179,7 @@ ${hasSlug ? `
|
|
|
4086
5179
|
* @returns {Promise<import('./types').${typeName}>}
|
|
4087
5180
|
*/
|
|
4088
5181
|
async create(data) {
|
|
4089
|
-
const response = await ${toCamelCase(collection.singularName)}Collection.create(
|
|
5182
|
+
const response = await ${toCamelCase(collection.singularName)}Collection.create(data);
|
|
4090
5183
|
return response.data;
|
|
4091
5184
|
},
|
|
4092
5185
|
|
|
@@ -4096,7 +5189,7 @@ ${hasSlug ? `
|
|
|
4096
5189
|
* @returns {Promise<import('./types').${typeName}>}
|
|
4097
5190
|
*/
|
|
4098
5191
|
async update(${idParam}, data) {
|
|
4099
|
-
const response = await ${toCamelCase(collection.singularName)}Collection.update(${idParam},
|
|
5192
|
+
const response = await ${toCamelCase(collection.singularName)}Collection.update(${idParam}, data);
|
|
4100
5193
|
return response.data;
|
|
4101
5194
|
},
|
|
4102
5195
|
|
|
@@ -4174,7 +5267,7 @@ const ${serviceName} = {
|
|
|
4174
5267
|
* @returns {Promise<import('./types').${typeName}>}
|
|
4175
5268
|
*/
|
|
4176
5269
|
async update(data) {
|
|
4177
|
-
const response = await ${toCamelCase(single.singularName)}Single.update(
|
|
5270
|
+
const response = await ${toCamelCase(single.singularName)}Single.update(data);
|
|
4178
5271
|
return response.data;
|
|
4179
5272
|
},
|
|
4180
5273
|
|
|
@@ -4189,6 +5282,259 @@ const ${serviceName} = {
|
|
|
4189
5282
|
${useESM ? `export { ${serviceName} };` : `module.exports = { ${serviceName} };`}
|
|
4190
5283
|
`;
|
|
4191
5284
|
}
|
|
5285
|
+
function generateUploadClientTS() {
|
|
5286
|
+
return `/**
|
|
5287
|
+
* Public Upload Client
|
|
5288
|
+
* Generated by strapi2front
|
|
5289
|
+
*
|
|
5290
|
+
* Uploads files directly from the browser to Strapi using a restricted public token.
|
|
5291
|
+
* This token should ONLY have upload permissions (no delete, no update).
|
|
5292
|
+
*
|
|
5293
|
+
* Required environment variables:
|
|
5294
|
+
* PUBLIC_STRAPI_URL - Your Strapi CMS base URL
|
|
5295
|
+
* PUBLIC_STRAPI_UPLOAD_TOKEN - Restricted API token (upload-only)
|
|
5296
|
+
*
|
|
5297
|
+
* Create the token in: Strapi Admin > Settings > API Tokens
|
|
5298
|
+
* Set permissions: Upload > upload (only)
|
|
5299
|
+
*/
|
|
5300
|
+
|
|
5301
|
+
import type { StrapiMedia, StrapiFileInfo } from './utils';
|
|
5302
|
+
|
|
5303
|
+
const STRAPI_URL = import.meta.env.PUBLIC_STRAPI_URL || '';
|
|
5304
|
+
const UPLOAD_TOKEN = import.meta.env.PUBLIC_STRAPI_UPLOAD_TOKEN || '';
|
|
5305
|
+
|
|
5306
|
+
/**
|
|
5307
|
+
* Upload a single file to Strapi from the browser
|
|
5308
|
+
*/
|
|
5309
|
+
export async function uploadFile(
|
|
5310
|
+
file: File,
|
|
5311
|
+
fileInfo?: StrapiFileInfo
|
|
5312
|
+
): Promise<StrapiMedia> {
|
|
5313
|
+
const formData = new FormData();
|
|
5314
|
+
formData.append('files', file);
|
|
5315
|
+
|
|
5316
|
+
if (fileInfo) {
|
|
5317
|
+
formData.append('fileInfo', JSON.stringify(fileInfo));
|
|
5318
|
+
}
|
|
5319
|
+
|
|
5320
|
+
const response = await fetch(\`\${STRAPI_URL}/api/upload\`, {
|
|
5321
|
+
method: 'POST',
|
|
5322
|
+
headers: {
|
|
5323
|
+
Authorization: \`Bearer \${UPLOAD_TOKEN}\`,
|
|
5324
|
+
},
|
|
5325
|
+
body: formData,
|
|
5326
|
+
});
|
|
5327
|
+
|
|
5328
|
+
if (!response.ok) {
|
|
5329
|
+
throw new Error(\`Upload failed: \${response.status} \${response.statusText}\`);
|
|
5330
|
+
}
|
|
5331
|
+
|
|
5332
|
+
const data = await response.json();
|
|
5333
|
+
return Array.isArray(data) ? data[0] : data;
|
|
5334
|
+
}
|
|
5335
|
+
|
|
5336
|
+
/**
|
|
5337
|
+
* Upload multiple files to Strapi from the browser
|
|
5338
|
+
*/
|
|
5339
|
+
export async function uploadFiles(
|
|
5340
|
+
files: File[],
|
|
5341
|
+
fileInfo?: StrapiFileInfo
|
|
5342
|
+
): Promise<StrapiMedia[]> {
|
|
5343
|
+
const formData = new FormData();
|
|
5344
|
+
files.forEach((f) => formData.append('files', f));
|
|
5345
|
+
|
|
5346
|
+
if (fileInfo) {
|
|
5347
|
+
formData.append('fileInfo', JSON.stringify(fileInfo));
|
|
5348
|
+
}
|
|
5349
|
+
|
|
5350
|
+
const response = await fetch(\`\${STRAPI_URL}/api/upload\`, {
|
|
5351
|
+
method: 'POST',
|
|
5352
|
+
headers: {
|
|
5353
|
+
Authorization: \`Bearer \${UPLOAD_TOKEN}\`,
|
|
5354
|
+
},
|
|
5355
|
+
body: formData,
|
|
5356
|
+
});
|
|
5357
|
+
|
|
5358
|
+
if (!response.ok) {
|
|
5359
|
+
throw new Error(\`Upload failed: \${response.status} \${response.statusText}\`);
|
|
5360
|
+
}
|
|
5361
|
+
|
|
5362
|
+
return response.json();
|
|
5363
|
+
}
|
|
5364
|
+
`;
|
|
5365
|
+
}
|
|
5366
|
+
function generateUploadClientJSDoc(useESM) {
|
|
5367
|
+
return `/**
|
|
5368
|
+
* Public Upload Client
|
|
5369
|
+
* Generated by strapi2front
|
|
5370
|
+
*
|
|
5371
|
+
* Uploads files directly from the browser to Strapi using a restricted public token.
|
|
5372
|
+
* This token should ONLY have upload permissions (no delete, no update).
|
|
5373
|
+
*
|
|
5374
|
+
* Required environment variables:
|
|
5375
|
+
* PUBLIC_STRAPI_URL - Your Strapi CMS base URL
|
|
5376
|
+
* PUBLIC_STRAPI_UPLOAD_TOKEN - Restricted API token (upload-only)
|
|
5377
|
+
*
|
|
5378
|
+
* Create the token in: Strapi Admin > Settings > API Tokens
|
|
5379
|
+
* Set permissions: Upload > upload (only)
|
|
5380
|
+
*/
|
|
5381
|
+
|
|
5382
|
+
/** @typedef {import('./utils').StrapiMedia} StrapiMedia */
|
|
5383
|
+
/** @typedef {import('./utils').StrapiFileInfo} StrapiFileInfo */
|
|
5384
|
+
|
|
5385
|
+
const STRAPI_URL = ${useESM ? "import.meta.env.PUBLIC_STRAPI_URL || ''" : "process.env.PUBLIC_STRAPI_URL || ''"};
|
|
5386
|
+
const UPLOAD_TOKEN = ${useESM ? "import.meta.env.PUBLIC_STRAPI_UPLOAD_TOKEN || ''" : "process.env.PUBLIC_STRAPI_UPLOAD_TOKEN || ''"};
|
|
5387
|
+
|
|
5388
|
+
/**
|
|
5389
|
+
* Upload a single file to Strapi from the browser
|
|
5390
|
+
* @param {File} file - The file to upload
|
|
5391
|
+
* @param {StrapiFileInfo} [fileInfo] - Optional file metadata
|
|
5392
|
+
* @returns {Promise<StrapiMedia>}
|
|
5393
|
+
*/
|
|
5394
|
+
${useESM ? "export " : ""}async function uploadFile(file, fileInfo) {
|
|
5395
|
+
const formData = new FormData();
|
|
5396
|
+
formData.append('files', file);
|
|
5397
|
+
|
|
5398
|
+
if (fileInfo) {
|
|
5399
|
+
formData.append('fileInfo', JSON.stringify(fileInfo));
|
|
5400
|
+
}
|
|
5401
|
+
|
|
5402
|
+
const response = await fetch(\`\${STRAPI_URL}/api/upload\`, {
|
|
5403
|
+
method: 'POST',
|
|
5404
|
+
headers: {
|
|
5405
|
+
Authorization: \`Bearer \${UPLOAD_TOKEN}\`,
|
|
5406
|
+
},
|
|
5407
|
+
body: formData,
|
|
5408
|
+
});
|
|
5409
|
+
|
|
5410
|
+
if (!response.ok) {
|
|
5411
|
+
throw new Error(\`Upload failed: \${response.status} \${response.statusText}\`);
|
|
5412
|
+
}
|
|
5413
|
+
|
|
5414
|
+
const data = await response.json();
|
|
5415
|
+
return Array.isArray(data) ? data[0] : data;
|
|
5416
|
+
}
|
|
5417
|
+
|
|
5418
|
+
/**
|
|
5419
|
+
* Upload multiple files to Strapi from the browser
|
|
5420
|
+
* @param {File[]} files - The files to upload
|
|
5421
|
+
* @param {StrapiFileInfo} [fileInfo] - Optional file metadata (applied to all)
|
|
5422
|
+
* @returns {Promise<StrapiMedia[]>}
|
|
5423
|
+
*/
|
|
5424
|
+
${useESM ? "export " : ""}async function uploadFiles(files, fileInfo) {
|
|
5425
|
+
const formData = new FormData();
|
|
5426
|
+
files.forEach((f) => formData.append('files', f));
|
|
5427
|
+
|
|
5428
|
+
if (fileInfo) {
|
|
5429
|
+
formData.append('fileInfo', JSON.stringify(fileInfo));
|
|
5430
|
+
}
|
|
5431
|
+
|
|
5432
|
+
const response = await fetch(\`\${STRAPI_URL}/api/upload\`, {
|
|
5433
|
+
method: 'POST',
|
|
5434
|
+
headers: {
|
|
5435
|
+
Authorization: \`Bearer \${UPLOAD_TOKEN}\`,
|
|
5436
|
+
},
|
|
5437
|
+
body: formData,
|
|
5438
|
+
});
|
|
5439
|
+
|
|
5440
|
+
if (!response.ok) {
|
|
5441
|
+
throw new Error(\`Upload failed: \${response.status} \${response.statusText}\`);
|
|
5442
|
+
}
|
|
5443
|
+
|
|
5444
|
+
return response.json();
|
|
5445
|
+
}
|
|
5446
|
+
|
|
5447
|
+
${useESM ? "" : "module.exports = { uploadFile, uploadFiles };"}
|
|
5448
|
+
`;
|
|
5449
|
+
}
|
|
5450
|
+
function generateUploadActionTS() {
|
|
5451
|
+
return `/**
|
|
5452
|
+
* Upload Action
|
|
5453
|
+
* Generated by strapi2front
|
|
5454
|
+
*
|
|
5455
|
+
* Astro Action that receives files via FormData and uploads them to Strapi server-side.
|
|
5456
|
+
* Uses the private STRAPI_TOKEN \u2014 never exposed to the browser.
|
|
5457
|
+
*
|
|
5458
|
+
* Usage:
|
|
5459
|
+
* import { actions } from 'astro:actions';
|
|
5460
|
+
* const result = await actions.upload({ file: myFile, alternativeText: 'My image' });
|
|
5461
|
+
*
|
|
5462
|
+
* Register this action in src/actions/index.ts:
|
|
5463
|
+
* import { uploadAction, uploadMultipleAction } from '../strapi/shared/upload-action';
|
|
5464
|
+
* export const server = { upload: uploadAction, uploadMultiple: uploadMultipleAction };
|
|
5465
|
+
*/
|
|
5466
|
+
|
|
5467
|
+
import { defineAction, ActionError } from 'astro:actions';
|
|
5468
|
+
import { z } from 'astro:schema';
|
|
5469
|
+
import { files } from './client';
|
|
5470
|
+
|
|
5471
|
+
/**
|
|
5472
|
+
* Upload a single file via Astro Action (server-side, secure)
|
|
5473
|
+
*/
|
|
5474
|
+
export const uploadAction = defineAction({
|
|
5475
|
+
accept: 'form',
|
|
5476
|
+
input: z.object({
|
|
5477
|
+
file: z.instanceof(File),
|
|
5478
|
+
name: z.string().optional(),
|
|
5479
|
+
alternativeText: z.string().optional(),
|
|
5480
|
+
caption: z.string().optional(),
|
|
5481
|
+
}),
|
|
5482
|
+
handler: async (input) => {
|
|
5483
|
+
try {
|
|
5484
|
+
const { file, name, alternativeText, caption } = input;
|
|
5485
|
+
|
|
5486
|
+
const fileInfo: Record<string, string> = {};
|
|
5487
|
+
if (name) fileInfo.name = name;
|
|
5488
|
+
if (alternativeText) fileInfo.alternativeText = alternativeText;
|
|
5489
|
+
if (caption) fileInfo.caption = caption;
|
|
5490
|
+
|
|
5491
|
+
const result = await files.upload(file, {
|
|
5492
|
+
fileInfo: Object.keys(fileInfo).length > 0 ? fileInfo : undefined,
|
|
5493
|
+
});
|
|
5494
|
+
|
|
5495
|
+
return result;
|
|
5496
|
+
} catch (error) {
|
|
5497
|
+
throw new ActionError({
|
|
5498
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
5499
|
+
message: error instanceof Error ? error.message : 'Upload failed',
|
|
5500
|
+
});
|
|
5501
|
+
}
|
|
5502
|
+
},
|
|
5503
|
+
});
|
|
5504
|
+
|
|
5505
|
+
/**
|
|
5506
|
+
* Upload multiple files via Astro Action (server-side, secure)
|
|
5507
|
+
*/
|
|
5508
|
+
export const uploadMultipleAction = defineAction({
|
|
5509
|
+
accept: 'form',
|
|
5510
|
+
input: z.object({
|
|
5511
|
+
files: z.array(z.instanceof(File)).min(1),
|
|
5512
|
+
alternativeText: z.string().optional(),
|
|
5513
|
+
caption: z.string().optional(),
|
|
5514
|
+
}),
|
|
5515
|
+
handler: async (input) => {
|
|
5516
|
+
try {
|
|
5517
|
+
const results = await Promise.all(
|
|
5518
|
+
input.files.map((file) =>
|
|
5519
|
+
files.upload(file, {
|
|
5520
|
+
fileInfo: {
|
|
5521
|
+
...(input.alternativeText && { alternativeText: input.alternativeText }),
|
|
5522
|
+
...(input.caption && { caption: input.caption }),
|
|
5523
|
+
},
|
|
5524
|
+
})
|
|
5525
|
+
)
|
|
5526
|
+
);
|
|
5527
|
+
return results;
|
|
5528
|
+
} catch (error) {
|
|
5529
|
+
throw new ActionError({
|
|
5530
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
5531
|
+
message: error instanceof Error ? error.message : 'Upload failed',
|
|
5532
|
+
});
|
|
5533
|
+
}
|
|
5534
|
+
},
|
|
5535
|
+
});
|
|
5536
|
+
`;
|
|
5537
|
+
}
|
|
4192
5538
|
|
|
4193
5539
|
// src/frameworks/nextjs/actions.ts
|
|
4194
5540
|
async function generateNextJsActions(_schema, _options) {
|
|
@@ -4222,7 +5568,176 @@ var frameworkSupport = {
|
|
|
4222
5568
|
nextjs: { status: "coming-soon", minVersion: "14.0.0" },
|
|
4223
5569
|
nuxt: { status: "coming-soon", minVersion: "3.0.0" }
|
|
4224
5570
|
};
|
|
5571
|
+
async function generateZodSchemas(schema, options) {
|
|
5572
|
+
const { outputDir, byFeature = false, strapiVersion = "v5" } = options;
|
|
5573
|
+
const generatedFiles = [];
|
|
5574
|
+
await ensureDir(outputDir);
|
|
5575
|
+
if (byFeature) {
|
|
5576
|
+
for (const collection of schema.collections) {
|
|
5577
|
+
const dirPath = path9.join(outputDir, "collections", toKebabCase(collection.singularName));
|
|
5578
|
+
await ensureDir(dirPath);
|
|
5579
|
+
const filePath = path9.join(dirPath, "schemas.ts");
|
|
5580
|
+
const content = generateCollectionSchemas2(collection, schema.components, strapiVersion);
|
|
5581
|
+
await writeFile(filePath, await formatCode(content));
|
|
5582
|
+
generatedFiles.push({
|
|
5583
|
+
filePath,
|
|
5584
|
+
schemas: [
|
|
5585
|
+
`${toCamelCase(collection.singularName)}CreateSchema`,
|
|
5586
|
+
`${toCamelCase(collection.singularName)}UpdateSchema`
|
|
5587
|
+
]
|
|
5588
|
+
});
|
|
5589
|
+
}
|
|
5590
|
+
for (const single of schema.singles) {
|
|
5591
|
+
const dirPath = path9.join(outputDir, "singles", toKebabCase(single.singularName));
|
|
5592
|
+
await ensureDir(dirPath);
|
|
5593
|
+
const filePath = path9.join(dirPath, "schemas.ts");
|
|
5594
|
+
const content = generateSingleSchemas2(single, schema.components, strapiVersion);
|
|
5595
|
+
await writeFile(filePath, await formatCode(content));
|
|
5596
|
+
generatedFiles.push({
|
|
5597
|
+
filePath,
|
|
5598
|
+
schemas: [`${toCamelCase(single.singularName)}UpdateSchema`]
|
|
5599
|
+
});
|
|
5600
|
+
}
|
|
5601
|
+
} else {
|
|
5602
|
+
const filePath = path9.join(outputDir, "schemas.ts");
|
|
5603
|
+
const content = generateAllSchemas(schema, strapiVersion);
|
|
5604
|
+
await writeFile(filePath, await formatCode(content));
|
|
5605
|
+
const allSchemas = [];
|
|
5606
|
+
for (const collection of schema.collections) {
|
|
5607
|
+
allSchemas.push(`${toCamelCase(collection.singularName)}CreateSchema`);
|
|
5608
|
+
allSchemas.push(`${toCamelCase(collection.singularName)}UpdateSchema`);
|
|
5609
|
+
}
|
|
5610
|
+
for (const single of schema.singles) {
|
|
5611
|
+
allSchemas.push(`${toCamelCase(single.singularName)}UpdateSchema`);
|
|
5612
|
+
}
|
|
5613
|
+
generatedFiles.push({ filePath, schemas: allSchemas });
|
|
5614
|
+
}
|
|
5615
|
+
return generatedFiles;
|
|
5616
|
+
}
|
|
5617
|
+
function generateAllSchemas(schema, strapiVersion = "v5") {
|
|
5618
|
+
const sections = [];
|
|
5619
|
+
sections.push(`/**
|
|
5620
|
+
* Zod Validation Schemas
|
|
5621
|
+
* Generated by strapi2front
|
|
5622
|
+
*
|
|
5623
|
+
* These schemas can be used for form validation, API input validation, etc.
|
|
5624
|
+
*/
|
|
5625
|
+
|
|
5626
|
+
import { z } from 'zod';
|
|
5627
|
+
`);
|
|
5628
|
+
if (schema.collections.length > 0) {
|
|
5629
|
+
sections.push("// ============================================");
|
|
5630
|
+
sections.push("// Collection Schemas");
|
|
5631
|
+
sections.push("// ============================================\n");
|
|
5632
|
+
for (const collection of schema.collections) {
|
|
5633
|
+
sections.push(generateCollectionSchemaContent(collection, schema.components, strapiVersion));
|
|
5634
|
+
sections.push("");
|
|
5635
|
+
}
|
|
5636
|
+
}
|
|
5637
|
+
if (schema.singles.length > 0) {
|
|
5638
|
+
sections.push("// ============================================");
|
|
5639
|
+
sections.push("// Single Type Schemas");
|
|
5640
|
+
sections.push("// ============================================\n");
|
|
5641
|
+
for (const single of schema.singles) {
|
|
5642
|
+
sections.push(generateSingleSchemaContent(single, schema.components, strapiVersion));
|
|
5643
|
+
sections.push("");
|
|
5644
|
+
}
|
|
5645
|
+
}
|
|
5646
|
+
sections.push("// ============================================");
|
|
5647
|
+
sections.push("// Schema Exports");
|
|
5648
|
+
sections.push("// ============================================\n");
|
|
5649
|
+
const exports$1 = [];
|
|
5650
|
+
for (const collection of schema.collections) {
|
|
5651
|
+
const name = toCamelCase(collection.singularName);
|
|
5652
|
+
exports$1.push(` ${name}CreateSchema,`);
|
|
5653
|
+
exports$1.push(` ${name}UpdateSchema,`);
|
|
5654
|
+
}
|
|
5655
|
+
for (const single of schema.singles) {
|
|
5656
|
+
const name = toCamelCase(single.singularName);
|
|
5657
|
+
exports$1.push(` ${name}UpdateSchema,`);
|
|
5658
|
+
}
|
|
5659
|
+
sections.push(`export const schemas = {
|
|
5660
|
+
${exports$1.join("\n")}
|
|
5661
|
+
};`);
|
|
5662
|
+
return sections.join("\n");
|
|
5663
|
+
}
|
|
5664
|
+
function generateCollectionSchemas2(collection, _components, strapiVersion = "v5") {
|
|
5665
|
+
const name = toCamelCase(collection.singularName);
|
|
5666
|
+
const pascalName = toPascalCase(collection.singularName);
|
|
5667
|
+
return `/**
|
|
5668
|
+
* ${collection.displayName} Zod Schemas
|
|
5669
|
+
* ${collection.description || ""}
|
|
5670
|
+
* Generated by strapi2front
|
|
5671
|
+
*/
|
|
5672
|
+
|
|
5673
|
+
import { z } from 'zod';
|
|
5674
|
+
|
|
5675
|
+
${generateCollectionSchemaContent(collection, _components, strapiVersion)}
|
|
5676
|
+
|
|
5677
|
+
// Type inference helpers
|
|
5678
|
+
export type ${pascalName}CreateInput = z.infer<typeof ${name}CreateSchema>;
|
|
5679
|
+
export type ${pascalName}UpdateInput = z.infer<typeof ${name}UpdateSchema>;
|
|
5680
|
+
`;
|
|
5681
|
+
}
|
|
5682
|
+
function generateSingleSchemas2(single, _components, strapiVersion = "v5") {
|
|
5683
|
+
const name = toCamelCase(single.singularName);
|
|
5684
|
+
const pascalName = toPascalCase(single.singularName);
|
|
5685
|
+
return `/**
|
|
5686
|
+
* ${single.displayName} Zod Schemas
|
|
5687
|
+
* ${single.description || ""}
|
|
5688
|
+
* Generated by strapi2front
|
|
5689
|
+
*/
|
|
5690
|
+
|
|
5691
|
+
import { z } from 'zod';
|
|
5692
|
+
|
|
5693
|
+
${generateSingleSchemaContent(single, _components, strapiVersion)}
|
|
5694
|
+
|
|
5695
|
+
// Type inference helpers
|
|
5696
|
+
export type ${pascalName}UpdateInput = z.infer<typeof ${name}UpdateSchema>;
|
|
5697
|
+
`;
|
|
5698
|
+
}
|
|
5699
|
+
function generateCollectionSchemaContent(collection, _components, strapiVersion = "v5") {
|
|
5700
|
+
const name = toCamelCase(collection.singularName);
|
|
5701
|
+
const sections = [];
|
|
5702
|
+
const createOptions = {
|
|
5703
|
+
isUpdate: false,
|
|
5704
|
+
strapiVersion
|
|
5705
|
+
};
|
|
5706
|
+
const updateOptions = {
|
|
5707
|
+
isUpdate: true,
|
|
5708
|
+
strapiVersion
|
|
5709
|
+
};
|
|
5710
|
+
const createResult = generateZodObjectSchema(collection.attributes, createOptions);
|
|
5711
|
+
sections.push(`/**
|
|
5712
|
+
* Schema for creating a new ${collection.displayName}
|
|
5713
|
+
*/
|
|
5714
|
+
export const ${name}CreateSchema = ${createResult.schema};`);
|
|
5715
|
+
if (createResult.skippedFields.length > 0) {
|
|
5716
|
+
sections.push(`// Skipped fields: ${createResult.skippedFields.map((f) => f.name).join(", ")}`);
|
|
5717
|
+
}
|
|
5718
|
+
sections.push("");
|
|
5719
|
+
const updateResult = generateZodObjectSchema(collection.attributes, updateOptions);
|
|
5720
|
+
sections.push(`/**
|
|
5721
|
+
* Schema for updating a ${collection.displayName}
|
|
5722
|
+
* All fields are optional for partial updates
|
|
5723
|
+
*/
|
|
5724
|
+
export const ${name}UpdateSchema = ${updateResult.schema};`);
|
|
5725
|
+
return sections.join("\n");
|
|
5726
|
+
}
|
|
5727
|
+
function generateSingleSchemaContent(single, _components, strapiVersion = "v5") {
|
|
5728
|
+
const name = toCamelCase(single.singularName);
|
|
5729
|
+
const updateOptions = {
|
|
5730
|
+
isUpdate: true,
|
|
5731
|
+
strapiVersion
|
|
5732
|
+
};
|
|
5733
|
+
const updateResult = generateZodObjectSchema(single.attributes, updateOptions);
|
|
5734
|
+
return `/**
|
|
5735
|
+
* Schema for updating ${single.displayName}
|
|
5736
|
+
* All fields are optional for partial updates
|
|
5737
|
+
*/
|
|
5738
|
+
export const ${name}UpdateSchema = ${updateResult.schema};`;
|
|
5739
|
+
}
|
|
4225
5740
|
|
|
4226
|
-
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 };
|
|
5741
|
+
export { deleteFile, ensureDir, fileExists, formatCode, formatJson, frameworkSupport, generateActions, generateAstroActions, generateByFeature, generateClient, generateJSDocServices, generateJSDocTypes, generateLocales, generateNextJsActions, generateNuxtServerRoutes, generateServices, generateTypeScriptTypes, generateTypes, generateZodObjectSchema, generateZodSchemas, isAstroActionsSupported, isNextJsActionsSupported, isNuxtServerRoutesSupported, isSystemField, listFiles, mapAttributeToZodSchema, pluralize, readFile, toCamelCase, toKebabCase, toPascalCase, writeFile };
|
|
4227
5742
|
//# sourceMappingURL=index.js.map
|
|
4228
5743
|
//# sourceMappingURL=index.js.map
|