@see-ms/converter 0.1.2 → 0.1.4
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/README.md +38 -13
- package/dist/cli.mjs +872 -61
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +288 -55
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/converter.ts
|
|
2
2
|
import pc3 from "picocolors";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import path11 from "path";
|
|
4
|
+
import fs9 from "fs-extra";
|
|
5
5
|
|
|
6
6
|
// src/filesystem.ts
|
|
7
7
|
import fs from "fs-extra";
|
|
@@ -472,13 +472,20 @@ import * as cheerio2 from "cheerio";
|
|
|
472
472
|
import fs5 from "fs-extra";
|
|
473
473
|
import path6 from "path";
|
|
474
474
|
function cleanClassName(className) {
|
|
475
|
-
return className.split(" ").filter((cls) => !cls.startsWith("c-") && !cls.startsWith("w-")).filter((cls) => cls.length > 0).
|
|
475
|
+
return className.split(" ").filter((cls) => !cls.startsWith("c-") && !cls.startsWith("w-")).filter((cls) => cls.length > 0).join(" ");
|
|
476
476
|
}
|
|
477
477
|
function getPrimaryClass(classAttr) {
|
|
478
478
|
if (!classAttr) return null;
|
|
479
479
|
const cleaned = cleanClassName(classAttr);
|
|
480
480
|
const classes = cleaned.split(" ").filter((c) => c.length > 0);
|
|
481
|
-
|
|
481
|
+
if (classes.length === 0) return null;
|
|
482
|
+
const original = classes[0];
|
|
483
|
+
return {
|
|
484
|
+
selector: original,
|
|
485
|
+
// Keep original with dashes for CSS selector
|
|
486
|
+
fieldName: original.replace(/-/g, "_")
|
|
487
|
+
// Normalize for field name
|
|
488
|
+
};
|
|
482
489
|
}
|
|
483
490
|
function getContextModifier(_$, $el) {
|
|
484
491
|
let $current = $el.parent();
|
|
@@ -535,11 +542,11 @@ function detectEditableFields(templateHtml) {
|
|
|
535
542
|
const potentialCollections = /* @__PURE__ */ new Map();
|
|
536
543
|
$("[class]").each((_, el) => {
|
|
537
544
|
const primaryClass = getPrimaryClass($(el).attr("class"));
|
|
538
|
-
if (primaryClass && (primaryClass.includes("card") || primaryClass.includes("item") || primaryClass.includes("post") || primaryClass.includes("feature")) && !primaryClass.includes("image") && !primaryClass.includes("inner")) {
|
|
539
|
-
if (!potentialCollections.has(primaryClass)) {
|
|
540
|
-
potentialCollections.set(primaryClass, []);
|
|
545
|
+
if (primaryClass && (primaryClass.fieldName.includes("card") || primaryClass.fieldName.includes("item") || primaryClass.fieldName.includes("post") || primaryClass.fieldName.includes("feature")) && !primaryClass.fieldName.includes("image") && !primaryClass.fieldName.includes("inner")) {
|
|
546
|
+
if (!potentialCollections.has(primaryClass.fieldName)) {
|
|
547
|
+
potentialCollections.set(primaryClass.fieldName, []);
|
|
541
548
|
}
|
|
542
|
-
potentialCollections.get(primaryClass)?.push(el);
|
|
549
|
+
potentialCollections.get(primaryClass.fieldName)?.push(el);
|
|
543
550
|
}
|
|
544
551
|
});
|
|
545
552
|
potentialCollections.forEach((elements, className) => {
|
|
@@ -553,40 +560,43 @@ function detectEditableFields(templateHtml) {
|
|
|
553
560
|
collectionElements.add(child);
|
|
554
561
|
});
|
|
555
562
|
});
|
|
563
|
+
const collectionClassInfo = getPrimaryClass($(elements[0]).attr("class"));
|
|
564
|
+
const collectionSelector = collectionClassInfo ? `.${collectionClassInfo.selector}` : `.${className}`;
|
|
556
565
|
$first.find("img").each((_, img) => {
|
|
557
566
|
if (isInsideButton($, img)) return;
|
|
558
567
|
const $img = $(img);
|
|
559
568
|
const $parent = $img.parent();
|
|
560
|
-
const
|
|
561
|
-
if (
|
|
562
|
-
collectionFields.image = `.${
|
|
569
|
+
const parentClassInfo = getPrimaryClass($parent.attr("class"));
|
|
570
|
+
if (parentClassInfo && parentClassInfo.fieldName.includes("image")) {
|
|
571
|
+
collectionFields.image = `.${parentClassInfo.selector}`;
|
|
563
572
|
return false;
|
|
564
573
|
}
|
|
565
574
|
});
|
|
566
575
|
$first.find("div").each((_, el) => {
|
|
567
|
-
const
|
|
568
|
-
if (
|
|
569
|
-
collectionFields.tag = `.${
|
|
576
|
+
const classInfo = getPrimaryClass($(el).attr("class"));
|
|
577
|
+
if (classInfo && classInfo.fieldName.includes("tag") && !classInfo.fieldName.includes("container")) {
|
|
578
|
+
collectionFields.tag = `.${classInfo.selector}`;
|
|
570
579
|
return false;
|
|
571
580
|
}
|
|
572
581
|
});
|
|
573
582
|
$first.find("h1, h2, h3, h4, h5, h6").first().each((_, el) => {
|
|
574
|
-
const
|
|
575
|
-
if (
|
|
576
|
-
collectionFields.title = `.${
|
|
583
|
+
const classInfo = getPrimaryClass($(el).attr("class"));
|
|
584
|
+
if (classInfo) {
|
|
585
|
+
collectionFields.title = `.${classInfo.selector}`;
|
|
577
586
|
}
|
|
578
587
|
});
|
|
579
588
|
$first.find("p").first().each((_, el) => {
|
|
580
|
-
const
|
|
581
|
-
if (
|
|
582
|
-
collectionFields.description = `.${
|
|
589
|
+
const classInfo = getPrimaryClass($(el).attr("class"));
|
|
590
|
+
if (classInfo) {
|
|
591
|
+
collectionFields.description = `.${classInfo.selector}`;
|
|
583
592
|
}
|
|
584
593
|
});
|
|
585
594
|
$first.find("a, NuxtLink").not(".c_button, .c_icon_button").each((_, el) => {
|
|
586
595
|
const $link = $(el);
|
|
587
596
|
const linkText = $link.text().trim();
|
|
588
597
|
if (linkText) {
|
|
589
|
-
|
|
598
|
+
const classInfo = getPrimaryClass($link.attr("class"));
|
|
599
|
+
collectionFields.link = classInfo ? `.${classInfo.selector}` : "a";
|
|
590
600
|
return false;
|
|
591
601
|
}
|
|
592
602
|
});
|
|
@@ -596,7 +606,7 @@ function detectEditableFields(templateHtml) {
|
|
|
596
606
|
collectionName += "s";
|
|
597
607
|
}
|
|
598
608
|
detectedCollections[collectionName] = {
|
|
599
|
-
selector:
|
|
609
|
+
selector: collectionSelector,
|
|
600
610
|
fields: collectionFields
|
|
601
611
|
};
|
|
602
612
|
}
|
|
@@ -607,25 +617,30 @@ function detectEditableFields(templateHtml) {
|
|
|
607
617
|
if (collectionElements.has(el)) return;
|
|
608
618
|
const $el = $(el);
|
|
609
619
|
const text = $el.text().trim();
|
|
610
|
-
const
|
|
620
|
+
const classInfo = getPrimaryClass($el.attr("class"));
|
|
611
621
|
if (text) {
|
|
612
622
|
let fieldName;
|
|
613
|
-
|
|
614
|
-
|
|
623
|
+
let selector;
|
|
624
|
+
if (classInfo && !classInfo.fieldName.startsWith("heading_")) {
|
|
625
|
+
fieldName = classInfo.fieldName;
|
|
626
|
+
selector = `.${classInfo.selector}`;
|
|
615
627
|
} else {
|
|
616
628
|
const $parent = $el.closest('[class*="header"], [class*="hero"], [class*="cta"]').first();
|
|
617
|
-
const
|
|
629
|
+
const parentClassInfo = getPrimaryClass($parent.attr("class"));
|
|
618
630
|
const modifier = getContextModifier($, $el);
|
|
619
|
-
if (
|
|
620
|
-
fieldName = modifier ? `${modifier}_${
|
|
631
|
+
if (parentClassInfo) {
|
|
632
|
+
fieldName = modifier ? `${modifier}_${parentClassInfo.fieldName}` : parentClassInfo.fieldName;
|
|
633
|
+
selector = classInfo ? `.${classInfo.selector}` : `.${parentClassInfo.selector}`;
|
|
621
634
|
} else if (modifier) {
|
|
622
635
|
fieldName = `${modifier}_heading`;
|
|
636
|
+
selector = classInfo ? `.${classInfo.selector}` : el.tagName.toLowerCase();
|
|
623
637
|
} else {
|
|
624
638
|
fieldName = `heading_${index}`;
|
|
639
|
+
selector = classInfo ? `.${classInfo.selector}` : el.tagName.toLowerCase();
|
|
625
640
|
}
|
|
626
641
|
}
|
|
627
642
|
detectedFields[fieldName] = {
|
|
628
|
-
selector
|
|
643
|
+
selector,
|
|
629
644
|
type: "plain",
|
|
630
645
|
editable: true
|
|
631
646
|
};
|
|
@@ -635,11 +650,11 @@ function detectEditableFields(templateHtml) {
|
|
|
635
650
|
if (collectionElements.has(el)) return;
|
|
636
651
|
const $el = $(el);
|
|
637
652
|
const text = $el.text().trim();
|
|
638
|
-
const
|
|
639
|
-
if (text && text.length > 20 &&
|
|
653
|
+
const classInfo = getPrimaryClass($el.attr("class"));
|
|
654
|
+
if (text && text.length > 20 && classInfo) {
|
|
640
655
|
const hasFormatting = $el.find("strong, em, b, i, a, NuxtLink").length > 0;
|
|
641
|
-
detectedFields[
|
|
642
|
-
selector: `.${
|
|
656
|
+
detectedFields[classInfo.fieldName] = {
|
|
657
|
+
selector: `.${classInfo.selector}`,
|
|
643
658
|
type: hasFormatting ? "rich" : "plain",
|
|
644
659
|
editable: true
|
|
645
660
|
};
|
|
@@ -651,11 +666,11 @@ function detectEditableFields(templateHtml) {
|
|
|
651
666
|
const $el = $(el);
|
|
652
667
|
if (isDecorativeImage($, $el)) return;
|
|
653
668
|
const $parent = $el.parent();
|
|
654
|
-
const
|
|
655
|
-
if (
|
|
656
|
-
const fieldName =
|
|
669
|
+
const parentClassInfo = getPrimaryClass($parent.attr("class"));
|
|
670
|
+
if (parentClassInfo) {
|
|
671
|
+
const fieldName = parentClassInfo.fieldName.includes("image") ? parentClassInfo.fieldName : `${parentClassInfo.fieldName}_image`;
|
|
657
672
|
detectedFields[fieldName] = {
|
|
658
|
-
selector: `.${
|
|
673
|
+
selector: `.${parentClassInfo.selector}`,
|
|
659
674
|
type: "image",
|
|
660
675
|
editable: true
|
|
661
676
|
};
|
|
@@ -669,8 +684,9 @@ function detectEditableFields(templateHtml) {
|
|
|
669
684
|
}).first().text().trim();
|
|
670
685
|
if (text && text.length > 2) {
|
|
671
686
|
const $parent = $el.closest('[class*="cta"]').first();
|
|
672
|
-
const
|
|
673
|
-
|
|
687
|
+
const parentClassInfo = getPrimaryClass($parent.attr("class"));
|
|
688
|
+
const fieldName = parentClassInfo ? `${parentClassInfo.fieldName}_button_text` : "button_text";
|
|
689
|
+
detectedFields[fieldName] = {
|
|
674
690
|
selector: `.c_button`,
|
|
675
691
|
type: "plain",
|
|
676
692
|
editable: true
|
|
@@ -736,6 +752,18 @@ function mapFieldTypeToStrapi(fieldType) {
|
|
|
736
752
|
};
|
|
737
753
|
return typeMap[fieldType] || "string";
|
|
738
754
|
}
|
|
755
|
+
function pluralize(word) {
|
|
756
|
+
if (word.endsWith("s") || word.endsWith("x") || word.endsWith("z") || word.endsWith("ch") || word.endsWith("sh")) {
|
|
757
|
+
return word + "es";
|
|
758
|
+
}
|
|
759
|
+
if (word.endsWith("y") && word.length > 1) {
|
|
760
|
+
const secondLast = word[word.length - 2];
|
|
761
|
+
if (!"aeiou".includes(secondLast.toLowerCase())) {
|
|
762
|
+
return word.slice(0, -1) + "ies";
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return word + "s";
|
|
766
|
+
}
|
|
739
767
|
function pageToStrapiSchema(pageName, fields) {
|
|
740
768
|
const attributes = {};
|
|
741
769
|
for (const [fieldName, field] of Object.entries(fields)) {
|
|
@@ -748,12 +776,14 @@ function pageToStrapiSchema(pageName, fields) {
|
|
|
748
776
|
}
|
|
749
777
|
}
|
|
750
778
|
const displayName = pageName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
779
|
+
const kebabCaseName = pageName;
|
|
780
|
+
const pluralName = pluralize(kebabCaseName);
|
|
751
781
|
return {
|
|
752
782
|
kind: "singleType",
|
|
753
|
-
collectionName:
|
|
783
|
+
collectionName: kebabCaseName,
|
|
754
784
|
info: {
|
|
755
|
-
singularName:
|
|
756
|
-
pluralName
|
|
785
|
+
singularName: kebabCaseName,
|
|
786
|
+
pluralName,
|
|
757
787
|
displayName
|
|
758
788
|
},
|
|
759
789
|
options: {
|
|
@@ -780,13 +810,14 @@ function collectionToStrapiSchema(collectionName, collection) {
|
|
|
780
810
|
};
|
|
781
811
|
}
|
|
782
812
|
const displayName = collectionName.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
783
|
-
const
|
|
813
|
+
const kebabCaseName = collectionName.replace(/_/g, "-");
|
|
814
|
+
const singularName = kebabCaseName.endsWith("s") ? kebabCaseName.slice(0, -1) : kebabCaseName;
|
|
784
815
|
return {
|
|
785
816
|
kind: "collectionType",
|
|
786
|
-
collectionName:
|
|
817
|
+
collectionName: kebabCaseName,
|
|
787
818
|
info: {
|
|
788
|
-
singularName
|
|
789
|
-
pluralName:
|
|
819
|
+
singularName,
|
|
820
|
+
pluralName: kebabCaseName,
|
|
790
821
|
displayName
|
|
791
822
|
},
|
|
792
823
|
options: {
|
|
@@ -898,6 +929,186 @@ const { data } = await $fetch('http://localhost:1337/api/portfolio-cards')
|
|
|
898
929
|
await fs7.writeFile(readmePath, content, "utf-8");
|
|
899
930
|
}
|
|
900
931
|
|
|
932
|
+
// src/content-extractor.ts
|
|
933
|
+
import * as cheerio3 from "cheerio";
|
|
934
|
+
import path9 from "path";
|
|
935
|
+
function extractContentFromHTML(html, _pageName, pageManifest) {
|
|
936
|
+
const $ = cheerio3.load(html);
|
|
937
|
+
const content = {
|
|
938
|
+
fields: {},
|
|
939
|
+
collections: {}
|
|
940
|
+
};
|
|
941
|
+
if (pageManifest.fields) {
|
|
942
|
+
for (const [fieldName, field] of Object.entries(pageManifest.fields)) {
|
|
943
|
+
const selector = field.selector;
|
|
944
|
+
const element = $(selector).first();
|
|
945
|
+
if (element.length > 0) {
|
|
946
|
+
if (field.type === "image") {
|
|
947
|
+
const src = element.attr("src") || element.find("img").attr("src") || "";
|
|
948
|
+
content.fields[fieldName] = src;
|
|
949
|
+
} else {
|
|
950
|
+
const text = element.text().trim();
|
|
951
|
+
content.fields[fieldName] = text;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
if (pageManifest.collections) {
|
|
957
|
+
for (const [collectionName, collection] of Object.entries(pageManifest.collections)) {
|
|
958
|
+
const items = [];
|
|
959
|
+
const collectionElements = $(collection.selector);
|
|
960
|
+
collectionElements.each((_, elem) => {
|
|
961
|
+
const item = {};
|
|
962
|
+
const $elem = $(elem);
|
|
963
|
+
for (const [fieldName, fieldSelector] of Object.entries(collection.fields)) {
|
|
964
|
+
const fieldElement = $elem.find(fieldSelector).first();
|
|
965
|
+
if (fieldElement.length > 0) {
|
|
966
|
+
if (fieldName === "image" || fieldName.includes("image")) {
|
|
967
|
+
const src = fieldElement.attr("src") || fieldElement.find("img").attr("src") || "";
|
|
968
|
+
item[fieldName] = src;
|
|
969
|
+
} else if (fieldName === "link" || fieldName === "url") {
|
|
970
|
+
const href = fieldElement.attr("href") || "";
|
|
971
|
+
item[fieldName] = href;
|
|
972
|
+
} else {
|
|
973
|
+
const text = fieldElement.text().trim();
|
|
974
|
+
item[fieldName] = text;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
if (Object.keys(item).length > 0) {
|
|
979
|
+
items.push(item);
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
if (items.length > 0) {
|
|
983
|
+
content.collections[collectionName] = items;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return content;
|
|
988
|
+
}
|
|
989
|
+
function extractAllContent(htmlFiles, manifest) {
|
|
990
|
+
const extractedContent = {
|
|
991
|
+
pages: {}
|
|
992
|
+
};
|
|
993
|
+
for (const [pageName, pageManifest] of Object.entries(manifest.pages)) {
|
|
994
|
+
const html = htmlFiles.get(pageName);
|
|
995
|
+
if (html) {
|
|
996
|
+
const content = extractContentFromHTML(html, pageName, pageManifest);
|
|
997
|
+
extractedContent.pages[pageName] = content;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return extractedContent;
|
|
1001
|
+
}
|
|
1002
|
+
function normalizeImagePath(imageSrc) {
|
|
1003
|
+
if (!imageSrc) return "";
|
|
1004
|
+
if (imageSrc.startsWith("/")) return imageSrc;
|
|
1005
|
+
const filename = path9.basename(imageSrc);
|
|
1006
|
+
if (imageSrc.includes("images/")) {
|
|
1007
|
+
return `/images/${filename}`;
|
|
1008
|
+
}
|
|
1009
|
+
return `/${filename}`;
|
|
1010
|
+
}
|
|
1011
|
+
function formatForStrapi(extracted) {
|
|
1012
|
+
const seedData = {};
|
|
1013
|
+
for (const [pageName, content] of Object.entries(extracted.pages)) {
|
|
1014
|
+
if (Object.keys(content.fields).length > 0) {
|
|
1015
|
+
const formattedFields = {};
|
|
1016
|
+
for (const [fieldName, value] of Object.entries(content.fields)) {
|
|
1017
|
+
if (fieldName.includes("image") || fieldName.includes("bg")) {
|
|
1018
|
+
formattedFields[fieldName] = normalizeImagePath(value);
|
|
1019
|
+
} else {
|
|
1020
|
+
formattedFields[fieldName] = value;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
seedData[pageName] = formattedFields;
|
|
1024
|
+
}
|
|
1025
|
+
for (const [collectionName, items] of Object.entries(content.collections)) {
|
|
1026
|
+
const formattedItems = items.map((item) => {
|
|
1027
|
+
const formattedItem = {};
|
|
1028
|
+
for (const [fieldName, value] of Object.entries(item)) {
|
|
1029
|
+
if (fieldName === "image" || fieldName.includes("image")) {
|
|
1030
|
+
formattedItem[fieldName] = normalizeImagePath(value);
|
|
1031
|
+
} else {
|
|
1032
|
+
formattedItem[fieldName] = value;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
return formattedItem;
|
|
1036
|
+
});
|
|
1037
|
+
seedData[collectionName] = formattedItems;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
return seedData;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// src/seed-writer.ts
|
|
1044
|
+
import fs8 from "fs-extra";
|
|
1045
|
+
import path10 from "path";
|
|
1046
|
+
async function writeSeedData(outputDir, seedData) {
|
|
1047
|
+
const seedDir = path10.join(outputDir, "cms-seed");
|
|
1048
|
+
await fs8.ensureDir(seedDir);
|
|
1049
|
+
const seedPath = path10.join(seedDir, "seed-data.json");
|
|
1050
|
+
await fs8.writeJson(seedPath, seedData, { spaces: 2 });
|
|
1051
|
+
}
|
|
1052
|
+
async function createSeedReadme(outputDir) {
|
|
1053
|
+
const readmePath = path10.join(outputDir, "cms-seed", "README.md");
|
|
1054
|
+
const content = `# CMS Seed Data
|
|
1055
|
+
|
|
1056
|
+
Auto-extracted content from your Webflow export, ready to seed into Strapi.
|
|
1057
|
+
|
|
1058
|
+
## What's in this folder?
|
|
1059
|
+
|
|
1060
|
+
\`seed-data.json\` contains the actual content extracted from your HTML:
|
|
1061
|
+
- **Single types** - Page-specific content (homepage, about page, etc.)
|
|
1062
|
+
- **Collection types** - Repeating items (portfolio cards, team members, etc.)
|
|
1063
|
+
|
|
1064
|
+
## Structure
|
|
1065
|
+
|
|
1066
|
+
\`\`\`json
|
|
1067
|
+
{
|
|
1068
|
+
"index": {
|
|
1069
|
+
"hero_heading_container": "Actual heading from HTML",
|
|
1070
|
+
"hero_bg_image": "/images/hero.jpg",
|
|
1071
|
+
...
|
|
1072
|
+
},
|
|
1073
|
+
"portfolio_cards": [
|
|
1074
|
+
{
|
|
1075
|
+
"image": "/images/card1.jpg",
|
|
1076
|
+
"tag": "Technology",
|
|
1077
|
+
"description": "Card description"
|
|
1078
|
+
}
|
|
1079
|
+
]
|
|
1080
|
+
}
|
|
1081
|
+
\`\`\`
|
|
1082
|
+
|
|
1083
|
+
## How to Seed Strapi
|
|
1084
|
+
|
|
1085
|
+
### Option 1: Manual Entry
|
|
1086
|
+
1. Open Strapi admin panel
|
|
1087
|
+
2. Go to Content Manager
|
|
1088
|
+
3. Create entries using the data from \`seed-data.json\`
|
|
1089
|
+
|
|
1090
|
+
### Option 2: Automated Seeding (Coming Soon)
|
|
1091
|
+
We'll provide a seeding script that:
|
|
1092
|
+
1. Uploads images to Strapi media library
|
|
1093
|
+
2. Creates content entries via Strapi API
|
|
1094
|
+
3. Handles relationships between content types
|
|
1095
|
+
|
|
1096
|
+
## Image Paths
|
|
1097
|
+
|
|
1098
|
+
Image paths in the seed data reference files in your Nuxt \`public/\` directory:
|
|
1099
|
+
- \`/images/hero.jpg\` \u2192 \`public/images/hero.jpg\`
|
|
1100
|
+
|
|
1101
|
+
When seeding Strapi, these images will be uploaded to Strapi's media library.
|
|
1102
|
+
|
|
1103
|
+
## Next Steps
|
|
1104
|
+
|
|
1105
|
+
1. Review the extracted data for accuracy
|
|
1106
|
+
2. Set up your Strapi instance with the schemas from \`cms-schemas/\`
|
|
1107
|
+
3. Use this seed data to populate your CMS
|
|
1108
|
+
`;
|
|
1109
|
+
await fs8.writeFile(readmePath, content, "utf-8");
|
|
1110
|
+
}
|
|
1111
|
+
|
|
901
1112
|
// src/converter.ts
|
|
902
1113
|
async function convertWebflowExport(options) {
|
|
903
1114
|
const { inputDir, outputDir, boilerplate } = options;
|
|
@@ -906,7 +1117,7 @@ async function convertWebflowExport(options) {
|
|
|
906
1117
|
console.log(pc3.dim(`Output: ${outputDir}`));
|
|
907
1118
|
try {
|
|
908
1119
|
await setupBoilerplate(boilerplate, outputDir);
|
|
909
|
-
const inputExists = await
|
|
1120
|
+
const inputExists = await fs9.pathExists(inputDir);
|
|
910
1121
|
if (!inputExists) {
|
|
911
1122
|
throw new Error(`Input directory not found: ${inputDir}`);
|
|
912
1123
|
}
|
|
@@ -922,10 +1133,17 @@ async function convertWebflowExport(options) {
|
|
|
922
1133
|
console.log(pc3.blue("\n\u{1F50D} Finding HTML files..."));
|
|
923
1134
|
const htmlFiles = await findHTMLFiles(inputDir);
|
|
924
1135
|
console.log(pc3.green(` \u2713 Found ${htmlFiles.length} HTML files`));
|
|
1136
|
+
const htmlContentMap = /* @__PURE__ */ new Map();
|
|
1137
|
+
for (const htmlFile of htmlFiles) {
|
|
1138
|
+
const html = await readHTMLFile(inputDir, htmlFile);
|
|
1139
|
+
const pageName = htmlFile.replace(".html", "").replace(/\//g, "-");
|
|
1140
|
+
htmlContentMap.set(pageName, html);
|
|
1141
|
+
console.log(pc3.dim(` Stored: ${pageName} from ${htmlFile}`));
|
|
1142
|
+
}
|
|
925
1143
|
console.log(pc3.blue("\n\u2699\uFE0F Converting HTML to Vue components..."));
|
|
926
1144
|
let allEmbeddedStyles = "";
|
|
927
1145
|
for (const htmlFile of htmlFiles) {
|
|
928
|
-
const html =
|
|
1146
|
+
const html = htmlContentMap.get(htmlFile.replace(".html", "").replace(/\//g, "-"));
|
|
929
1147
|
const parsed = parseHTML(html, htmlFile);
|
|
930
1148
|
if (parsed.embeddedStyles) {
|
|
931
1149
|
allEmbeddedStyles += `
|
|
@@ -941,7 +1159,7 @@ ${parsed.embeddedStyles}
|
|
|
941
1159
|
}
|
|
942
1160
|
await formatVueFiles(outputDir);
|
|
943
1161
|
console.log(pc3.blue("\n\u{1F50D} Analyzing pages for CMS fields..."));
|
|
944
|
-
const pagesDir =
|
|
1162
|
+
const pagesDir = path11.join(outputDir, "pages");
|
|
945
1163
|
const manifest = await generateManifest(pagesDir);
|
|
946
1164
|
await writeManifest(outputDir, manifest);
|
|
947
1165
|
const totalFields = Object.values(manifest.pages).reduce(
|
|
@@ -955,12 +1173,26 @@ ${parsed.embeddedStyles}
|
|
|
955
1173
|
console.log(pc3.green(` \u2713 Detected ${totalFields} fields across ${Object.keys(manifest.pages).length} pages`));
|
|
956
1174
|
console.log(pc3.green(` \u2713 Detected ${totalCollections} collections`));
|
|
957
1175
|
console.log(pc3.green(" \u2713 Generated cms-manifest.json"));
|
|
1176
|
+
console.log(pc3.blue("\n\u{1F4DD} Extracting content from HTML..."));
|
|
1177
|
+
console.log(pc3.dim(` HTML map has ${htmlContentMap.size} entries`));
|
|
1178
|
+
console.log(pc3.dim(` Manifest has ${Object.keys(manifest.pages).length} pages`));
|
|
1179
|
+
const extractedContent = extractAllContent(htmlContentMap, manifest);
|
|
1180
|
+
const seedData = formatForStrapi(extractedContent);
|
|
1181
|
+
await writeSeedData(outputDir, seedData);
|
|
1182
|
+
await createSeedReadme(outputDir);
|
|
1183
|
+
const pagesWithContent = Object.keys(seedData).filter((key) => {
|
|
1184
|
+
const data = seedData[key];
|
|
1185
|
+
if (Array.isArray(data)) return data.length > 0;
|
|
1186
|
+
return Object.keys(data).length > 0;
|
|
1187
|
+
}).length;
|
|
1188
|
+
console.log(pc3.green(` \u2713 Extracted content from ${pagesWithContent} pages`));
|
|
1189
|
+
console.log(pc3.green(` \u2713 Generated cms-seed/seed-data.json`));
|
|
958
1190
|
console.log(pc3.blue("\n\u{1F4CB} Generating Strapi schemas..."));
|
|
959
1191
|
const schemas = manifestToSchemas(manifest);
|
|
960
1192
|
await writeAllSchemas(outputDir, schemas);
|
|
961
1193
|
await createStrapiReadme(outputDir);
|
|
962
1194
|
console.log(pc3.green(` \u2713 Generated ${Object.keys(schemas).length} Strapi content types`));
|
|
963
|
-
console.log(pc3.dim(" View schemas in:
|
|
1195
|
+
console.log(pc3.dim(" View schemas in: cms-schemas/"));
|
|
964
1196
|
if (allEmbeddedStyles.trim()) {
|
|
965
1197
|
console.log(pc3.blue("\n\u2728 Writing embedded styles..."));
|
|
966
1198
|
const dedupedStyles = deduplicateStyles(allEmbeddedStyles);
|
|
@@ -975,7 +1207,7 @@ ${parsed.embeddedStyles}
|
|
|
975
1207
|
await updateNuxtConfig(outputDir, assets.css);
|
|
976
1208
|
console.log(pc3.green(" \u2713 Config updated"));
|
|
977
1209
|
} catch (error) {
|
|
978
|
-
console.log(pc3.yellow(" \u26A0
|
|
1210
|
+
console.log(pc3.yellow(" \u26A0 Could not update nuxt.config.ts automatically"));
|
|
979
1211
|
console.log(pc3.dim(" Please add CSS files manually"));
|
|
980
1212
|
}
|
|
981
1213
|
console.log(pc3.blue("\n\u{1F3A8} Setting up editor overlay..."));
|
|
@@ -988,10 +1220,11 @@ ${parsed.embeddedStyles}
|
|
|
988
1220
|
console.log(pc3.green("\n\u2705 Conversion completed successfully!"));
|
|
989
1221
|
console.log(pc3.cyan("\n\u{1F4CB} Next steps:"));
|
|
990
1222
|
console.log(pc3.dim(` 1. cd ${outputDir}`));
|
|
991
|
-
console.log(pc3.dim(" 2. Review cms-manifest.json"));
|
|
992
|
-
console.log(pc3.dim(" 3.
|
|
993
|
-
console.log(pc3.dim(" 4.
|
|
994
|
-
console.log(pc3.dim(" 5.
|
|
1223
|
+
console.log(pc3.dim(" 2. Review cms-manifest.json and cms-seed/seed-data.json"));
|
|
1224
|
+
console.log(pc3.dim(" 3. Set up Strapi and install schemas from cms-schemas/"));
|
|
1225
|
+
console.log(pc3.dim(" 4. Seed Strapi with data from cms-seed/"));
|
|
1226
|
+
console.log(pc3.dim(" 5. pnpm install && pnpm dev"));
|
|
1227
|
+
console.log(pc3.dim(" 6. Visit http://localhost:3000?preview=true to edit inline!"));
|
|
995
1228
|
} catch (error) {
|
|
996
1229
|
console.error(pc3.red("\n\u274C Conversion failed:"));
|
|
997
1230
|
console.error(pc3.red(error instanceof Error ? error.message : String(error)));
|