@see-ms/converter 0.1.0 → 0.1.2

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.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  // src/converter.ts
2
2
  import pc3 from "picocolors";
3
- import fs4 from "fs-extra";
3
+ import path9 from "path";
4
+ import fs8 from "fs-extra";
4
5
 
5
6
  // src/filesystem.ts
6
7
  import fs from "fs-extra";
@@ -319,9 +320,86 @@ ${styles}`, "utf-8");
319
320
  }
320
321
  }
321
322
 
322
- // src/boilerplate.ts
323
+ // src/editor-integration.ts
323
324
  import fs3 from "fs-extra";
324
325
  import path4 from "path";
326
+ async function createEditorPlugin(outputDir) {
327
+ const pluginsDir = path4.join(outputDir, "plugins");
328
+ await fs3.ensureDir(pluginsDir);
329
+ const pluginContent = `/**
330
+ * CMS Editor Overlay Plugin
331
+ * Loads the inline editor when ?preview=true
332
+ */
333
+
334
+ export default defineNuxtPlugin(() => {
335
+ // Only run on client side
336
+ if (process.server) return;
337
+
338
+ // Check for preview mode
339
+ const params = new URLSearchParams(window.location.search);
340
+
341
+ if (params.get('preview') === 'true') {
342
+ // Dynamically import the editor
343
+ import('@see-ms/editor-overlay').then(({ initEditor, createToolbar }) => {
344
+ const editor = initEditor({
345
+ apiEndpoint: '/api/cms/save',
346
+ richText: true,
347
+ });
348
+
349
+ editor.enable();
350
+
351
+ const toolbar = createToolbar(editor);
352
+ document.body.appendChild(toolbar);
353
+ });
354
+ }
355
+ });
356
+ `;
357
+ const pluginPath = path4.join(pluginsDir, "cms-editor.client.ts");
358
+ await fs3.writeFile(pluginPath, pluginContent, "utf-8");
359
+ }
360
+ async function addEditorDependency(outputDir) {
361
+ const packageJsonPath = path4.join(outputDir, "package.json");
362
+ if (await fs3.pathExists(packageJsonPath)) {
363
+ const packageJson = await fs3.readJson(packageJsonPath);
364
+ if (!packageJson.dependencies) {
365
+ packageJson.dependencies = {};
366
+ }
367
+ packageJson.dependencies["@see-ms/editor-overlay"] = "^0.1.1";
368
+ await fs3.writeJson(packageJsonPath, packageJson, { spaces: 2 });
369
+ }
370
+ }
371
+ async function createSaveEndpoint(outputDir) {
372
+ const serverDir = path4.join(outputDir, "server", "api", "cms");
373
+ await fs3.ensureDir(serverDir);
374
+ const endpointContent = `/**
375
+ * API endpoint for saving CMS changes
376
+ */
377
+
378
+ export default defineEventHandler(async (event) => {
379
+ const body = await readBody(event);
380
+
381
+ // TODO: Implement actual saving to Strapi
382
+ // For now, just log the changes
383
+ console.log('CMS changes:', body);
384
+
385
+ // In production, this would:
386
+ // 1. Validate the changes
387
+ // 2. Send to Strapi API
388
+ // 3. Return success/error
389
+
390
+ return {
391
+ success: true,
392
+ message: 'Changes saved (demo mode)',
393
+ };
394
+ });
395
+ `;
396
+ const endpointPath = path4.join(serverDir, "save.post.ts");
397
+ await fs3.writeFile(endpointPath, endpointContent, "utf-8");
398
+ }
399
+
400
+ // src/boilerplate.ts
401
+ import fs4 from "fs-extra";
402
+ import path5 from "path";
325
403
  import { execSync as execSync2 } from "child_process";
326
404
  import pc2 from "picocolors";
327
405
  function isGitHubURL(source) {
@@ -331,8 +409,8 @@ async function cloneFromGitHub(repoUrl, outputDir) {
331
409
  console.log(pc2.blue(" Cloning from GitHub..."));
332
410
  try {
333
411
  execSync2(`git clone ${repoUrl} ${outputDir}`, { stdio: "inherit" });
334
- const gitDir = path4.join(outputDir, ".git");
335
- await fs3.remove(gitDir);
412
+ const gitDir = path5.join(outputDir, ".git");
413
+ await fs4.remove(gitDir);
336
414
  console.log(pc2.green(" \u2713 Boilerplate cloned successfully"));
337
415
  } catch (error) {
338
416
  throw new Error(`Failed to clone repository: ${error instanceof Error ? error.message : String(error)}`);
@@ -340,13 +418,13 @@ async function cloneFromGitHub(repoUrl, outputDir) {
340
418
  }
341
419
  async function copyFromLocal(sourcePath, outputDir) {
342
420
  console.log(pc2.blue(" Copying from local path..."));
343
- const sourceExists = await fs3.pathExists(sourcePath);
421
+ const sourceExists = await fs4.pathExists(sourcePath);
344
422
  if (!sourceExists) {
345
423
  throw new Error(`Local boilerplate not found: ${sourcePath}`);
346
424
  }
347
- await fs3.copy(sourcePath, outputDir, {
425
+ await fs4.copy(sourcePath, outputDir, {
348
426
  filter: (src) => {
349
- const name = path4.basename(src);
427
+ const name = path5.basename(src);
350
428
  return !["node_modules", ".nuxt", ".output", ".git", "dist"].includes(name);
351
429
  }
352
430
  });
@@ -355,25 +433,25 @@ async function copyFromLocal(sourcePath, outputDir) {
355
433
  async function setupBoilerplate(boilerplateSource, outputDir) {
356
434
  if (!boilerplateSource) {
357
435
  console.log(pc2.blue("\n\u{1F4E6} Creating minimal Nuxt structure..."));
358
- await fs3.ensureDir(outputDir);
359
- await fs3.ensureDir(path4.join(outputDir, "pages"));
360
- await fs3.ensureDir(path4.join(outputDir, "assets"));
361
- await fs3.ensureDir(path4.join(outputDir, "public"));
362
- await fs3.ensureDir(path4.join(outputDir, "utils"));
363
- const configPath = path4.join(outputDir, "nuxt.config.ts");
364
- const configExists = await fs3.pathExists(configPath);
436
+ await fs4.ensureDir(outputDir);
437
+ await fs4.ensureDir(path5.join(outputDir, "pages"));
438
+ await fs4.ensureDir(path5.join(outputDir, "assets"));
439
+ await fs4.ensureDir(path5.join(outputDir, "public"));
440
+ await fs4.ensureDir(path5.join(outputDir, "utils"));
441
+ const configPath = path5.join(outputDir, "nuxt.config.ts");
442
+ const configExists = await fs4.pathExists(configPath);
365
443
  if (!configExists) {
366
444
  const basicConfig = `export default defineNuxtConfig({
367
445
  devtools: { enabled: true },
368
446
  css: [],
369
447
  })
370
448
  `;
371
- await fs3.writeFile(configPath, basicConfig, "utf-8");
449
+ await fs4.writeFile(configPath, basicConfig, "utf-8");
372
450
  }
373
451
  console.log(pc2.green(" \u2713 Structure created"));
374
452
  return;
375
453
  }
376
- const outputExists = await fs3.pathExists(outputDir);
454
+ const outputExists = await fs4.pathExists(outputDir);
377
455
  if (outputExists) {
378
456
  throw new Error(`Output directory already exists: ${outputDir}. Please choose a different path or remove it first.`);
379
457
  }
@@ -385,6 +463,441 @@ async function setupBoilerplate(boilerplateSource, outputDir) {
385
463
  }
386
464
  }
387
465
 
466
+ // src/manifest.ts
467
+ import fs6 from "fs-extra";
468
+ import path7 from "path";
469
+
470
+ // src/detector.ts
471
+ import * as cheerio2 from "cheerio";
472
+ import fs5 from "fs-extra";
473
+ import path6 from "path";
474
+ function cleanClassName(className) {
475
+ return className.split(" ").filter((cls) => !cls.startsWith("c-") && !cls.startsWith("w-")).filter((cls) => cls.length > 0).map((cls) => cls.replace(/-/g, "_")).join(" ");
476
+ }
477
+ function getPrimaryClass(classAttr) {
478
+ if (!classAttr) return null;
479
+ const cleaned = cleanClassName(classAttr);
480
+ const classes = cleaned.split(" ").filter((c) => c.length > 0);
481
+ return classes[0] || null;
482
+ }
483
+ function getContextModifier(_$, $el) {
484
+ let $current = $el.parent();
485
+ let depth = 0;
486
+ while ($current.length > 0 && depth < 5) {
487
+ const classes = $current.attr("class");
488
+ if (classes) {
489
+ const ccClass = classes.split(" ").find((c) => c.startsWith("cc-"));
490
+ if (ccClass) {
491
+ return ccClass.replace("cc-", "").replace(/-/g, "_");
492
+ }
493
+ }
494
+ $current = $current.parent();
495
+ depth++;
496
+ }
497
+ return null;
498
+ }
499
+ function isDecorativeImage(_$, $img) {
500
+ const $parent = $img.parent();
501
+ const parentClass = $parent.attr("class") || "";
502
+ const decorativePatterns = [
503
+ "nav",
504
+ "logo",
505
+ "icon",
506
+ "arrow",
507
+ "button",
508
+ "quote",
509
+ "pagination",
510
+ "footer",
511
+ "link"
512
+ ];
513
+ return decorativePatterns.some(
514
+ (pattern) => parentClass.includes(pattern) || parentClass.includes(`${pattern}_`)
515
+ );
516
+ }
517
+ function isInsideButton($, el) {
518
+ const $el = $(el);
519
+ const $button = $el.closest("button, a, NuxtLink, .c_button, .c_icon_button");
520
+ return $button.length > 0;
521
+ }
522
+ function extractTemplateFromVue(vueContent) {
523
+ const templateMatch = vueContent.match(/<template>([\s\S]*?)<\/template>/);
524
+ if (!templateMatch) {
525
+ return "";
526
+ }
527
+ return templateMatch[1];
528
+ }
529
+ function detectEditableFields(templateHtml) {
530
+ const $ = cheerio2.load(templateHtml);
531
+ const detectedFields = {};
532
+ const detectedCollections = {};
533
+ const collectionElements = /* @__PURE__ */ new Set();
534
+ const processedCollectionClasses = /* @__PURE__ */ new Set();
535
+ const potentialCollections = /* @__PURE__ */ new Map();
536
+ $("[class]").each((_, el) => {
537
+ 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, []);
541
+ }
542
+ potentialCollections.get(primaryClass)?.push(el);
543
+ }
544
+ });
545
+ potentialCollections.forEach((elements, className) => {
546
+ if (elements.length >= 2) {
547
+ const $first = $(elements[0]);
548
+ const collectionFields = {};
549
+ processedCollectionClasses.add(className);
550
+ elements.forEach((el) => {
551
+ collectionElements.add(el);
552
+ $(el).find("*").each((_, child) => {
553
+ collectionElements.add(child);
554
+ });
555
+ });
556
+ $first.find("img").each((_, img) => {
557
+ if (isInsideButton($, img)) return;
558
+ const $img = $(img);
559
+ const $parent = $img.parent();
560
+ const parentClass = getPrimaryClass($parent.attr("class"));
561
+ if (parentClass && parentClass.includes("image")) {
562
+ collectionFields.image = `.${parentClass}`;
563
+ return false;
564
+ }
565
+ });
566
+ $first.find("div").each((_, el) => {
567
+ const primaryClass = getPrimaryClass($(el).attr("class"));
568
+ if (primaryClass && primaryClass.includes("tag") && !primaryClass.includes("container")) {
569
+ collectionFields.tag = `.${primaryClass}`;
570
+ return false;
571
+ }
572
+ });
573
+ $first.find("h1, h2, h3, h4, h5, h6").first().each((_, el) => {
574
+ const primaryClass = getPrimaryClass($(el).attr("class"));
575
+ if (primaryClass) {
576
+ collectionFields.title = `.${primaryClass}`;
577
+ }
578
+ });
579
+ $first.find("p").first().each((_, el) => {
580
+ const primaryClass = getPrimaryClass($(el).attr("class"));
581
+ if (primaryClass) {
582
+ collectionFields.description = `.${primaryClass}`;
583
+ }
584
+ });
585
+ $first.find("a, NuxtLink").not(".c_button, .c_icon_button").each((_, el) => {
586
+ const $link = $(el);
587
+ const linkText = $link.text().trim();
588
+ if (linkText) {
589
+ collectionFields.link = `.${getPrimaryClass($link.attr("class")) || "a"}`;
590
+ return false;
591
+ }
592
+ });
593
+ if (Object.keys(collectionFields).length > 0) {
594
+ let collectionName = className;
595
+ if (!collectionName.endsWith("s")) {
596
+ collectionName += "s";
597
+ }
598
+ detectedCollections[collectionName] = {
599
+ selector: `.${className}`,
600
+ fields: collectionFields
601
+ };
602
+ }
603
+ }
604
+ });
605
+ const $body = $("body");
606
+ $body.find("h1, h2, h3, h4, h5, h6").each((index, el) => {
607
+ if (collectionElements.has(el)) return;
608
+ const $el = $(el);
609
+ const text = $el.text().trim();
610
+ const primaryClass = getPrimaryClass($el.attr("class"));
611
+ if (text) {
612
+ let fieldName;
613
+ if (primaryClass && !primaryClass.startsWith("heading_")) {
614
+ fieldName = primaryClass;
615
+ } else {
616
+ const $parent = $el.closest('[class*="header"], [class*="hero"], [class*="cta"]').first();
617
+ const parentClass = getPrimaryClass($parent.attr("class"));
618
+ const modifier = getContextModifier($, $el);
619
+ if (parentClass) {
620
+ fieldName = modifier ? `${modifier}_${parentClass}` : parentClass;
621
+ } else if (modifier) {
622
+ fieldName = `${modifier}_heading`;
623
+ } else {
624
+ fieldName = `heading_${index}`;
625
+ }
626
+ }
627
+ detectedFields[fieldName] = {
628
+ selector: primaryClass ? `.${primaryClass}` : el.tagName.toLowerCase(),
629
+ type: "plain",
630
+ editable: true
631
+ };
632
+ }
633
+ });
634
+ $body.find("p").each((_index, el) => {
635
+ if (collectionElements.has(el)) return;
636
+ const $el = $(el);
637
+ const text = $el.text().trim();
638
+ const primaryClass = getPrimaryClass($el.attr("class"));
639
+ if (text && text.length > 20 && primaryClass) {
640
+ const hasFormatting = $el.find("strong, em, b, i, a, NuxtLink").length > 0;
641
+ detectedFields[primaryClass] = {
642
+ selector: `.${primaryClass}`,
643
+ type: hasFormatting ? "rich" : "plain",
644
+ editable: true
645
+ };
646
+ }
647
+ });
648
+ $body.find("img").each((_index, el) => {
649
+ if (collectionElements.has(el)) return;
650
+ if (isInsideButton($, el)) return;
651
+ const $el = $(el);
652
+ if (isDecorativeImage($, $el)) return;
653
+ const $parent = $el.parent();
654
+ const parentClass = getPrimaryClass($parent.attr("class"));
655
+ if (parentClass) {
656
+ const fieldName = parentClass.includes("image") ? parentClass : `${parentClass}_image`;
657
+ detectedFields[fieldName] = {
658
+ selector: `.${parentClass}`,
659
+ type: "image",
660
+ editable: true
661
+ };
662
+ }
663
+ });
664
+ $body.find("NuxtLink.c_button, a.c_button, .c_button").each((_index, el) => {
665
+ if (collectionElements.has(el)) return;
666
+ const $el = $(el);
667
+ const text = $el.contents().filter(function() {
668
+ return this.type === "text" || this.type === "tag" && this.name === "div";
669
+ }).first().text().trim();
670
+ if (text && text.length > 2) {
671
+ const $parent = $el.closest('[class*="cta"]').first();
672
+ const parentClass = getPrimaryClass($parent.attr("class")) || "button";
673
+ detectedFields[`${parentClass}_button_text`] = {
674
+ selector: `.c_button`,
675
+ type: "plain",
676
+ editable: true
677
+ };
678
+ }
679
+ });
680
+ return {
681
+ fields: detectedFields,
682
+ collections: detectedCollections
683
+ };
684
+ }
685
+ async function analyzeVuePages(pagesDir) {
686
+ const results = {};
687
+ const vueFiles = await fs5.readdir(pagesDir);
688
+ for (const file of vueFiles) {
689
+ if (file.endsWith(".vue")) {
690
+ const filePath = path6.join(pagesDir, file);
691
+ const content = await fs5.readFile(filePath, "utf-8");
692
+ const template = extractTemplateFromVue(content);
693
+ if (template) {
694
+ const pageName = file.replace(".vue", "");
695
+ results[pageName] = detectEditableFields(template);
696
+ }
697
+ }
698
+ }
699
+ return results;
700
+ }
701
+
702
+ // src/manifest.ts
703
+ async function generateManifest(pagesDir) {
704
+ const analyzed = await analyzeVuePages(pagesDir);
705
+ const pages = {};
706
+ for (const [pageName, detection] of Object.entries(analyzed)) {
707
+ pages[pageName] = {
708
+ fields: detection.fields,
709
+ collections: detection.collections,
710
+ meta: {
711
+ route: pageName === "index" ? "/" : `/${pageName}`
712
+ }
713
+ };
714
+ }
715
+ const manifest = {
716
+ version: "1.0",
717
+ pages
718
+ };
719
+ return manifest;
720
+ }
721
+ async function writeManifest(outputDir, manifest) {
722
+ const manifestPath = path7.join(outputDir, "cms-manifest.json");
723
+ await fs6.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
724
+ }
725
+
726
+ // src/transformer.ts
727
+ function mapFieldTypeToStrapi(fieldType) {
728
+ const typeMap = {
729
+ plain: "string",
730
+ rich: "richtext",
731
+ html: "richtext",
732
+ image: "media",
733
+ link: "string",
734
+ email: "email",
735
+ phone: "string"
736
+ };
737
+ return typeMap[fieldType] || "string";
738
+ }
739
+ function pageToStrapiSchema(pageName, fields) {
740
+ const attributes = {};
741
+ for (const [fieldName, field] of Object.entries(fields)) {
742
+ attributes[fieldName] = {
743
+ type: mapFieldTypeToStrapi(field.type),
744
+ required: field.required || false
745
+ };
746
+ if (field.default) {
747
+ attributes[fieldName].default = field.default;
748
+ }
749
+ }
750
+ const displayName = pageName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
751
+ return {
752
+ kind: "singleType",
753
+ collectionName: pageName.replace(/-/g, "_"),
754
+ info: {
755
+ singularName: pageName.replace(/-/g, "_"),
756
+ pluralName: pageName.replace(/-/g, "_"),
757
+ displayName
758
+ },
759
+ options: {
760
+ draftAndPublish: true
761
+ },
762
+ attributes
763
+ };
764
+ }
765
+ function collectionToStrapiSchema(collectionName, collection) {
766
+ const attributes = {};
767
+ for (const [fieldName, _selector] of Object.entries(collection.fields)) {
768
+ let type = "string";
769
+ if (fieldName === "image" || fieldName.includes("image")) {
770
+ type = "media";
771
+ } else if (fieldName === "description" || fieldName === "content") {
772
+ type = "richtext";
773
+ } else if (fieldName === "link" || fieldName === "url") {
774
+ type = "string";
775
+ } else if (fieldName === "title" || fieldName === "tag") {
776
+ type = "string";
777
+ }
778
+ attributes[fieldName] = {
779
+ type
780
+ };
781
+ }
782
+ const displayName = collectionName.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
783
+ const singularName = collectionName.endsWith("s") ? collectionName.slice(0, -1) : collectionName;
784
+ return {
785
+ kind: "collectionType",
786
+ collectionName: collectionName.replace(/[-_]/g, "_"),
787
+ info: {
788
+ singularName: singularName.replace(/[-_]/g, "_"),
789
+ pluralName: collectionName.replace(/[-_]/g, "_"),
790
+ displayName
791
+ },
792
+ options: {
793
+ draftAndPublish: true
794
+ },
795
+ attributes
796
+ };
797
+ }
798
+ function manifestToSchemas(manifest) {
799
+ const schemas = {};
800
+ for (const [pageName, page] of Object.entries(manifest.pages)) {
801
+ if (page.fields && Object.keys(page.fields).length > 0) {
802
+ schemas[pageName] = pageToStrapiSchema(pageName, page.fields);
803
+ }
804
+ if (page.collections) {
805
+ for (const [collectionName, collection] of Object.entries(page.collections)) {
806
+ schemas[collectionName] = collectionToStrapiSchema(collectionName, collection);
807
+ }
808
+ }
809
+ }
810
+ return schemas;
811
+ }
812
+
813
+ // src/schema-writer.ts
814
+ import fs7 from "fs-extra";
815
+ import path8 from "path";
816
+ async function writeStrapiSchema(outputDir, name, schema) {
817
+ const schemasDir = path8.join(outputDir, "cms-schemas");
818
+ await fs7.ensureDir(schemasDir);
819
+ const schemaPath = path8.join(schemasDir, `${name}.json`);
820
+ await fs7.writeFile(schemaPath, JSON.stringify(schema, null, 2), "utf-8");
821
+ }
822
+ async function writeAllSchemas(outputDir, schemas) {
823
+ for (const [name, schema] of Object.entries(schemas)) {
824
+ await writeStrapiSchema(outputDir, name, schema);
825
+ }
826
+ }
827
+ async function createStrapiReadme(outputDir) {
828
+ const readmePath = path8.join(outputDir, "cms-schemas", "README.md");
829
+ const content = `# CMS Schemas
830
+
831
+ Auto-generated Strapi content type schemas from your Webflow export.
832
+
833
+ ## What's in this folder?
834
+
835
+ Each \`.json\` file is a Strapi content type schema:
836
+
837
+ - **Pages** (single types) - Unique pages like \`index.json\`, \`about.json\`
838
+ - **Collections** (collection types) - Repeating content like \`portfolio_cards.json\`
839
+
840
+ ## How to use with Strapi
841
+
842
+ ### Option 1: Manual Setup (Recommended for learning)
843
+
844
+ 1. Start your Strapi project
845
+ 2. In Strapi admin, go to **Content-Type Builder**
846
+ 3. Create each content type manually using these schemas as reference
847
+ 4. Match the field names and types
848
+
849
+ ### Option 2: Automated Setup (Advanced)
850
+
851
+ Copy schemas to your Strapi project structure:
852
+
853
+ \`\`\`bash
854
+ # For each schema file, create the Strapi directory structure
855
+ # Example for index.json (single type):
856
+ mkdir -p strapi/src/api/index/content-types/index
857
+ cp cms-schemas/index.json strapi/src/api/index/content-types/index/schema.json
858
+
859
+ # Example for portfolio_cards.json (collection type):
860
+ mkdir -p strapi/src/api/portfolio-cards/content-types/portfolio-card
861
+ cp cms-schemas/portfolio_cards.json strapi/src/api/portfolio-cards/content-types/portfolio-card/schema.json
862
+ \`\`\`
863
+
864
+ Then restart Strapi - it will auto-create the content types.
865
+
866
+ ## Schema Structure
867
+
868
+ Each schema defines:
869
+ - \`kind\`: "singleType" (unique page) or "collectionType" (repeating)
870
+ - \`attributes\`: Fields and their types (string, richtext, media, etc.)
871
+ - \`displayName\`: How it appears in Strapi admin
872
+
873
+ ## Field Types
874
+
875
+ - \`string\` - Plain text
876
+ - \`richtext\` - Formatted text with HTML
877
+ - \`media\` - Image uploads
878
+
879
+ ## Next Steps
880
+
881
+ 1. Set up a Strapi project: \`npx create-strapi-app@latest my-strapi\`
882
+ 2. Use these schemas to create content types
883
+ 3. Populate content in Strapi admin
884
+ 4. Connect your Nuxt app to Strapi API
885
+
886
+ ## API Usage in Nuxt
887
+
888
+ Once Strapi is running with these content types:
889
+
890
+ \`\`\`typescript
891
+ // Fetch single type (e.g., home page)
892
+ const { data } = await $fetch('http://localhost:1337/api/index')
893
+
894
+ // Fetch collection type (e.g., portfolio cards)
895
+ const { data } = await $fetch('http://localhost:1337/api/portfolio-cards')
896
+ \`\`\`
897
+ `;
898
+ await fs7.writeFile(readmePath, content, "utf-8");
899
+ }
900
+
388
901
  // src/converter.ts
389
902
  async function convertWebflowExport(options) {
390
903
  const { inputDir, outputDir, boilerplate } = options;
@@ -393,7 +906,7 @@ async function convertWebflowExport(options) {
393
906
  console.log(pc3.dim(`Output: ${outputDir}`));
394
907
  try {
395
908
  await setupBoilerplate(boilerplate, outputDir);
396
- const inputExists = await fs4.pathExists(inputDir);
909
+ const inputExists = await fs8.pathExists(inputDir);
397
910
  if (!inputExists) {
398
911
  throw new Error(`Input directory not found: ${inputDir}`);
399
912
  }
@@ -427,6 +940,27 @@ ${parsed.embeddedStyles}
427
940
  console.log(pc3.green(` \u2713 Created ${htmlFile.replace(".html", ".vue")}`));
428
941
  }
429
942
  await formatVueFiles(outputDir);
943
+ console.log(pc3.blue("\n\u{1F50D} Analyzing pages for CMS fields..."));
944
+ const pagesDir = path9.join(outputDir, "pages");
945
+ const manifest = await generateManifest(pagesDir);
946
+ await writeManifest(outputDir, manifest);
947
+ const totalFields = Object.values(manifest.pages).reduce(
948
+ (sum, page) => sum + Object.keys(page.fields || {}).length,
949
+ 0
950
+ );
951
+ const totalCollections = Object.values(manifest.pages).reduce(
952
+ (sum, page) => sum + Object.keys(page.collections || {}).length,
953
+ 0
954
+ );
955
+ console.log(pc3.green(` \u2713 Detected ${totalFields} fields across ${Object.keys(manifest.pages).length} pages`));
956
+ console.log(pc3.green(` \u2713 Detected ${totalCollections} collections`));
957
+ console.log(pc3.green(" \u2713 Generated cms-manifest.json"));
958
+ console.log(pc3.blue("\n\u{1F4CB} Generating Strapi schemas..."));
959
+ const schemas = manifestToSchemas(manifest);
960
+ await writeAllSchemas(outputDir, schemas);
961
+ await createStrapiReadme(outputDir);
962
+ console.log(pc3.green(` \u2713 Generated ${Object.keys(schemas).length} Strapi content types`));
963
+ console.log(pc3.dim(" View schemas in: strapi/src/api/"));
430
964
  if (allEmbeddedStyles.trim()) {
431
965
  console.log(pc3.blue("\n\u2728 Writing embedded styles..."));
432
966
  const dedupedStyles = deduplicateStyles(allEmbeddedStyles);
@@ -444,11 +978,20 @@ ${parsed.embeddedStyles}
444
978
  console.log(pc3.yellow(" \u26A0 Could not update nuxt.config.ts automatically"));
445
979
  console.log(pc3.dim(" Please add CSS files manually"));
446
980
  }
981
+ console.log(pc3.blue("\n\u{1F3A8} Setting up editor overlay..."));
982
+ await createEditorPlugin(outputDir);
983
+ await addEditorDependency(outputDir);
984
+ await createSaveEndpoint(outputDir);
985
+ console.log(pc3.green(" \u2713 Editor plugin created"));
986
+ console.log(pc3.green(" \u2713 Editor dependency added"));
987
+ console.log(pc3.green(" \u2713 Save endpoint created"));
447
988
  console.log(pc3.green("\n\u2705 Conversion completed successfully!"));
448
989
  console.log(pc3.cyan("\n\u{1F4CB} Next steps:"));
449
990
  console.log(pc3.dim(` 1. cd ${outputDir}`));
450
- console.log(pc3.dim(" 2. pnpm install"));
451
- console.log(pc3.dim(" 3. pnpm dev"));
991
+ console.log(pc3.dim(" 2. Review cms-manifest.json"));
992
+ console.log(pc3.dim(" 3. Copy strapi/ schemas to your Strapi project"));
993
+ console.log(pc3.dim(" 4. pnpm install && pnpm dev"));
994
+ console.log(pc3.dim(" 5. Visit http://localhost:3000?preview=true to edit inline!"));
452
995
  } catch (error) {
453
996
  console.error(pc3.red("\n\u274C Conversion failed:"));
454
997
  console.error(pc3.red(error instanceof Error ? error.message : String(error)));
@@ -456,31 +999,16 @@ ${parsed.embeddedStyles}
456
999
  }
457
1000
  }
458
1001
 
459
- // src/detector.ts
460
- function detectEditableFields(_html) {
461
- throw new Error("Not yet implemented");
462
- }
463
-
464
- // src/manifest.ts
465
- function generateManifest(_data) {
466
- throw new Error("Not yet implemented");
467
- }
468
-
469
1002
  // src/generator.ts
470
1003
  async function generateSchemas(_manifestPath, _cmsType) {
471
1004
  throw new Error("Not yet implemented");
472
1005
  }
473
-
474
- // src/transformer.ts
475
- function manifestToSchema(_manifest, _cmsType) {
476
- throw new Error("Not yet implemented");
477
- }
478
1006
  export {
479
1007
  convertWebflowExport,
480
1008
  detectEditableFields,
481
1009
  generateManifest,
482
1010
  generateSchemas,
483
- manifestToSchema,
1011
+ manifestToSchemas,
484
1012
  setupBoilerplate
485
1013
  };
486
1014
  //# sourceMappingURL=index.mjs.map