@see-ms/converter 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -13
- package/dist/cli.mjs +1230 -24
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +20 -10
- package/dist/index.mjs +798 -37
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/converter.ts
|
|
2
2
|
import pc3 from "picocolors";
|
|
3
|
-
import
|
|
3
|
+
import path11 from "path";
|
|
4
|
+
import fs9 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/
|
|
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 =
|
|
335
|
-
await
|
|
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
|
|
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
|
|
425
|
+
await fs4.copy(sourcePath, outputDir, {
|
|
348
426
|
filter: (src) => {
|
|
349
|
-
const name =
|
|
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
|
|
359
|
-
await
|
|
360
|
-
await
|
|
361
|
-
await
|
|
362
|
-
await
|
|
363
|
-
const configPath =
|
|
364
|
-
const configExists = await
|
|
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
|
|
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
|
|
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,652 @@ 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).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
|
+
if (classes.length === 0) return null;
|
|
482
|
+
const original = classes[0];
|
|
483
|
+
return {
|
|
484
|
+
selector: original,
|
|
485
|
+
// Keep original with dashes for CSS selector
|
|
486
|
+
fieldName: original.replace(/-/g, "_")
|
|
487
|
+
// Normalize for field name
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function getContextModifier(_$, $el) {
|
|
491
|
+
let $current = $el.parent();
|
|
492
|
+
let depth = 0;
|
|
493
|
+
while ($current.length > 0 && depth < 5) {
|
|
494
|
+
const classes = $current.attr("class");
|
|
495
|
+
if (classes) {
|
|
496
|
+
const ccClass = classes.split(" ").find((c) => c.startsWith("cc-"));
|
|
497
|
+
if (ccClass) {
|
|
498
|
+
return ccClass.replace("cc-", "").replace(/-/g, "_");
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
$current = $current.parent();
|
|
502
|
+
depth++;
|
|
503
|
+
}
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
function isDecorativeImage(_$, $img) {
|
|
507
|
+
const $parent = $img.parent();
|
|
508
|
+
const parentClass = $parent.attr("class") || "";
|
|
509
|
+
const decorativePatterns = [
|
|
510
|
+
"nav",
|
|
511
|
+
"logo",
|
|
512
|
+
"icon",
|
|
513
|
+
"arrow",
|
|
514
|
+
"button",
|
|
515
|
+
"quote",
|
|
516
|
+
"pagination",
|
|
517
|
+
"footer",
|
|
518
|
+
"link"
|
|
519
|
+
];
|
|
520
|
+
return decorativePatterns.some(
|
|
521
|
+
(pattern) => parentClass.includes(pattern) || parentClass.includes(`${pattern}_`)
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
function isInsideButton($, el) {
|
|
525
|
+
const $el = $(el);
|
|
526
|
+
const $button = $el.closest("button, a, NuxtLink, .c_button, .c_icon_button");
|
|
527
|
+
return $button.length > 0;
|
|
528
|
+
}
|
|
529
|
+
function extractTemplateFromVue(vueContent) {
|
|
530
|
+
const templateMatch = vueContent.match(/<template>([\s\S]*?)<\/template>/);
|
|
531
|
+
if (!templateMatch) {
|
|
532
|
+
return "";
|
|
533
|
+
}
|
|
534
|
+
return templateMatch[1];
|
|
535
|
+
}
|
|
536
|
+
function detectEditableFields(templateHtml) {
|
|
537
|
+
const $ = cheerio2.load(templateHtml);
|
|
538
|
+
const detectedFields = {};
|
|
539
|
+
const detectedCollections = {};
|
|
540
|
+
const collectionElements = /* @__PURE__ */ new Set();
|
|
541
|
+
const processedCollectionClasses = /* @__PURE__ */ new Set();
|
|
542
|
+
const potentialCollections = /* @__PURE__ */ new Map();
|
|
543
|
+
$("[class]").each((_, el) => {
|
|
544
|
+
const primaryClass = getPrimaryClass($(el).attr("class"));
|
|
545
|
+
if (primaryClass && (primaryClass.fieldName.includes("card") || primaryClass.fieldName.includes("item") || primaryClass.fieldName.includes("post") || primaryClass.fieldName.includes("feature")) && !primaryClass.fieldName.includes("image") && !primaryClass.fieldName.includes("inner")) {
|
|
546
|
+
if (!potentialCollections.has(primaryClass.fieldName)) {
|
|
547
|
+
potentialCollections.set(primaryClass.fieldName, []);
|
|
548
|
+
}
|
|
549
|
+
potentialCollections.get(primaryClass.fieldName)?.push(el);
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
potentialCollections.forEach((elements, className) => {
|
|
553
|
+
if (elements.length >= 2) {
|
|
554
|
+
const $first = $(elements[0]);
|
|
555
|
+
const collectionFields = {};
|
|
556
|
+
processedCollectionClasses.add(className);
|
|
557
|
+
elements.forEach((el) => {
|
|
558
|
+
collectionElements.add(el);
|
|
559
|
+
$(el).find("*").each((_, child) => {
|
|
560
|
+
collectionElements.add(child);
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
const collectionClassInfo = getPrimaryClass($(elements[0]).attr("class"));
|
|
564
|
+
const collectionSelector = collectionClassInfo ? `.${collectionClassInfo.selector}` : `.${className}`;
|
|
565
|
+
$first.find("img").each((_, img) => {
|
|
566
|
+
if (isInsideButton($, img)) return;
|
|
567
|
+
const $img = $(img);
|
|
568
|
+
const $parent = $img.parent();
|
|
569
|
+
const parentClassInfo = getPrimaryClass($parent.attr("class"));
|
|
570
|
+
if (parentClassInfo && parentClassInfo.fieldName.includes("image")) {
|
|
571
|
+
collectionFields.image = `.${parentClassInfo.selector}`;
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
$first.find("div").each((_, el) => {
|
|
576
|
+
const classInfo = getPrimaryClass($(el).attr("class"));
|
|
577
|
+
if (classInfo && classInfo.fieldName.includes("tag") && !classInfo.fieldName.includes("container")) {
|
|
578
|
+
collectionFields.tag = `.${classInfo.selector}`;
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
$first.find("h1, h2, h3, h4, h5, h6").first().each((_, el) => {
|
|
583
|
+
const classInfo = getPrimaryClass($(el).attr("class"));
|
|
584
|
+
if (classInfo) {
|
|
585
|
+
collectionFields.title = `.${classInfo.selector}`;
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
$first.find("p").first().each((_, el) => {
|
|
589
|
+
const classInfo = getPrimaryClass($(el).attr("class"));
|
|
590
|
+
if (classInfo) {
|
|
591
|
+
collectionFields.description = `.${classInfo.selector}`;
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
$first.find("a, NuxtLink").not(".c_button, .c_icon_button").each((_, el) => {
|
|
595
|
+
const $link = $(el);
|
|
596
|
+
const linkText = $link.text().trim();
|
|
597
|
+
if (linkText) {
|
|
598
|
+
const classInfo = getPrimaryClass($link.attr("class"));
|
|
599
|
+
collectionFields.link = classInfo ? `.${classInfo.selector}` : "a";
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
if (Object.keys(collectionFields).length > 0) {
|
|
604
|
+
let collectionName = className;
|
|
605
|
+
if (!collectionName.endsWith("s")) {
|
|
606
|
+
collectionName += "s";
|
|
607
|
+
}
|
|
608
|
+
detectedCollections[collectionName] = {
|
|
609
|
+
selector: collectionSelector,
|
|
610
|
+
fields: collectionFields
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
const $body = $("body");
|
|
616
|
+
$body.find("h1, h2, h3, h4, h5, h6").each((index, el) => {
|
|
617
|
+
if (collectionElements.has(el)) return;
|
|
618
|
+
const $el = $(el);
|
|
619
|
+
const text = $el.text().trim();
|
|
620
|
+
const classInfo = getPrimaryClass($el.attr("class"));
|
|
621
|
+
if (text) {
|
|
622
|
+
let fieldName;
|
|
623
|
+
let selector;
|
|
624
|
+
if (classInfo && !classInfo.fieldName.startsWith("heading_")) {
|
|
625
|
+
fieldName = classInfo.fieldName;
|
|
626
|
+
selector = `.${classInfo.selector}`;
|
|
627
|
+
} else {
|
|
628
|
+
const $parent = $el.closest('[class*="header"], [class*="hero"], [class*="cta"]').first();
|
|
629
|
+
const parentClassInfo = getPrimaryClass($parent.attr("class"));
|
|
630
|
+
const modifier = getContextModifier($, $el);
|
|
631
|
+
if (parentClassInfo) {
|
|
632
|
+
fieldName = modifier ? `${modifier}_${parentClassInfo.fieldName}` : parentClassInfo.fieldName;
|
|
633
|
+
selector = classInfo ? `.${classInfo.selector}` : `.${parentClassInfo.selector}`;
|
|
634
|
+
} else if (modifier) {
|
|
635
|
+
fieldName = `${modifier}_heading`;
|
|
636
|
+
selector = classInfo ? `.${classInfo.selector}` : el.tagName.toLowerCase();
|
|
637
|
+
} else {
|
|
638
|
+
fieldName = `heading_${index}`;
|
|
639
|
+
selector = classInfo ? `.${classInfo.selector}` : el.tagName.toLowerCase();
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
detectedFields[fieldName] = {
|
|
643
|
+
selector,
|
|
644
|
+
type: "plain",
|
|
645
|
+
editable: true
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
$body.find("p").each((_index, el) => {
|
|
650
|
+
if (collectionElements.has(el)) return;
|
|
651
|
+
const $el = $(el);
|
|
652
|
+
const text = $el.text().trim();
|
|
653
|
+
const classInfo = getPrimaryClass($el.attr("class"));
|
|
654
|
+
if (text && text.length > 20 && classInfo) {
|
|
655
|
+
const hasFormatting = $el.find("strong, em, b, i, a, NuxtLink").length > 0;
|
|
656
|
+
detectedFields[classInfo.fieldName] = {
|
|
657
|
+
selector: `.${classInfo.selector}`,
|
|
658
|
+
type: hasFormatting ? "rich" : "plain",
|
|
659
|
+
editable: true
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
$body.find("img").each((_index, el) => {
|
|
664
|
+
if (collectionElements.has(el)) return;
|
|
665
|
+
if (isInsideButton($, el)) return;
|
|
666
|
+
const $el = $(el);
|
|
667
|
+
if (isDecorativeImage($, $el)) return;
|
|
668
|
+
const $parent = $el.parent();
|
|
669
|
+
const parentClassInfo = getPrimaryClass($parent.attr("class"));
|
|
670
|
+
if (parentClassInfo) {
|
|
671
|
+
const fieldName = parentClassInfo.fieldName.includes("image") ? parentClassInfo.fieldName : `${parentClassInfo.fieldName}_image`;
|
|
672
|
+
detectedFields[fieldName] = {
|
|
673
|
+
selector: `.${parentClassInfo.selector}`,
|
|
674
|
+
type: "image",
|
|
675
|
+
editable: true
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
$body.find("NuxtLink.c_button, a.c_button, .c_button").each((_index, el) => {
|
|
680
|
+
if (collectionElements.has(el)) return;
|
|
681
|
+
const $el = $(el);
|
|
682
|
+
const text = $el.contents().filter(function() {
|
|
683
|
+
return this.type === "text" || this.type === "tag" && this.name === "div";
|
|
684
|
+
}).first().text().trim();
|
|
685
|
+
if (text && text.length > 2) {
|
|
686
|
+
const $parent = $el.closest('[class*="cta"]').first();
|
|
687
|
+
const parentClassInfo = getPrimaryClass($parent.attr("class"));
|
|
688
|
+
const fieldName = parentClassInfo ? `${parentClassInfo.fieldName}_button_text` : "button_text";
|
|
689
|
+
detectedFields[fieldName] = {
|
|
690
|
+
selector: `.c_button`,
|
|
691
|
+
type: "plain",
|
|
692
|
+
editable: true
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
return {
|
|
697
|
+
fields: detectedFields,
|
|
698
|
+
collections: detectedCollections
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
async function analyzeVuePages(pagesDir) {
|
|
702
|
+
const results = {};
|
|
703
|
+
const vueFiles = await fs5.readdir(pagesDir);
|
|
704
|
+
for (const file of vueFiles) {
|
|
705
|
+
if (file.endsWith(".vue")) {
|
|
706
|
+
const filePath = path6.join(pagesDir, file);
|
|
707
|
+
const content = await fs5.readFile(filePath, "utf-8");
|
|
708
|
+
const template = extractTemplateFromVue(content);
|
|
709
|
+
if (template) {
|
|
710
|
+
const pageName = file.replace(".vue", "");
|
|
711
|
+
results[pageName] = detectEditableFields(template);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return results;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// src/manifest.ts
|
|
719
|
+
async function generateManifest(pagesDir) {
|
|
720
|
+
const analyzed = await analyzeVuePages(pagesDir);
|
|
721
|
+
const pages = {};
|
|
722
|
+
for (const [pageName, detection] of Object.entries(analyzed)) {
|
|
723
|
+
pages[pageName] = {
|
|
724
|
+
fields: detection.fields,
|
|
725
|
+
collections: detection.collections,
|
|
726
|
+
meta: {
|
|
727
|
+
route: pageName === "index" ? "/" : `/${pageName}`
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
const manifest = {
|
|
732
|
+
version: "1.0",
|
|
733
|
+
pages
|
|
734
|
+
};
|
|
735
|
+
return manifest;
|
|
736
|
+
}
|
|
737
|
+
async function writeManifest(outputDir, manifest) {
|
|
738
|
+
const manifestPath = path7.join(outputDir, "cms-manifest.json");
|
|
739
|
+
await fs6.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// src/transformer.ts
|
|
743
|
+
function mapFieldTypeToStrapi(fieldType) {
|
|
744
|
+
const typeMap = {
|
|
745
|
+
plain: "string",
|
|
746
|
+
rich: "richtext",
|
|
747
|
+
html: "richtext",
|
|
748
|
+
image: "media",
|
|
749
|
+
link: "string",
|
|
750
|
+
email: "email",
|
|
751
|
+
phone: "string"
|
|
752
|
+
};
|
|
753
|
+
return typeMap[fieldType] || "string";
|
|
754
|
+
}
|
|
755
|
+
function pluralize(word) {
|
|
756
|
+
if (word.endsWith("s") || word.endsWith("x") || word.endsWith("z") || word.endsWith("ch") || word.endsWith("sh")) {
|
|
757
|
+
return word + "es";
|
|
758
|
+
}
|
|
759
|
+
if (word.endsWith("y") && word.length > 1) {
|
|
760
|
+
const secondLast = word[word.length - 2];
|
|
761
|
+
if (!"aeiou".includes(secondLast.toLowerCase())) {
|
|
762
|
+
return word.slice(0, -1) + "ies";
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return word + "s";
|
|
766
|
+
}
|
|
767
|
+
function pageToStrapiSchema(pageName, fields) {
|
|
768
|
+
const attributes = {};
|
|
769
|
+
for (const [fieldName, field] of Object.entries(fields)) {
|
|
770
|
+
attributes[fieldName] = {
|
|
771
|
+
type: mapFieldTypeToStrapi(field.type),
|
|
772
|
+
required: field.required || false
|
|
773
|
+
};
|
|
774
|
+
if (field.default) {
|
|
775
|
+
attributes[fieldName].default = field.default;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
const displayName = pageName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
779
|
+
const kebabCaseName = pageName;
|
|
780
|
+
const pluralName = pluralize(kebabCaseName);
|
|
781
|
+
return {
|
|
782
|
+
kind: "singleType",
|
|
783
|
+
collectionName: kebabCaseName,
|
|
784
|
+
info: {
|
|
785
|
+
singularName: kebabCaseName,
|
|
786
|
+
pluralName,
|
|
787
|
+
displayName
|
|
788
|
+
},
|
|
789
|
+
options: {
|
|
790
|
+
draftAndPublish: true
|
|
791
|
+
},
|
|
792
|
+
attributes
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
function collectionToStrapiSchema(collectionName, collection) {
|
|
796
|
+
const attributes = {};
|
|
797
|
+
for (const [fieldName, _selector] of Object.entries(collection.fields)) {
|
|
798
|
+
let type = "string";
|
|
799
|
+
if (fieldName === "image" || fieldName.includes("image")) {
|
|
800
|
+
type = "media";
|
|
801
|
+
} else if (fieldName === "description" || fieldName === "content") {
|
|
802
|
+
type = "richtext";
|
|
803
|
+
} else if (fieldName === "link" || fieldName === "url") {
|
|
804
|
+
type = "string";
|
|
805
|
+
} else if (fieldName === "title" || fieldName === "tag") {
|
|
806
|
+
type = "string";
|
|
807
|
+
}
|
|
808
|
+
attributes[fieldName] = {
|
|
809
|
+
type
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
const displayName = collectionName.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
813
|
+
const kebabCaseName = collectionName.replace(/_/g, "-");
|
|
814
|
+
const singularName = kebabCaseName.endsWith("s") ? kebabCaseName.slice(0, -1) : kebabCaseName;
|
|
815
|
+
return {
|
|
816
|
+
kind: "collectionType",
|
|
817
|
+
collectionName: kebabCaseName,
|
|
818
|
+
info: {
|
|
819
|
+
singularName,
|
|
820
|
+
pluralName: kebabCaseName,
|
|
821
|
+
displayName
|
|
822
|
+
},
|
|
823
|
+
options: {
|
|
824
|
+
draftAndPublish: true
|
|
825
|
+
},
|
|
826
|
+
attributes
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
function manifestToSchemas(manifest) {
|
|
830
|
+
const schemas = {};
|
|
831
|
+
for (const [pageName, page] of Object.entries(manifest.pages)) {
|
|
832
|
+
if (page.fields && Object.keys(page.fields).length > 0) {
|
|
833
|
+
schemas[pageName] = pageToStrapiSchema(pageName, page.fields);
|
|
834
|
+
}
|
|
835
|
+
if (page.collections) {
|
|
836
|
+
for (const [collectionName, collection] of Object.entries(page.collections)) {
|
|
837
|
+
schemas[collectionName] = collectionToStrapiSchema(collectionName, collection);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return schemas;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// src/schema-writer.ts
|
|
845
|
+
import fs7 from "fs-extra";
|
|
846
|
+
import path8 from "path";
|
|
847
|
+
async function writeStrapiSchema(outputDir, name, schema) {
|
|
848
|
+
const schemasDir = path8.join(outputDir, "cms-schemas");
|
|
849
|
+
await fs7.ensureDir(schemasDir);
|
|
850
|
+
const schemaPath = path8.join(schemasDir, `${name}.json`);
|
|
851
|
+
await fs7.writeFile(schemaPath, JSON.stringify(schema, null, 2), "utf-8");
|
|
852
|
+
}
|
|
853
|
+
async function writeAllSchemas(outputDir, schemas) {
|
|
854
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
855
|
+
await writeStrapiSchema(outputDir, name, schema);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
async function createStrapiReadme(outputDir) {
|
|
859
|
+
const readmePath = path8.join(outputDir, "cms-schemas", "README.md");
|
|
860
|
+
const content = `# CMS Schemas
|
|
861
|
+
|
|
862
|
+
Auto-generated Strapi content type schemas from your Webflow export.
|
|
863
|
+
|
|
864
|
+
## What's in this folder?
|
|
865
|
+
|
|
866
|
+
Each \`.json\` file is a Strapi content type schema:
|
|
867
|
+
|
|
868
|
+
- **Pages** (single types) - Unique pages like \`index.json\`, \`about.json\`
|
|
869
|
+
- **Collections** (collection types) - Repeating content like \`portfolio_cards.json\`
|
|
870
|
+
|
|
871
|
+
## How to use with Strapi
|
|
872
|
+
|
|
873
|
+
### Option 1: Manual Setup (Recommended for learning)
|
|
874
|
+
|
|
875
|
+
1. Start your Strapi project
|
|
876
|
+
2. In Strapi admin, go to **Content-Type Builder**
|
|
877
|
+
3. Create each content type manually using these schemas as reference
|
|
878
|
+
4. Match the field names and types
|
|
879
|
+
|
|
880
|
+
### Option 2: Automated Setup (Advanced)
|
|
881
|
+
|
|
882
|
+
Copy schemas to your Strapi project structure:
|
|
883
|
+
|
|
884
|
+
\`\`\`bash
|
|
885
|
+
# For each schema file, create the Strapi directory structure
|
|
886
|
+
# Example for index.json (single type):
|
|
887
|
+
mkdir -p strapi/src/api/index/content-types/index
|
|
888
|
+
cp cms-schemas/index.json strapi/src/api/index/content-types/index/schema.json
|
|
889
|
+
|
|
890
|
+
# Example for portfolio_cards.json (collection type):
|
|
891
|
+
mkdir -p strapi/src/api/portfolio-cards/content-types/portfolio-card
|
|
892
|
+
cp cms-schemas/portfolio_cards.json strapi/src/api/portfolio-cards/content-types/portfolio-card/schema.json
|
|
893
|
+
\`\`\`
|
|
894
|
+
|
|
895
|
+
Then restart Strapi - it will auto-create the content types.
|
|
896
|
+
|
|
897
|
+
## Schema Structure
|
|
898
|
+
|
|
899
|
+
Each schema defines:
|
|
900
|
+
- \`kind\`: "singleType" (unique page) or "collectionType" (repeating)
|
|
901
|
+
- \`attributes\`: Fields and their types (string, richtext, media, etc.)
|
|
902
|
+
- \`displayName\`: How it appears in Strapi admin
|
|
903
|
+
|
|
904
|
+
## Field Types
|
|
905
|
+
|
|
906
|
+
- \`string\` - Plain text
|
|
907
|
+
- \`richtext\` - Formatted text with HTML
|
|
908
|
+
- \`media\` - Image uploads
|
|
909
|
+
|
|
910
|
+
## Next Steps
|
|
911
|
+
|
|
912
|
+
1. Set up a Strapi project: \`npx create-strapi-app@latest my-strapi\`
|
|
913
|
+
2. Use these schemas to create content types
|
|
914
|
+
3. Populate content in Strapi admin
|
|
915
|
+
4. Connect your Nuxt app to Strapi API
|
|
916
|
+
|
|
917
|
+
## API Usage in Nuxt
|
|
918
|
+
|
|
919
|
+
Once Strapi is running with these content types:
|
|
920
|
+
|
|
921
|
+
\`\`\`typescript
|
|
922
|
+
// Fetch single type (e.g., home page)
|
|
923
|
+
const { data } = await $fetch('http://localhost:1337/api/index')
|
|
924
|
+
|
|
925
|
+
// Fetch collection type (e.g., portfolio cards)
|
|
926
|
+
const { data } = await $fetch('http://localhost:1337/api/portfolio-cards')
|
|
927
|
+
\`\`\`
|
|
928
|
+
`;
|
|
929
|
+
await fs7.writeFile(readmePath, content, "utf-8");
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// src/content-extractor.ts
|
|
933
|
+
import * as cheerio3 from "cheerio";
|
|
934
|
+
import path9 from "path";
|
|
935
|
+
function extractContentFromHTML(html, _pageName, pageManifest) {
|
|
936
|
+
const $ = cheerio3.load(html);
|
|
937
|
+
const content = {
|
|
938
|
+
fields: {},
|
|
939
|
+
collections: {}
|
|
940
|
+
};
|
|
941
|
+
if (pageManifest.fields) {
|
|
942
|
+
for (const [fieldName, field] of Object.entries(pageManifest.fields)) {
|
|
943
|
+
const selector = field.selector;
|
|
944
|
+
const element = $(selector).first();
|
|
945
|
+
if (element.length > 0) {
|
|
946
|
+
if (field.type === "image") {
|
|
947
|
+
const src = element.attr("src") || element.find("img").attr("src") || "";
|
|
948
|
+
content.fields[fieldName] = src;
|
|
949
|
+
} else {
|
|
950
|
+
const text = element.text().trim();
|
|
951
|
+
content.fields[fieldName] = text;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
if (pageManifest.collections) {
|
|
957
|
+
for (const [collectionName, collection] of Object.entries(pageManifest.collections)) {
|
|
958
|
+
const items = [];
|
|
959
|
+
const collectionElements = $(collection.selector);
|
|
960
|
+
collectionElements.each((_, elem) => {
|
|
961
|
+
const item = {};
|
|
962
|
+
const $elem = $(elem);
|
|
963
|
+
for (const [fieldName, fieldSelector] of Object.entries(collection.fields)) {
|
|
964
|
+
const fieldElement = $elem.find(fieldSelector).first();
|
|
965
|
+
if (fieldElement.length > 0) {
|
|
966
|
+
if (fieldName === "image" || fieldName.includes("image")) {
|
|
967
|
+
const src = fieldElement.attr("src") || fieldElement.find("img").attr("src") || "";
|
|
968
|
+
item[fieldName] = src;
|
|
969
|
+
} else if (fieldName === "link" || fieldName === "url") {
|
|
970
|
+
const href = fieldElement.attr("href") || "";
|
|
971
|
+
item[fieldName] = href;
|
|
972
|
+
} else {
|
|
973
|
+
const text = fieldElement.text().trim();
|
|
974
|
+
item[fieldName] = text;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
if (Object.keys(item).length > 0) {
|
|
979
|
+
items.push(item);
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
if (items.length > 0) {
|
|
983
|
+
content.collections[collectionName] = items;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return content;
|
|
988
|
+
}
|
|
989
|
+
function extractAllContent(htmlFiles, manifest) {
|
|
990
|
+
const extractedContent = {
|
|
991
|
+
pages: {}
|
|
992
|
+
};
|
|
993
|
+
for (const [pageName, pageManifest] of Object.entries(manifest.pages)) {
|
|
994
|
+
const html = htmlFiles.get(pageName);
|
|
995
|
+
if (html) {
|
|
996
|
+
const content = extractContentFromHTML(html, pageName, pageManifest);
|
|
997
|
+
extractedContent.pages[pageName] = content;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return extractedContent;
|
|
1001
|
+
}
|
|
1002
|
+
function normalizeImagePath(imageSrc) {
|
|
1003
|
+
if (!imageSrc) return "";
|
|
1004
|
+
if (imageSrc.startsWith("/")) return imageSrc;
|
|
1005
|
+
const filename = path9.basename(imageSrc);
|
|
1006
|
+
if (imageSrc.includes("images/")) {
|
|
1007
|
+
return `/images/${filename}`;
|
|
1008
|
+
}
|
|
1009
|
+
return `/${filename}`;
|
|
1010
|
+
}
|
|
1011
|
+
function formatForStrapi(extracted) {
|
|
1012
|
+
const seedData = {};
|
|
1013
|
+
for (const [pageName, content] of Object.entries(extracted.pages)) {
|
|
1014
|
+
if (Object.keys(content.fields).length > 0) {
|
|
1015
|
+
const formattedFields = {};
|
|
1016
|
+
for (const [fieldName, value] of Object.entries(content.fields)) {
|
|
1017
|
+
if (fieldName.includes("image") || fieldName.includes("bg")) {
|
|
1018
|
+
formattedFields[fieldName] = normalizeImagePath(value);
|
|
1019
|
+
} else {
|
|
1020
|
+
formattedFields[fieldName] = value;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
seedData[pageName] = formattedFields;
|
|
1024
|
+
}
|
|
1025
|
+
for (const [collectionName, items] of Object.entries(content.collections)) {
|
|
1026
|
+
const formattedItems = items.map((item) => {
|
|
1027
|
+
const formattedItem = {};
|
|
1028
|
+
for (const [fieldName, value] of Object.entries(item)) {
|
|
1029
|
+
if (fieldName === "image" || fieldName.includes("image")) {
|
|
1030
|
+
formattedItem[fieldName] = normalizeImagePath(value);
|
|
1031
|
+
} else {
|
|
1032
|
+
formattedItem[fieldName] = value;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
return formattedItem;
|
|
1036
|
+
});
|
|
1037
|
+
seedData[collectionName] = formattedItems;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
return seedData;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// src/seed-writer.ts
|
|
1044
|
+
import fs8 from "fs-extra";
|
|
1045
|
+
import path10 from "path";
|
|
1046
|
+
async function writeSeedData(outputDir, seedData) {
|
|
1047
|
+
const seedDir = path10.join(outputDir, "cms-seed");
|
|
1048
|
+
await fs8.ensureDir(seedDir);
|
|
1049
|
+
const seedPath = path10.join(seedDir, "seed-data.json");
|
|
1050
|
+
await fs8.writeJson(seedPath, seedData, { spaces: 2 });
|
|
1051
|
+
}
|
|
1052
|
+
async function createSeedReadme(outputDir) {
|
|
1053
|
+
const readmePath = path10.join(outputDir, "cms-seed", "README.md");
|
|
1054
|
+
const content = `# CMS Seed Data
|
|
1055
|
+
|
|
1056
|
+
Auto-extracted content from your Webflow export, ready to seed into Strapi.
|
|
1057
|
+
|
|
1058
|
+
## What's in this folder?
|
|
1059
|
+
|
|
1060
|
+
\`seed-data.json\` contains the actual content extracted from your HTML:
|
|
1061
|
+
- **Single types** - Page-specific content (homepage, about page, etc.)
|
|
1062
|
+
- **Collection types** - Repeating items (portfolio cards, team members, etc.)
|
|
1063
|
+
|
|
1064
|
+
## Structure
|
|
1065
|
+
|
|
1066
|
+
\`\`\`json
|
|
1067
|
+
{
|
|
1068
|
+
"index": {
|
|
1069
|
+
"hero_heading_container": "Actual heading from HTML",
|
|
1070
|
+
"hero_bg_image": "/images/hero.jpg",
|
|
1071
|
+
...
|
|
1072
|
+
},
|
|
1073
|
+
"portfolio_cards": [
|
|
1074
|
+
{
|
|
1075
|
+
"image": "/images/card1.jpg",
|
|
1076
|
+
"tag": "Technology",
|
|
1077
|
+
"description": "Card description"
|
|
1078
|
+
}
|
|
1079
|
+
]
|
|
1080
|
+
}
|
|
1081
|
+
\`\`\`
|
|
1082
|
+
|
|
1083
|
+
## How to Seed Strapi
|
|
1084
|
+
|
|
1085
|
+
### Option 1: Manual Entry
|
|
1086
|
+
1. Open Strapi admin panel
|
|
1087
|
+
2. Go to Content Manager
|
|
1088
|
+
3. Create entries using the data from \`seed-data.json\`
|
|
1089
|
+
|
|
1090
|
+
### Option 2: Automated Seeding (Coming Soon)
|
|
1091
|
+
We'll provide a seeding script that:
|
|
1092
|
+
1. Uploads images to Strapi media library
|
|
1093
|
+
2. Creates content entries via Strapi API
|
|
1094
|
+
3. Handles relationships between content types
|
|
1095
|
+
|
|
1096
|
+
## Image Paths
|
|
1097
|
+
|
|
1098
|
+
Image paths in the seed data reference files in your Nuxt \`public/\` directory:
|
|
1099
|
+
- \`/images/hero.jpg\` \u2192 \`public/images/hero.jpg\`
|
|
1100
|
+
|
|
1101
|
+
When seeding Strapi, these images will be uploaded to Strapi's media library.
|
|
1102
|
+
|
|
1103
|
+
## Next Steps
|
|
1104
|
+
|
|
1105
|
+
1. Review the extracted data for accuracy
|
|
1106
|
+
2. Set up your Strapi instance with the schemas from \`cms-schemas/\`
|
|
1107
|
+
3. Use this seed data to populate your CMS
|
|
1108
|
+
`;
|
|
1109
|
+
await fs8.writeFile(readmePath, content, "utf-8");
|
|
1110
|
+
}
|
|
1111
|
+
|
|
388
1112
|
// src/converter.ts
|
|
389
1113
|
async function convertWebflowExport(options) {
|
|
390
1114
|
const { inputDir, outputDir, boilerplate } = options;
|
|
@@ -393,7 +1117,7 @@ async function convertWebflowExport(options) {
|
|
|
393
1117
|
console.log(pc3.dim(`Output: ${outputDir}`));
|
|
394
1118
|
try {
|
|
395
1119
|
await setupBoilerplate(boilerplate, outputDir);
|
|
396
|
-
const inputExists = await
|
|
1120
|
+
const inputExists = await fs9.pathExists(inputDir);
|
|
397
1121
|
if (!inputExists) {
|
|
398
1122
|
throw new Error(`Input directory not found: ${inputDir}`);
|
|
399
1123
|
}
|
|
@@ -409,10 +1133,17 @@ async function convertWebflowExport(options) {
|
|
|
409
1133
|
console.log(pc3.blue("\n\u{1F50D} Finding HTML files..."));
|
|
410
1134
|
const htmlFiles = await findHTMLFiles(inputDir);
|
|
411
1135
|
console.log(pc3.green(` \u2713 Found ${htmlFiles.length} HTML files`));
|
|
1136
|
+
const htmlContentMap = /* @__PURE__ */ new Map();
|
|
1137
|
+
for (const htmlFile of htmlFiles) {
|
|
1138
|
+
const html = await readHTMLFile(inputDir, htmlFile);
|
|
1139
|
+
const pageName = htmlFile.replace(".html", "").replace(/\//g, "-");
|
|
1140
|
+
htmlContentMap.set(pageName, html);
|
|
1141
|
+
console.log(pc3.dim(` Stored: ${pageName} from ${htmlFile}`));
|
|
1142
|
+
}
|
|
412
1143
|
console.log(pc3.blue("\n\u2699\uFE0F Converting HTML to Vue components..."));
|
|
413
1144
|
let allEmbeddedStyles = "";
|
|
414
1145
|
for (const htmlFile of htmlFiles) {
|
|
415
|
-
const html =
|
|
1146
|
+
const html = htmlContentMap.get(htmlFile.replace(".html", "").replace(/\//g, "-"));
|
|
416
1147
|
const parsed = parseHTML(html, htmlFile);
|
|
417
1148
|
if (parsed.embeddedStyles) {
|
|
418
1149
|
allEmbeddedStyles += `
|
|
@@ -427,6 +1158,41 @@ ${parsed.embeddedStyles}
|
|
|
427
1158
|
console.log(pc3.green(` \u2713 Created ${htmlFile.replace(".html", ".vue")}`));
|
|
428
1159
|
}
|
|
429
1160
|
await formatVueFiles(outputDir);
|
|
1161
|
+
console.log(pc3.blue("\n\u{1F50D} Analyzing pages for CMS fields..."));
|
|
1162
|
+
const pagesDir = path11.join(outputDir, "pages");
|
|
1163
|
+
const manifest = await generateManifest(pagesDir);
|
|
1164
|
+
await writeManifest(outputDir, manifest);
|
|
1165
|
+
const totalFields = Object.values(manifest.pages).reduce(
|
|
1166
|
+
(sum, page) => sum + Object.keys(page.fields || {}).length,
|
|
1167
|
+
0
|
|
1168
|
+
);
|
|
1169
|
+
const totalCollections = Object.values(manifest.pages).reduce(
|
|
1170
|
+
(sum, page) => sum + Object.keys(page.collections || {}).length,
|
|
1171
|
+
0
|
|
1172
|
+
);
|
|
1173
|
+
console.log(pc3.green(` \u2713 Detected ${totalFields} fields across ${Object.keys(manifest.pages).length} pages`));
|
|
1174
|
+
console.log(pc3.green(` \u2713 Detected ${totalCollections} collections`));
|
|
1175
|
+
console.log(pc3.green(" \u2713 Generated cms-manifest.json"));
|
|
1176
|
+
console.log(pc3.blue("\n\u{1F4DD} Extracting content from HTML..."));
|
|
1177
|
+
console.log(pc3.dim(` HTML map has ${htmlContentMap.size} entries`));
|
|
1178
|
+
console.log(pc3.dim(` Manifest has ${Object.keys(manifest.pages).length} pages`));
|
|
1179
|
+
const extractedContent = extractAllContent(htmlContentMap, manifest);
|
|
1180
|
+
const seedData = formatForStrapi(extractedContent);
|
|
1181
|
+
await writeSeedData(outputDir, seedData);
|
|
1182
|
+
await createSeedReadme(outputDir);
|
|
1183
|
+
const pagesWithContent = Object.keys(seedData).filter((key) => {
|
|
1184
|
+
const data = seedData[key];
|
|
1185
|
+
if (Array.isArray(data)) return data.length > 0;
|
|
1186
|
+
return Object.keys(data).length > 0;
|
|
1187
|
+
}).length;
|
|
1188
|
+
console.log(pc3.green(` \u2713 Extracted content from ${pagesWithContent} pages`));
|
|
1189
|
+
console.log(pc3.green(` \u2713 Generated cms-seed/seed-data.json`));
|
|
1190
|
+
console.log(pc3.blue("\n\u{1F4CB} Generating Strapi schemas..."));
|
|
1191
|
+
const schemas = manifestToSchemas(manifest);
|
|
1192
|
+
await writeAllSchemas(outputDir, schemas);
|
|
1193
|
+
await createStrapiReadme(outputDir);
|
|
1194
|
+
console.log(pc3.green(` \u2713 Generated ${Object.keys(schemas).length} Strapi content types`));
|
|
1195
|
+
console.log(pc3.dim(" View schemas in: cms-schemas/"));
|
|
430
1196
|
if (allEmbeddedStyles.trim()) {
|
|
431
1197
|
console.log(pc3.blue("\n\u2728 Writing embedded styles..."));
|
|
432
1198
|
const dedupedStyles = deduplicateStyles(allEmbeddedStyles);
|
|
@@ -441,14 +1207,24 @@ ${parsed.embeddedStyles}
|
|
|
441
1207
|
await updateNuxtConfig(outputDir, assets.css);
|
|
442
1208
|
console.log(pc3.green(" \u2713 Config updated"));
|
|
443
1209
|
} catch (error) {
|
|
444
|
-
console.log(pc3.yellow(" \u26A0
|
|
1210
|
+
console.log(pc3.yellow(" \u26A0 Could not update nuxt.config.ts automatically"));
|
|
445
1211
|
console.log(pc3.dim(" Please add CSS files manually"));
|
|
446
1212
|
}
|
|
1213
|
+
console.log(pc3.blue("\n\u{1F3A8} Setting up editor overlay..."));
|
|
1214
|
+
await createEditorPlugin(outputDir);
|
|
1215
|
+
await addEditorDependency(outputDir);
|
|
1216
|
+
await createSaveEndpoint(outputDir);
|
|
1217
|
+
console.log(pc3.green(" \u2713 Editor plugin created"));
|
|
1218
|
+
console.log(pc3.green(" \u2713 Editor dependency added"));
|
|
1219
|
+
console.log(pc3.green(" \u2713 Save endpoint created"));
|
|
447
1220
|
console.log(pc3.green("\n\u2705 Conversion completed successfully!"));
|
|
448
1221
|
console.log(pc3.cyan("\n\u{1F4CB} Next steps:"));
|
|
449
1222
|
console.log(pc3.dim(` 1. cd ${outputDir}`));
|
|
450
|
-
console.log(pc3.dim(" 2.
|
|
451
|
-
console.log(pc3.dim(" 3.
|
|
1223
|
+
console.log(pc3.dim(" 2. Review cms-manifest.json and cms-seed/seed-data.json"));
|
|
1224
|
+
console.log(pc3.dim(" 3. Set up Strapi and install schemas from cms-schemas/"));
|
|
1225
|
+
console.log(pc3.dim(" 4. Seed Strapi with data from cms-seed/"));
|
|
1226
|
+
console.log(pc3.dim(" 5. pnpm install && pnpm dev"));
|
|
1227
|
+
console.log(pc3.dim(" 6. Visit http://localhost:3000?preview=true to edit inline!"));
|
|
452
1228
|
} catch (error) {
|
|
453
1229
|
console.error(pc3.red("\n\u274C Conversion failed:"));
|
|
454
1230
|
console.error(pc3.red(error instanceof Error ? error.message : String(error)));
|
|
@@ -456,31 +1232,16 @@ ${parsed.embeddedStyles}
|
|
|
456
1232
|
}
|
|
457
1233
|
}
|
|
458
1234
|
|
|
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
1235
|
// src/generator.ts
|
|
470
1236
|
async function generateSchemas(_manifestPath, _cmsType) {
|
|
471
1237
|
throw new Error("Not yet implemented");
|
|
472
1238
|
}
|
|
473
|
-
|
|
474
|
-
// src/transformer.ts
|
|
475
|
-
function manifestToSchema(_manifest, _cmsType) {
|
|
476
|
-
throw new Error("Not yet implemented");
|
|
477
|
-
}
|
|
478
1239
|
export {
|
|
479
1240
|
convertWebflowExport,
|
|
480
1241
|
detectEditableFields,
|
|
481
1242
|
generateManifest,
|
|
482
1243
|
generateSchemas,
|
|
483
|
-
|
|
1244
|
+
manifestToSchemas,
|
|
484
1245
|
setupBoilerplate
|
|
485
1246
|
};
|
|
486
1247
|
//# sourceMappingURL=index.mjs.map
|