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