@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/dist/cli.mjs CHANGED
@@ -3,11 +3,14 @@
3
3
  // src/cli.ts
4
4
  import { Command } from "commander";
5
5
  import pc4 from "picocolors";
6
+ import * as readline2 from "readline";
7
+ import fs11 from "fs-extra";
8
+ import path13 from "path";
6
9
 
7
10
  // src/converter.ts
8
11
  import pc3 from "picocolors";
9
- import path9 from "path";
10
- import fs8 from "fs-extra";
12
+ import path11 from "path";
13
+ import fs9 from "fs-extra";
11
14
 
12
15
  // src/filesystem.ts
13
16
  import fs from "fs-extra";
@@ -478,13 +481,20 @@ import * as cheerio2 from "cheerio";
478
481
  import fs5 from "fs-extra";
479
482
  import path6 from "path";
480
483
  function cleanClassName(className) {
481
- return className.split(" ").filter((cls) => !cls.startsWith("c-") && !cls.startsWith("w-")).filter((cls) => cls.length > 0).map((cls) => cls.replace(/-/g, "_")).join(" ");
484
+ return className.split(" ").filter((cls) => !cls.startsWith("c-") && !cls.startsWith("w-")).filter((cls) => cls.length > 0).join(" ");
482
485
  }
483
486
  function getPrimaryClass(classAttr) {
484
487
  if (!classAttr) return null;
485
488
  const cleaned = cleanClassName(classAttr);
486
489
  const classes = cleaned.split(" ").filter((c) => c.length > 0);
487
- return classes[0] || null;
490
+ if (classes.length === 0) return null;
491
+ const original = classes[0];
492
+ return {
493
+ selector: original,
494
+ // Keep original with dashes for CSS selector
495
+ fieldName: original.replace(/-/g, "_")
496
+ // Normalize for field name
497
+ };
488
498
  }
489
499
  function getContextModifier(_$, $el) {
490
500
  let $current = $el.parent();
@@ -541,11 +551,11 @@ function detectEditableFields(templateHtml) {
541
551
  const potentialCollections = /* @__PURE__ */ new Map();
542
552
  $("[class]").each((_, el) => {
543
553
  const primaryClass = getPrimaryClass($(el).attr("class"));
544
- if (primaryClass && (primaryClass.includes("card") || primaryClass.includes("item") || primaryClass.includes("post") || primaryClass.includes("feature")) && !primaryClass.includes("image") && !primaryClass.includes("inner")) {
545
- if (!potentialCollections.has(primaryClass)) {
546
- potentialCollections.set(primaryClass, []);
554
+ 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")) {
555
+ if (!potentialCollections.has(primaryClass.fieldName)) {
556
+ potentialCollections.set(primaryClass.fieldName, []);
547
557
  }
548
- potentialCollections.get(primaryClass)?.push(el);
558
+ potentialCollections.get(primaryClass.fieldName)?.push(el);
549
559
  }
550
560
  });
551
561
  potentialCollections.forEach((elements, className) => {
@@ -559,40 +569,43 @@ function detectEditableFields(templateHtml) {
559
569
  collectionElements.add(child);
560
570
  });
561
571
  });
572
+ const collectionClassInfo = getPrimaryClass($(elements[0]).attr("class"));
573
+ const collectionSelector = collectionClassInfo ? `.${collectionClassInfo.selector}` : `.${className}`;
562
574
  $first.find("img").each((_, img) => {
563
575
  if (isInsideButton($, img)) return;
564
576
  const $img = $(img);
565
577
  const $parent = $img.parent();
566
- const parentClass = getPrimaryClass($parent.attr("class"));
567
- if (parentClass && parentClass.includes("image")) {
568
- collectionFields.image = `.${parentClass}`;
578
+ const parentClassInfo = getPrimaryClass($parent.attr("class"));
579
+ if (parentClassInfo && parentClassInfo.fieldName.includes("image")) {
580
+ collectionFields.image = `.${parentClassInfo.selector}`;
569
581
  return false;
570
582
  }
571
583
  });
572
584
  $first.find("div").each((_, el) => {
573
- const primaryClass = getPrimaryClass($(el).attr("class"));
574
- if (primaryClass && primaryClass.includes("tag") && !primaryClass.includes("container")) {
575
- collectionFields.tag = `.${primaryClass}`;
585
+ const classInfo = getPrimaryClass($(el).attr("class"));
586
+ if (classInfo && classInfo.fieldName.includes("tag") && !classInfo.fieldName.includes("container")) {
587
+ collectionFields.tag = `.${classInfo.selector}`;
576
588
  return false;
577
589
  }
578
590
  });
579
591
  $first.find("h1, h2, h3, h4, h5, h6").first().each((_, el) => {
580
- const primaryClass = getPrimaryClass($(el).attr("class"));
581
- if (primaryClass) {
582
- collectionFields.title = `.${primaryClass}`;
592
+ const classInfo = getPrimaryClass($(el).attr("class"));
593
+ if (classInfo) {
594
+ collectionFields.title = `.${classInfo.selector}`;
583
595
  }
584
596
  });
585
597
  $first.find("p").first().each((_, el) => {
586
- const primaryClass = getPrimaryClass($(el).attr("class"));
587
- if (primaryClass) {
588
- collectionFields.description = `.${primaryClass}`;
598
+ const classInfo = getPrimaryClass($(el).attr("class"));
599
+ if (classInfo) {
600
+ collectionFields.description = `.${classInfo.selector}`;
589
601
  }
590
602
  });
591
603
  $first.find("a, NuxtLink").not(".c_button, .c_icon_button").each((_, el) => {
592
604
  const $link = $(el);
593
605
  const linkText = $link.text().trim();
594
606
  if (linkText) {
595
- collectionFields.link = `.${getPrimaryClass($link.attr("class")) || "a"}`;
607
+ const classInfo = getPrimaryClass($link.attr("class"));
608
+ collectionFields.link = classInfo ? `.${classInfo.selector}` : "a";
596
609
  return false;
597
610
  }
598
611
  });
@@ -602,7 +615,7 @@ function detectEditableFields(templateHtml) {
602
615
  collectionName += "s";
603
616
  }
604
617
  detectedCollections[collectionName] = {
605
- selector: `.${className}`,
618
+ selector: collectionSelector,
606
619
  fields: collectionFields
607
620
  };
608
621
  }
@@ -613,25 +626,30 @@ function detectEditableFields(templateHtml) {
613
626
  if (collectionElements.has(el)) return;
614
627
  const $el = $(el);
615
628
  const text = $el.text().trim();
616
- const primaryClass = getPrimaryClass($el.attr("class"));
629
+ const classInfo = getPrimaryClass($el.attr("class"));
617
630
  if (text) {
618
631
  let fieldName;
619
- if (primaryClass && !primaryClass.startsWith("heading_")) {
620
- fieldName = primaryClass;
632
+ let selector;
633
+ if (classInfo && !classInfo.fieldName.startsWith("heading_")) {
634
+ fieldName = classInfo.fieldName;
635
+ selector = `.${classInfo.selector}`;
621
636
  } else {
622
637
  const $parent = $el.closest('[class*="header"], [class*="hero"], [class*="cta"]').first();
623
- const parentClass = getPrimaryClass($parent.attr("class"));
638
+ const parentClassInfo = getPrimaryClass($parent.attr("class"));
624
639
  const modifier = getContextModifier($, $el);
625
- if (parentClass) {
626
- fieldName = modifier ? `${modifier}_${parentClass}` : parentClass;
640
+ if (parentClassInfo) {
641
+ fieldName = modifier ? `${modifier}_${parentClassInfo.fieldName}` : parentClassInfo.fieldName;
642
+ selector = classInfo ? `.${classInfo.selector}` : `.${parentClassInfo.selector}`;
627
643
  } else if (modifier) {
628
644
  fieldName = `${modifier}_heading`;
645
+ selector = classInfo ? `.${classInfo.selector}` : el.tagName.toLowerCase();
629
646
  } else {
630
647
  fieldName = `heading_${index}`;
648
+ selector = classInfo ? `.${classInfo.selector}` : el.tagName.toLowerCase();
631
649
  }
632
650
  }
633
651
  detectedFields[fieldName] = {
634
- selector: primaryClass ? `.${primaryClass}` : el.tagName.toLowerCase(),
652
+ selector,
635
653
  type: "plain",
636
654
  editable: true
637
655
  };
@@ -641,11 +659,11 @@ function detectEditableFields(templateHtml) {
641
659
  if (collectionElements.has(el)) return;
642
660
  const $el = $(el);
643
661
  const text = $el.text().trim();
644
- const primaryClass = getPrimaryClass($el.attr("class"));
645
- if (text && text.length > 20 && primaryClass) {
662
+ const classInfo = getPrimaryClass($el.attr("class"));
663
+ if (text && text.length > 20 && classInfo) {
646
664
  const hasFormatting = $el.find("strong, em, b, i, a, NuxtLink").length > 0;
647
- detectedFields[primaryClass] = {
648
- selector: `.${primaryClass}`,
665
+ detectedFields[classInfo.fieldName] = {
666
+ selector: `.${classInfo.selector}`,
649
667
  type: hasFormatting ? "rich" : "plain",
650
668
  editable: true
651
669
  };
@@ -657,11 +675,11 @@ function detectEditableFields(templateHtml) {
657
675
  const $el = $(el);
658
676
  if (isDecorativeImage($, $el)) return;
659
677
  const $parent = $el.parent();
660
- const parentClass = getPrimaryClass($parent.attr("class"));
661
- if (parentClass) {
662
- const fieldName = parentClass.includes("image") ? parentClass : `${parentClass}_image`;
678
+ const parentClassInfo = getPrimaryClass($parent.attr("class"));
679
+ if (parentClassInfo) {
680
+ const fieldName = parentClassInfo.fieldName.includes("image") ? parentClassInfo.fieldName : `${parentClassInfo.fieldName}_image`;
663
681
  detectedFields[fieldName] = {
664
- selector: `.${parentClass}`,
682
+ selector: `.${parentClassInfo.selector}`,
665
683
  type: "image",
666
684
  editable: true
667
685
  };
@@ -675,8 +693,9 @@ function detectEditableFields(templateHtml) {
675
693
  }).first().text().trim();
676
694
  if (text && text.length > 2) {
677
695
  const $parent = $el.closest('[class*="cta"]').first();
678
- const parentClass = getPrimaryClass($parent.attr("class")) || "button";
679
- detectedFields[`${parentClass}_button_text`] = {
696
+ const parentClassInfo = getPrimaryClass($parent.attr("class"));
697
+ const fieldName = parentClassInfo ? `${parentClassInfo.fieldName}_button_text` : "button_text";
698
+ detectedFields[fieldName] = {
680
699
  selector: `.c_button`,
681
700
  type: "plain",
682
701
  editable: true
@@ -742,6 +761,18 @@ function mapFieldTypeToStrapi(fieldType) {
742
761
  };
743
762
  return typeMap[fieldType] || "string";
744
763
  }
764
+ function pluralize(word) {
765
+ if (word.endsWith("s") || word.endsWith("x") || word.endsWith("z") || word.endsWith("ch") || word.endsWith("sh")) {
766
+ return word + "es";
767
+ }
768
+ if (word.endsWith("y") && word.length > 1) {
769
+ const secondLast = word[word.length - 2];
770
+ if (!"aeiou".includes(secondLast.toLowerCase())) {
771
+ return word.slice(0, -1) + "ies";
772
+ }
773
+ }
774
+ return word + "s";
775
+ }
745
776
  function pageToStrapiSchema(pageName, fields) {
746
777
  const attributes = {};
747
778
  for (const [fieldName, field] of Object.entries(fields)) {
@@ -754,12 +785,14 @@ function pageToStrapiSchema(pageName, fields) {
754
785
  }
755
786
  }
756
787
  const displayName = pageName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
788
+ const kebabCaseName = pageName;
789
+ const pluralName = pluralize(kebabCaseName);
757
790
  return {
758
791
  kind: "singleType",
759
- collectionName: pageName.replace(/-/g, "_"),
792
+ collectionName: kebabCaseName,
760
793
  info: {
761
- singularName: pageName.replace(/-/g, "_"),
762
- pluralName: pageName.replace(/-/g, "_"),
794
+ singularName: kebabCaseName,
795
+ pluralName,
763
796
  displayName
764
797
  },
765
798
  options: {
@@ -786,13 +819,14 @@ function collectionToStrapiSchema(collectionName, collection) {
786
819
  };
787
820
  }
788
821
  const displayName = collectionName.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
789
- const singularName = collectionName.endsWith("s") ? collectionName.slice(0, -1) : collectionName;
822
+ const kebabCaseName = collectionName.replace(/_/g, "-");
823
+ const singularName = kebabCaseName.endsWith("s") ? kebabCaseName.slice(0, -1) : kebabCaseName;
790
824
  return {
791
825
  kind: "collectionType",
792
- collectionName: collectionName.replace(/[-_]/g, "_"),
826
+ collectionName: kebabCaseName,
793
827
  info: {
794
- singularName: singularName.replace(/[-_]/g, "_"),
795
- pluralName: collectionName.replace(/[-_]/g, "_"),
828
+ singularName,
829
+ pluralName: kebabCaseName,
796
830
  displayName
797
831
  },
798
832
  options: {
@@ -904,6 +938,186 @@ const { data } = await $fetch('http://localhost:1337/api/portfolio-cards')
904
938
  await fs7.writeFile(readmePath, content, "utf-8");
905
939
  }
906
940
 
941
+ // src/content-extractor.ts
942
+ import * as cheerio3 from "cheerio";
943
+ import path9 from "path";
944
+ function extractContentFromHTML(html, _pageName, pageManifest) {
945
+ const $ = cheerio3.load(html);
946
+ const content = {
947
+ fields: {},
948
+ collections: {}
949
+ };
950
+ if (pageManifest.fields) {
951
+ for (const [fieldName, field] of Object.entries(pageManifest.fields)) {
952
+ const selector = field.selector;
953
+ const element = $(selector).first();
954
+ if (element.length > 0) {
955
+ if (field.type === "image") {
956
+ const src = element.attr("src") || element.find("img").attr("src") || "";
957
+ content.fields[fieldName] = src;
958
+ } else {
959
+ const text = element.text().trim();
960
+ content.fields[fieldName] = text;
961
+ }
962
+ }
963
+ }
964
+ }
965
+ if (pageManifest.collections) {
966
+ for (const [collectionName, collection] of Object.entries(pageManifest.collections)) {
967
+ const items = [];
968
+ const collectionElements = $(collection.selector);
969
+ collectionElements.each((_, elem) => {
970
+ const item = {};
971
+ const $elem = $(elem);
972
+ for (const [fieldName, fieldSelector] of Object.entries(collection.fields)) {
973
+ const fieldElement = $elem.find(fieldSelector).first();
974
+ if (fieldElement.length > 0) {
975
+ if (fieldName === "image" || fieldName.includes("image")) {
976
+ const src = fieldElement.attr("src") || fieldElement.find("img").attr("src") || "";
977
+ item[fieldName] = src;
978
+ } else if (fieldName === "link" || fieldName === "url") {
979
+ const href = fieldElement.attr("href") || "";
980
+ item[fieldName] = href;
981
+ } else {
982
+ const text = fieldElement.text().trim();
983
+ item[fieldName] = text;
984
+ }
985
+ }
986
+ }
987
+ if (Object.keys(item).length > 0) {
988
+ items.push(item);
989
+ }
990
+ });
991
+ if (items.length > 0) {
992
+ content.collections[collectionName] = items;
993
+ }
994
+ }
995
+ }
996
+ return content;
997
+ }
998
+ function extractAllContent(htmlFiles, manifest) {
999
+ const extractedContent = {
1000
+ pages: {}
1001
+ };
1002
+ for (const [pageName, pageManifest] of Object.entries(manifest.pages)) {
1003
+ const html = htmlFiles.get(pageName);
1004
+ if (html) {
1005
+ const content = extractContentFromHTML(html, pageName, pageManifest);
1006
+ extractedContent.pages[pageName] = content;
1007
+ }
1008
+ }
1009
+ return extractedContent;
1010
+ }
1011
+ function normalizeImagePath(imageSrc) {
1012
+ if (!imageSrc) return "";
1013
+ if (imageSrc.startsWith("/")) return imageSrc;
1014
+ const filename = path9.basename(imageSrc);
1015
+ if (imageSrc.includes("images/")) {
1016
+ return `/images/${filename}`;
1017
+ }
1018
+ return `/${filename}`;
1019
+ }
1020
+ function formatForStrapi(extracted) {
1021
+ const seedData = {};
1022
+ for (const [pageName, content] of Object.entries(extracted.pages)) {
1023
+ if (Object.keys(content.fields).length > 0) {
1024
+ const formattedFields = {};
1025
+ for (const [fieldName, value] of Object.entries(content.fields)) {
1026
+ if (fieldName.includes("image") || fieldName.includes("bg")) {
1027
+ formattedFields[fieldName] = normalizeImagePath(value);
1028
+ } else {
1029
+ formattedFields[fieldName] = value;
1030
+ }
1031
+ }
1032
+ seedData[pageName] = formattedFields;
1033
+ }
1034
+ for (const [collectionName, items] of Object.entries(content.collections)) {
1035
+ const formattedItems = items.map((item) => {
1036
+ const formattedItem = {};
1037
+ for (const [fieldName, value] of Object.entries(item)) {
1038
+ if (fieldName === "image" || fieldName.includes("image")) {
1039
+ formattedItem[fieldName] = normalizeImagePath(value);
1040
+ } else {
1041
+ formattedItem[fieldName] = value;
1042
+ }
1043
+ }
1044
+ return formattedItem;
1045
+ });
1046
+ seedData[collectionName] = formattedItems;
1047
+ }
1048
+ }
1049
+ return seedData;
1050
+ }
1051
+
1052
+ // src/seed-writer.ts
1053
+ import fs8 from "fs-extra";
1054
+ import path10 from "path";
1055
+ async function writeSeedData(outputDir, seedData) {
1056
+ const seedDir = path10.join(outputDir, "cms-seed");
1057
+ await fs8.ensureDir(seedDir);
1058
+ const seedPath = path10.join(seedDir, "seed-data.json");
1059
+ await fs8.writeJson(seedPath, seedData, { spaces: 2 });
1060
+ }
1061
+ async function createSeedReadme(outputDir) {
1062
+ const readmePath = path10.join(outputDir, "cms-seed", "README.md");
1063
+ const content = `# CMS Seed Data
1064
+
1065
+ Auto-extracted content from your Webflow export, ready to seed into Strapi.
1066
+
1067
+ ## What's in this folder?
1068
+
1069
+ \`seed-data.json\` contains the actual content extracted from your HTML:
1070
+ - **Single types** - Page-specific content (homepage, about page, etc.)
1071
+ - **Collection types** - Repeating items (portfolio cards, team members, etc.)
1072
+
1073
+ ## Structure
1074
+
1075
+ \`\`\`json
1076
+ {
1077
+ "index": {
1078
+ "hero_heading_container": "Actual heading from HTML",
1079
+ "hero_bg_image": "/images/hero.jpg",
1080
+ ...
1081
+ },
1082
+ "portfolio_cards": [
1083
+ {
1084
+ "image": "/images/card1.jpg",
1085
+ "tag": "Technology",
1086
+ "description": "Card description"
1087
+ }
1088
+ ]
1089
+ }
1090
+ \`\`\`
1091
+
1092
+ ## How to Seed Strapi
1093
+
1094
+ ### Option 1: Manual Entry
1095
+ 1. Open Strapi admin panel
1096
+ 2. Go to Content Manager
1097
+ 3. Create entries using the data from \`seed-data.json\`
1098
+
1099
+ ### Option 2: Automated Seeding (Coming Soon)
1100
+ We'll provide a seeding script that:
1101
+ 1. Uploads images to Strapi media library
1102
+ 2. Creates content entries via Strapi API
1103
+ 3. Handles relationships between content types
1104
+
1105
+ ## Image Paths
1106
+
1107
+ Image paths in the seed data reference files in your Nuxt \`public/\` directory:
1108
+ - \`/images/hero.jpg\` \u2192 \`public/images/hero.jpg\`
1109
+
1110
+ When seeding Strapi, these images will be uploaded to Strapi's media library.
1111
+
1112
+ ## Next Steps
1113
+
1114
+ 1. Review the extracted data for accuracy
1115
+ 2. Set up your Strapi instance with the schemas from \`cms-schemas/\`
1116
+ 3. Use this seed data to populate your CMS
1117
+ `;
1118
+ await fs8.writeFile(readmePath, content, "utf-8");
1119
+ }
1120
+
907
1121
  // src/converter.ts
908
1122
  async function convertWebflowExport(options) {
909
1123
  const { inputDir, outputDir, boilerplate } = options;
@@ -912,7 +1126,7 @@ async function convertWebflowExport(options) {
912
1126
  console.log(pc3.dim(`Output: ${outputDir}`));
913
1127
  try {
914
1128
  await setupBoilerplate(boilerplate, outputDir);
915
- const inputExists = await fs8.pathExists(inputDir);
1129
+ const inputExists = await fs9.pathExists(inputDir);
916
1130
  if (!inputExists) {
917
1131
  throw new Error(`Input directory not found: ${inputDir}`);
918
1132
  }
@@ -928,10 +1142,17 @@ async function convertWebflowExport(options) {
928
1142
  console.log(pc3.blue("\n\u{1F50D} Finding HTML files..."));
929
1143
  const htmlFiles = await findHTMLFiles(inputDir);
930
1144
  console.log(pc3.green(` \u2713 Found ${htmlFiles.length} HTML files`));
1145
+ const htmlContentMap = /* @__PURE__ */ new Map();
1146
+ for (const htmlFile of htmlFiles) {
1147
+ const html = await readHTMLFile(inputDir, htmlFile);
1148
+ const pageName = htmlFile.replace(".html", "").replace(/\//g, "-");
1149
+ htmlContentMap.set(pageName, html);
1150
+ console.log(pc3.dim(` Stored: ${pageName} from ${htmlFile}`));
1151
+ }
931
1152
  console.log(pc3.blue("\n\u2699\uFE0F Converting HTML to Vue components..."));
932
1153
  let allEmbeddedStyles = "";
933
1154
  for (const htmlFile of htmlFiles) {
934
- const html = await readHTMLFile(inputDir, htmlFile);
1155
+ const html = htmlContentMap.get(htmlFile.replace(".html", "").replace(/\//g, "-"));
935
1156
  const parsed = parseHTML(html, htmlFile);
936
1157
  if (parsed.embeddedStyles) {
937
1158
  allEmbeddedStyles += `
@@ -947,7 +1168,7 @@ ${parsed.embeddedStyles}
947
1168
  }
948
1169
  await formatVueFiles(outputDir);
949
1170
  console.log(pc3.blue("\n\u{1F50D} Analyzing pages for CMS fields..."));
950
- const pagesDir = path9.join(outputDir, "pages");
1171
+ const pagesDir = path11.join(outputDir, "pages");
951
1172
  const manifest = await generateManifest(pagesDir);
952
1173
  await writeManifest(outputDir, manifest);
953
1174
  const totalFields = Object.values(manifest.pages).reduce(
@@ -961,12 +1182,26 @@ ${parsed.embeddedStyles}
961
1182
  console.log(pc3.green(` \u2713 Detected ${totalFields} fields across ${Object.keys(manifest.pages).length} pages`));
962
1183
  console.log(pc3.green(` \u2713 Detected ${totalCollections} collections`));
963
1184
  console.log(pc3.green(" \u2713 Generated cms-manifest.json"));
1185
+ console.log(pc3.blue("\n\u{1F4DD} Extracting content from HTML..."));
1186
+ console.log(pc3.dim(` HTML map has ${htmlContentMap.size} entries`));
1187
+ console.log(pc3.dim(` Manifest has ${Object.keys(manifest.pages).length} pages`));
1188
+ const extractedContent = extractAllContent(htmlContentMap, manifest);
1189
+ const seedData = formatForStrapi(extractedContent);
1190
+ await writeSeedData(outputDir, seedData);
1191
+ await createSeedReadme(outputDir);
1192
+ const pagesWithContent = Object.keys(seedData).filter((key) => {
1193
+ const data = seedData[key];
1194
+ if (Array.isArray(data)) return data.length > 0;
1195
+ return Object.keys(data).length > 0;
1196
+ }).length;
1197
+ console.log(pc3.green(` \u2713 Extracted content from ${pagesWithContent} pages`));
1198
+ console.log(pc3.green(` \u2713 Generated cms-seed/seed-data.json`));
964
1199
  console.log(pc3.blue("\n\u{1F4CB} Generating Strapi schemas..."));
965
1200
  const schemas = manifestToSchemas(manifest);
966
1201
  await writeAllSchemas(outputDir, schemas);
967
1202
  await createStrapiReadme(outputDir);
968
1203
  console.log(pc3.green(` \u2713 Generated ${Object.keys(schemas).length} Strapi content types`));
969
- console.log(pc3.dim(" View schemas in: strapi/src/api/"));
1204
+ console.log(pc3.dim(" View schemas in: cms-schemas/"));
970
1205
  if (allEmbeddedStyles.trim()) {
971
1206
  console.log(pc3.blue("\n\u2728 Writing embedded styles..."));
972
1207
  const dedupedStyles = deduplicateStyles(allEmbeddedStyles);
@@ -981,7 +1216,7 @@ ${parsed.embeddedStyles}
981
1216
  await updateNuxtConfig(outputDir, assets.css);
982
1217
  console.log(pc3.green(" \u2713 Config updated"));
983
1218
  } catch (error) {
984
- console.log(pc3.yellow(" \u26A0 Could not update nuxt.config.ts automatically"));
1219
+ console.log(pc3.yellow(" \u26A0 Could not update nuxt.config.ts automatically"));
985
1220
  console.log(pc3.dim(" Please add CSS files manually"));
986
1221
  }
987
1222
  console.log(pc3.blue("\n\u{1F3A8} Setting up editor overlay..."));
@@ -994,10 +1229,11 @@ ${parsed.embeddedStyles}
994
1229
  console.log(pc3.green("\n\u2705 Conversion completed successfully!"));
995
1230
  console.log(pc3.cyan("\n\u{1F4CB} Next steps:"));
996
1231
  console.log(pc3.dim(` 1. cd ${outputDir}`));
997
- console.log(pc3.dim(" 2. Review cms-manifest.json"));
998
- console.log(pc3.dim(" 3. Copy strapi/ schemas to your Strapi project"));
999
- console.log(pc3.dim(" 4. pnpm install && pnpm dev"));
1000
- console.log(pc3.dim(" 5. Visit http://localhost:3000?preview=true to edit inline!"));
1232
+ console.log(pc3.dim(" 2. Review cms-manifest.json and cms-seed/seed-data.json"));
1233
+ console.log(pc3.dim(" 3. Set up Strapi and install schemas from cms-schemas/"));
1234
+ console.log(pc3.dim(" 4. Seed Strapi with data from cms-seed/"));
1235
+ console.log(pc3.dim(" 5. pnpm install && pnpm dev"));
1236
+ console.log(pc3.dim(" 6. Visit http://localhost:3000?preview=true to edit inline!"));
1001
1237
  } catch (error) {
1002
1238
  console.error(pc3.red("\n\u274C Conversion failed:"));
1003
1239
  console.error(pc3.red(error instanceof Error ? error.message : String(error)));
@@ -1005,10 +1241,501 @@ ${parsed.embeddedStyles}
1005
1241
  }
1006
1242
  }
1007
1243
 
1244
+ // src/strapi-setup.ts
1245
+ import fs10 from "fs-extra";
1246
+ import path12 from "path";
1247
+ import { glob as glob2 } from "glob";
1248
+ import * as readline from "readline";
1249
+ var ENV_FILE = ".env";
1250
+ async function loadConfig(projectDir) {
1251
+ const envPath = path12.join(projectDir, ENV_FILE);
1252
+ if (await fs10.pathExists(envPath)) {
1253
+ try {
1254
+ const content = await fs10.readFile(envPath, "utf-8");
1255
+ const config = {};
1256
+ for (const line of content.split("\n")) {
1257
+ const trimmed = line.trim();
1258
+ if (!trimmed || trimmed.startsWith("#")) continue;
1259
+ const [key, ...valueParts] = trimmed.split("=");
1260
+ const value = valueParts.join("=").trim();
1261
+ if (key === "STRAPI_API_TOKEN") {
1262
+ config.apiToken = value;
1263
+ } else if (key === "STRAPI_URL") {
1264
+ config.strapiUrl = value;
1265
+ }
1266
+ }
1267
+ return config;
1268
+ } catch {
1269
+ return {};
1270
+ }
1271
+ }
1272
+ return {};
1273
+ }
1274
+ async function saveConfig(projectDir, config) {
1275
+ const envPath = path12.join(projectDir, ENV_FILE);
1276
+ let content = "";
1277
+ if (await fs10.pathExists(envPath)) {
1278
+ content = await fs10.readFile(envPath, "utf-8");
1279
+ content = content.split("\n").filter((line) => !line.startsWith("STRAPI_API_TOKEN=") && !line.startsWith("STRAPI_URL=")).join("\n");
1280
+ if (content && !content.endsWith("\n")) {
1281
+ content += "\n";
1282
+ }
1283
+ }
1284
+ if (config.strapiUrl) {
1285
+ content += `STRAPI_URL=${config.strapiUrl}
1286
+ `;
1287
+ }
1288
+ if (config.apiToken) {
1289
+ content += `STRAPI_API_TOKEN=${config.apiToken}
1290
+ `;
1291
+ }
1292
+ await fs10.writeFile(envPath, content);
1293
+ }
1294
+ async function completeSetup(options) {
1295
+ const { projectDir, strapiDir, strapiUrl: optionUrl, apiToken: optionToken, ignoreSavedToken } = options;
1296
+ const savedConfig = await loadConfig(projectDir);
1297
+ const strapiUrl = optionUrl || savedConfig.strapiUrl || "http://localhost:1337";
1298
+ console.log("\u{1F680} Starting complete Strapi setup...\n");
1299
+ console.log("\u{1F4E6} Step 1: Installing schemas...");
1300
+ await installSchemas(projectDir, strapiDir);
1301
+ console.log("\u2713 Schemas installed\n");
1302
+ console.log("\u23F8\uFE0F Step 2: Restart Strapi to load schemas");
1303
+ console.log(" Run: npm run develop (in Strapi directory)");
1304
+ console.log(" Press Enter when Strapi is running...");
1305
+ await waitForEnter();
1306
+ console.log("\n\u{1F50D} Step 3: Checking Strapi connection...");
1307
+ const isRunning = await checkStrapiRunning(strapiUrl);
1308
+ if (!isRunning) {
1309
+ console.error("\u274C Cannot connect to Strapi at", strapiUrl);
1310
+ console.log(" Make sure Strapi is running: npm run develop");
1311
+ process.exit(1);
1312
+ }
1313
+ console.log("\u2713 Connected to Strapi\n");
1314
+ let token = optionToken || (!ignoreSavedToken ? savedConfig.apiToken : void 0);
1315
+ if (token && !ignoreSavedToken) {
1316
+ console.log("\u{1F511} Step 4: Using saved API token");
1317
+ } else if (token && optionToken) {
1318
+ console.log("\u{1F511} Step 4: Using provided API token");
1319
+ } else {
1320
+ console.log("\u{1F511} Step 4: API Token needed");
1321
+ console.log(" 1. Open Strapi admin: http://localhost:1337/admin");
1322
+ console.log(" 2. Go to Settings > API Tokens > Create new API Token");
1323
+ console.log(' 3. Name: "Seed Script", Type: "Full access", Duration: "Unlimited"');
1324
+ console.log(" 4. Copy the token and paste it here:\n");
1325
+ token = await promptForToken();
1326
+ const saveToken = await promptYesNo(" Save token for future use?");
1327
+ if (saveToken) {
1328
+ await saveConfig(projectDir, { ...savedConfig, apiToken: token, strapiUrl });
1329
+ console.log(" \u2713 Token saved to .env");
1330
+ }
1331
+ console.log("");
1332
+ }
1333
+ console.log("\u{1F4F8} Step 5: Uploading images...");
1334
+ const mediaMap = await uploadAllImages(projectDir, strapiUrl, token);
1335
+ console.log(`\u2713 Uploaded ${Object.keys(mediaMap).length} images
1336
+ `);
1337
+ console.log("\u{1F4DD} Step 6: Seeding content...");
1338
+ await seedContent(projectDir, strapiUrl, token, mediaMap);
1339
+ console.log("\u2713 Content seeded\n");
1340
+ console.log("\u2705 Complete setup finished!");
1341
+ console.log("\n\u{1F4CB} Next steps:");
1342
+ console.log(" 1. Open Strapi admin: http://localhost:1337/admin");
1343
+ console.log(" 2. Check Content Manager - your content should be there!");
1344
+ console.log(" 3. Connect your Nuxt app to Strapi API");
1345
+ }
1346
+ async function installSchemas(projectDir, strapiDir) {
1347
+ if (!await fs10.pathExists(strapiDir)) {
1348
+ console.error(` \u2717 Strapi directory not found: ${strapiDir}`);
1349
+ console.error(` Resolved to: ${path12.resolve(strapiDir)}`);
1350
+ throw new Error(`Strapi directory not found: ${strapiDir}`);
1351
+ }
1352
+ const packageJsonPath = path12.join(strapiDir, "package.json");
1353
+ if (await fs10.pathExists(packageJsonPath)) {
1354
+ const pkg = await fs10.readJson(packageJsonPath);
1355
+ if (!pkg.dependencies?.["@strapi/strapi"]) {
1356
+ console.warn(` \u26A0\uFE0F Warning: ${strapiDir} may not be a Strapi project`);
1357
+ }
1358
+ }
1359
+ const schemaDir = path12.join(projectDir, "cms-schemas");
1360
+ const schemaFiles = await glob2("*.json", {
1361
+ cwd: schemaDir,
1362
+ absolute: false
1363
+ });
1364
+ if (schemaFiles.length === 0) {
1365
+ console.log("\u26A0\uFE0F No schema files found");
1366
+ return;
1367
+ }
1368
+ console.log(` Found ${schemaFiles.length} schema file(s)`);
1369
+ for (const file of schemaFiles) {
1370
+ const schemaPath = path12.join(schemaDir, file);
1371
+ const schema = await fs10.readJson(schemaPath);
1372
+ const singularName = schema.info?.singularName || path12.basename(file, ".json");
1373
+ console.log(` Installing ${singularName}...`);
1374
+ try {
1375
+ const apiPath = path12.join(strapiDir, "src", "api", singularName);
1376
+ const contentTypesPath = path12.join(
1377
+ apiPath,
1378
+ "content-types",
1379
+ singularName
1380
+ );
1381
+ const targetPath = path12.join(contentTypesPath, "schema.json");
1382
+ await fs10.ensureDir(contentTypesPath);
1383
+ await fs10.ensureDir(path12.join(apiPath, "routes"));
1384
+ await fs10.ensureDir(path12.join(apiPath, "controllers"));
1385
+ await fs10.ensureDir(path12.join(apiPath, "services"));
1386
+ await fs10.writeJson(targetPath, schema, { spaces: 2 });
1387
+ const routeContent = `import { factories } from '@strapi/strapi';
1388
+ export default factories.createCoreRouter('api::${singularName}.${singularName}');
1389
+ `;
1390
+ await fs10.writeFile(
1391
+ path12.join(apiPath, "routes", `${singularName}.ts`),
1392
+ routeContent
1393
+ );
1394
+ const controllerContent = `import { factories } from '@strapi/strapi';
1395
+ export default factories.createCoreController('api::${singularName}.${singularName}');
1396
+ `;
1397
+ await fs10.writeFile(
1398
+ path12.join(apiPath, "controllers", `${singularName}.ts`),
1399
+ controllerContent
1400
+ );
1401
+ const serviceContent = `import { factories } from '@strapi/strapi';
1402
+ export default factories.createCoreService('api::${singularName}.${singularName}');
1403
+ `;
1404
+ await fs10.writeFile(
1405
+ path12.join(apiPath, "services", `${singularName}.ts`),
1406
+ serviceContent
1407
+ );
1408
+ } catch (error) {
1409
+ console.error(` \u2717 Failed to install ${singularName}: ${error.message}`);
1410
+ }
1411
+ }
1412
+ }
1413
+ async function checkStrapiRunning(strapiUrl) {
1414
+ try {
1415
+ const response = await fetch(`${strapiUrl}/_health`);
1416
+ return response.ok;
1417
+ } catch {
1418
+ return false;
1419
+ }
1420
+ }
1421
+ function createReadline() {
1422
+ return readline.createInterface({
1423
+ input: process.stdin,
1424
+ output: process.stdout
1425
+ });
1426
+ }
1427
+ async function waitForEnter() {
1428
+ const rl = createReadline();
1429
+ return new Promise((resolve) => {
1430
+ rl.question("", () => {
1431
+ rl.close();
1432
+ resolve();
1433
+ });
1434
+ });
1435
+ }
1436
+ async function promptForToken() {
1437
+ const rl = createReadline();
1438
+ return new Promise((resolve) => {
1439
+ rl.question(" Token: ", (answer) => {
1440
+ rl.close();
1441
+ resolve(answer.trim());
1442
+ });
1443
+ });
1444
+ }
1445
+ async function promptYesNo(question) {
1446
+ const rl = createReadline();
1447
+ return new Promise((resolve) => {
1448
+ rl.question(`${question} (y/n): `, (answer) => {
1449
+ rl.close();
1450
+ resolve(answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes");
1451
+ });
1452
+ });
1453
+ }
1454
+ async function getExistingMedia(strapiUrl, apiToken) {
1455
+ const existingMedia = /* @__PURE__ */ new Map();
1456
+ try {
1457
+ let page = 1;
1458
+ const pageSize = 100;
1459
+ let hasMore = true;
1460
+ while (hasMore) {
1461
+ const response = await fetch(
1462
+ `${strapiUrl}/api/upload/files?pagination[page]=${page}&pagination[pageSize]=${pageSize}`,
1463
+ {
1464
+ headers: {
1465
+ Authorization: `Bearer ${apiToken}`
1466
+ }
1467
+ }
1468
+ );
1469
+ if (!response.ok) {
1470
+ break;
1471
+ }
1472
+ const data = await response.json();
1473
+ const files = Array.isArray(data) ? data : data.results || [];
1474
+ for (const file of files) {
1475
+ if (file.name) {
1476
+ existingMedia.set(file.name, file.id);
1477
+ }
1478
+ }
1479
+ hasMore = files.length === pageSize;
1480
+ page++;
1481
+ }
1482
+ } catch (error) {
1483
+ }
1484
+ return existingMedia;
1485
+ }
1486
+ async function uploadAllImages(projectDir, strapiUrl, apiToken) {
1487
+ const mediaMap = /* @__PURE__ */ new Map();
1488
+ const imagesDir = path12.join(projectDir, "public", "assets", "images");
1489
+ if (!await fs10.pathExists(imagesDir)) {
1490
+ console.log(" No images directory found");
1491
+ return mediaMap;
1492
+ }
1493
+ const imageFiles = await glob2("**/*.{jpg,jpeg,png,gif,webp,svg}", {
1494
+ cwd: imagesDir,
1495
+ absolute: false
1496
+ });
1497
+ console.log(` Checking for existing media...`);
1498
+ const existingMedia = await getExistingMedia(strapiUrl, apiToken);
1499
+ let uploadedCount = 0;
1500
+ let skippedCount = 0;
1501
+ console.log(` Processing ${imageFiles.length} images...`);
1502
+ for (const imageFile of imageFiles) {
1503
+ const fileName = path12.basename(imageFile);
1504
+ const existingId = existingMedia.get(fileName);
1505
+ if (existingId) {
1506
+ mediaMap.set(`/images/${imageFile}`, existingId);
1507
+ mediaMap.set(imageFile, existingId);
1508
+ skippedCount++;
1509
+ continue;
1510
+ }
1511
+ const imagePath = path12.join(imagesDir, imageFile);
1512
+ const mediaId = await uploadImage(imagePath, imageFile, strapiUrl, apiToken);
1513
+ if (mediaId) {
1514
+ mediaMap.set(`/images/${imageFile}`, mediaId);
1515
+ mediaMap.set(imageFile, mediaId);
1516
+ uploadedCount++;
1517
+ console.log(` \u2713 ${imageFile}`);
1518
+ }
1519
+ }
1520
+ console.log(` Uploaded: ${uploadedCount}, Skipped (existing): ${skippedCount}`);
1521
+ return mediaMap;
1522
+ }
1523
+ async function uploadImage(filePath, fileName, strapiUrl, apiToken) {
1524
+ try {
1525
+ const fileBuffer = await fs10.readFile(filePath);
1526
+ const mimeType = getMimeType(fileName);
1527
+ const blob = new Blob([fileBuffer], { type: mimeType });
1528
+ const formData = new globalThis.FormData();
1529
+ formData.append("files", blob, fileName);
1530
+ const response = await fetch(`${strapiUrl}/api/upload`, {
1531
+ method: "POST",
1532
+ headers: {
1533
+ Authorization: `Bearer ${apiToken}`
1534
+ },
1535
+ body: formData
1536
+ });
1537
+ if (!response.ok) {
1538
+ const errorText = await response.text();
1539
+ console.error(
1540
+ ` \u2717 Failed to upload ${fileName}: ${response.status} - ${errorText}`
1541
+ );
1542
+ return null;
1543
+ }
1544
+ const data = await response.json();
1545
+ return data[0]?.id || null;
1546
+ } catch (error) {
1547
+ console.error(` \u2717 Error uploading ${fileName}:`, error);
1548
+ return null;
1549
+ }
1550
+ }
1551
+ function getMimeType(fileName) {
1552
+ const ext = path12.extname(fileName).toLowerCase();
1553
+ const mimeTypes = {
1554
+ ".jpg": "image/jpeg",
1555
+ ".jpeg": "image/jpeg",
1556
+ ".png": "image/png",
1557
+ ".gif": "image/gif",
1558
+ ".webp": "image/webp",
1559
+ ".svg": "image/svg+xml"
1560
+ };
1561
+ return mimeTypes[ext] || "application/octet-stream";
1562
+ }
1563
+ async function seedContent(projectDir, strapiUrl, apiToken, mediaMap) {
1564
+ const seedPath = path12.join(projectDir, "cms-seed", "seed-data.json");
1565
+ if (!await fs10.pathExists(seedPath)) {
1566
+ console.log(" No seed data found");
1567
+ return;
1568
+ }
1569
+ const seedData = await fs10.readJson(seedPath);
1570
+ const schemasDir = path12.join(projectDir, "cms-schemas");
1571
+ const schemas = /* @__PURE__ */ new Map();
1572
+ const schemaFiles = await glob2("*.json", { cwd: schemasDir });
1573
+ for (const file of schemaFiles) {
1574
+ const schema = await fs10.readJson(path12.join(schemasDir, file));
1575
+ const name = path12.basename(file, ".json");
1576
+ schemas.set(name, schema);
1577
+ }
1578
+ let successCount = 0;
1579
+ let totalCount = 0;
1580
+ for (const [contentType, data] of Object.entries(seedData)) {
1581
+ const schema = schemas.get(contentType);
1582
+ if (!schema) {
1583
+ console.log(` \u26A0\uFE0F No schema found for ${contentType}, skipping...`);
1584
+ continue;
1585
+ }
1586
+ const singularName = schema.info.singularName;
1587
+ const pluralName = schema.info.pluralName;
1588
+ if (Array.isArray(data)) {
1589
+ console.log(` Seeding ${contentType} (${data.length} items)...`);
1590
+ for (const item of data) {
1591
+ totalCount++;
1592
+ const processedItem = processMediaFields(item, mediaMap);
1593
+ const success = await createEntry(
1594
+ pluralName,
1595
+ processedItem,
1596
+ strapiUrl,
1597
+ apiToken
1598
+ );
1599
+ if (success) successCount++;
1600
+ }
1601
+ } else {
1602
+ console.log(` Seeding ${contentType}...`);
1603
+ totalCount++;
1604
+ const processedData = processMediaFields(data, mediaMap);
1605
+ const success = await createOrUpdateSingleType(
1606
+ singularName,
1607
+ processedData,
1608
+ strapiUrl,
1609
+ apiToken
1610
+ );
1611
+ if (success) successCount++;
1612
+ }
1613
+ }
1614
+ console.log(` \u2713 Successfully seeded ${successCount}/${totalCount} entries`);
1615
+ }
1616
+ function processMediaFields(data, mediaMap) {
1617
+ const processed = {};
1618
+ for (const [key, value] of Object.entries(data)) {
1619
+ if (typeof value === "string") {
1620
+ if (key.includes("image") || key.includes("bg") || value.startsWith("/images/")) {
1621
+ const mediaId = mediaMap.get(value);
1622
+ if (mediaId) {
1623
+ processed[key] = mediaId;
1624
+ } else {
1625
+ processed[key] = value;
1626
+ }
1627
+ } else {
1628
+ processed[key] = value;
1629
+ }
1630
+ } else {
1631
+ processed[key] = value;
1632
+ }
1633
+ }
1634
+ return processed;
1635
+ }
1636
+ async function createEntry(contentType, data, strapiUrl, apiToken) {
1637
+ try {
1638
+ const response = await fetch(`${strapiUrl}/api/${contentType}`, {
1639
+ method: "POST",
1640
+ headers: {
1641
+ "Content-Type": "application/json",
1642
+ Authorization: `Bearer ${apiToken}`
1643
+ },
1644
+ body: JSON.stringify({ data })
1645
+ });
1646
+ if (!response.ok) {
1647
+ const errorText = await response.text();
1648
+ console.error(
1649
+ ` \u2717 Failed to create ${contentType}: ${response.status} - ${errorText}`
1650
+ );
1651
+ return false;
1652
+ }
1653
+ return true;
1654
+ } catch (error) {
1655
+ console.error(` \u2717 Error creating ${contentType}:`, error);
1656
+ return false;
1657
+ }
1658
+ }
1659
+ async function createOrUpdateSingleType(contentType, data, strapiUrl, apiToken) {
1660
+ try {
1661
+ const response = await fetch(`${strapiUrl}/api/${contentType}`, {
1662
+ method: "PUT",
1663
+ headers: {
1664
+ "Content-Type": "application/json",
1665
+ Authorization: `Bearer ${apiToken}`
1666
+ },
1667
+ body: JSON.stringify({ data })
1668
+ });
1669
+ if (!response.ok) {
1670
+ const errorText = await response.text();
1671
+ console.error(
1672
+ ` \u2717 Failed to update ${contentType}: ${response.status} - ${errorText}`
1673
+ );
1674
+ return false;
1675
+ }
1676
+ return true;
1677
+ } catch (error) {
1678
+ console.error(` \u2717 Error updating ${contentType}:`, error);
1679
+ return false;
1680
+ }
1681
+ }
1682
+ async function main() {
1683
+ const args = process.argv.slice(2);
1684
+ if (args.length < 2) {
1685
+ console.log(
1686
+ "Usage: tsx strapi-setup.ts <project-dir> <strapi-dir> [strapi-url] [api-token]"
1687
+ );
1688
+ console.log("");
1689
+ console.log("Example:");
1690
+ console.log(" tsx strapi-setup.ts ./nuxt-project ./strapi-dev");
1691
+ console.log(
1692
+ " tsx strapi-setup.ts ./nuxt-project ./strapi-dev http://localhost:1337 abc123"
1693
+ );
1694
+ process.exit(1);
1695
+ }
1696
+ const [projectDir, strapiDir, strapiUrl, apiToken] = args;
1697
+ await completeSetup({
1698
+ projectDir,
1699
+ strapiDir,
1700
+ strapiUrl,
1701
+ apiToken
1702
+ });
1703
+ }
1704
+ var isMainModule = process.argv[1] && process.argv[1].endsWith("strapi-setup.ts");
1705
+ if (isMainModule) {
1706
+ main().catch((error) => {
1707
+ console.error("\u274C Setup failed:", error.message);
1708
+ process.exit(1);
1709
+ });
1710
+ }
1711
+
1008
1712
  // src/cli.ts
1009
1713
  var program = new Command();
1010
- program.name("cms").description("SeeMS - Webflow to CMS converter").version("0.1.0");
1011
- program.command("convert").description("Convert Webflow export to Nuxt 3 project").argument("<input>", "Path to Webflow export directory").argument("<output>", "Path to output Nuxt project directory").option("-b, --boilerplate <source>", "Boilerplate source (GitHub URL or local path)").option("-o, --overrides <path>", "Path to overrides JSON file").option("--generate-schemas", "Generate CMS schemas immediately").option("--cms <type>", "CMS backend type (strapi|contentful|sanity)", "strapi").action(async (input, output, options) => {
1714
+ async function prompt(question) {
1715
+ const rl = readline2.createInterface({
1716
+ input: process.stdin,
1717
+ output: process.stdout
1718
+ });
1719
+ return new Promise((resolve) => {
1720
+ rl.question(question, (answer) => {
1721
+ rl.close();
1722
+ resolve(answer.trim());
1723
+ });
1724
+ });
1725
+ }
1726
+ async function confirm(question) {
1727
+ const answer = await prompt(`${question} (y/n): `);
1728
+ return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
1729
+ }
1730
+ program.name("cms").description("SeeMS - Webflow to CMS converter").version("0.1.2");
1731
+ program.command("convert").description("Convert Webflow export to Nuxt 3 project").argument("<input>", "Path to Webflow export directory").argument("<output>", "Path to output Nuxt project directory").option(
1732
+ "-b, --boilerplate <source>",
1733
+ "Boilerplate source (GitHub URL or local path)"
1734
+ ).option("-o, --overrides <path>", "Path to overrides JSON file").option("--generate-schemas", "Generate CMS schemas immediately").option(
1735
+ "--cms <type>",
1736
+ "CMS backend type (strapi|contentful|sanity)",
1737
+ "strapi"
1738
+ ).option("--no-interactive", "Skip interactive prompts").action(async (input, output, options) => {
1012
1739
  try {
1013
1740
  await convertWebflowExport({
1014
1741
  inputDir: input,
@@ -1018,15 +1745,99 @@ program.command("convert").description("Convert Webflow export to Nuxt 3 project
1018
1745
  generateStrapi: options.generateSchemas,
1019
1746
  cmsBackend: options.cms
1020
1747
  });
1748
+ if (options.interactive && options.cms === "strapi") {
1749
+ console.log("");
1750
+ const shouldSetup = await confirm(
1751
+ pc4.cyan("\u{1F3AF} Would you like to setup Strapi now?")
1752
+ );
1753
+ if (shouldSetup) {
1754
+ const strapiDir = await prompt(
1755
+ pc4.cyan(
1756
+ "\u{1F4C1} Enter path to your Strapi directory (e.g., ./strapi-dev): "
1757
+ )
1758
+ );
1759
+ if (strapiDir) {
1760
+ console.log("");
1761
+ console.log(pc4.cyan("\u{1F680} Starting Strapi setup..."));
1762
+ console.log("");
1763
+ try {
1764
+ await completeSetup({
1765
+ projectDir: output,
1766
+ strapiDir
1767
+ });
1768
+ } catch (error) {
1769
+ console.error(pc4.red("\n\u274C Strapi setup failed"));
1770
+ console.error(pc4.dim("You can run setup manually later with:"));
1771
+ console.error(
1772
+ pc4.dim(` cms setup-strapi ${output} ${strapiDir}`)
1773
+ );
1774
+ }
1775
+ }
1776
+ } else {
1777
+ console.log("");
1778
+ console.log(pc4.dim("\u{1F4A1} You can setup Strapi later with:"));
1779
+ console.log(
1780
+ pc4.dim(` cms setup-strapi ${output} <strapi-directory>`)
1781
+ );
1782
+ }
1783
+ }
1021
1784
  } catch (error) {
1022
1785
  console.error(pc4.red("Conversion failed"));
1023
1786
  process.exit(1);
1024
1787
  }
1025
1788
  });
1026
- program.command("generate").description("Generate CMS schemas from manifest").argument("<manifest>", "Path to cms-manifest.json").option("-t, --type <cms>", "CMS type (strapi|contentful|sanity)", "strapi").option("-o, --output <dir>", "Output directory for schemas").action(async (manifest, _options) => {
1027
- console.log(pc4.cyan("\u{1F3D7}\uFE0F SeeMS Schema Generator"));
1028
- console.log(pc4.dim(`Generating schemas from: ${manifest}`));
1029
- console.log(pc4.yellow("\u26A0\uFE0F Schema generation logic to be implemented"));
1789
+ program.command("setup-strapi").description("Setup Strapi with schemas and seed data").argument("<project-dir>", "Path to converted project directory").argument("<strapi-dir>", "Path to Strapi directory").option("--url <url>", "Strapi URL", "http://localhost:1337").option("--token <token>", "Strapi API token (optional)").option("--new-token", "Ignore saved token and prompt for a new one").action(async (projectDir, strapiDir, options) => {
1790
+ try {
1791
+ await completeSetup({
1792
+ projectDir,
1793
+ strapiDir,
1794
+ strapiUrl: options.url,
1795
+ apiToken: options.token,
1796
+ ignoreSavedToken: options.newToken
1797
+ });
1798
+ } catch (error) {
1799
+ console.error(pc4.red("Strapi setup failed"));
1800
+ console.error(error);
1801
+ process.exit(1);
1802
+ }
1803
+ });
1804
+ program.command("generate").description("Generate CMS schemas from manifest").argument("<manifest>", "Path to cms-manifest.json").option("-t, --type <cms>", "CMS type (strapi|contentful|sanity)", "strapi").option("-o, --output <dir>", "Output directory for schemas").action(async (manifestPath, options) => {
1805
+ try {
1806
+ console.log(pc4.cyan("\u{1F5C2}\uFE0F SeeMS Schema Generator"));
1807
+ console.log(pc4.dim(`Reading manifest from: ${manifestPath}`));
1808
+ const manifestExists = await fs11.pathExists(manifestPath);
1809
+ if (!manifestExists) {
1810
+ throw new Error(`Manifest file not found: ${manifestPath}`);
1811
+ }
1812
+ const manifestContent = await fs11.readFile(manifestPath, "utf-8");
1813
+ const manifest = JSON.parse(manifestContent);
1814
+ console.log(pc4.green(` \u2713 Manifest loaded successfully`));
1815
+ const outputDir = options.output || path13.dirname(manifestPath);
1816
+ if (options.type !== "strapi") {
1817
+ console.log(
1818
+ pc4.yellow(
1819
+ `\u26A0\uFE0F Only Strapi is currently supported. Using Strapi schema format.`
1820
+ )
1821
+ );
1822
+ }
1823
+ console.log(pc4.blue("\n\u{1F4CB} Generating Strapi schemas..."));
1824
+ const schemas = manifestToSchemas(manifest);
1825
+ await writeAllSchemas(outputDir, schemas);
1826
+ await createStrapiReadme(outputDir);
1827
+ console.log(
1828
+ pc4.green(
1829
+ ` \u2713 Generated ${Object.keys(schemas).length} Strapi content types`
1830
+ )
1831
+ );
1832
+ console.log(pc4.dim(` \u2713 Schemas written to: ${path13.join(outputDir, "cms-schemas")}/`));
1833
+ console.log(pc4.green("\n\u2705 Schema generation completed successfully!"));
1834
+ } catch (error) {
1835
+ console.error(pc4.red("\n\u274C Schema generation failed:"));
1836
+ console.error(
1837
+ pc4.red(error instanceof Error ? error.message : String(error))
1838
+ );
1839
+ process.exit(1);
1840
+ }
1030
1841
  });
1031
1842
  program.parse();
1032
1843
  //# sourceMappingURL=cli.mjs.map