@litodocs/cli 1.2.0 → 1.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@litodocs/cli",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Beautiful documentation sites from Markdown. Fast, simple, and open-source.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,6 +13,46 @@ const { copy, ensureDir, readFile, writeFile, pathExists, readJson } = pkg;
13
13
  import { join, relative, basename, extname } from 'path';
14
14
  import { readdir } from 'fs/promises';
15
15
 
16
+ /**
17
+ * Add is:inline to all <script> and <style> tags in HTML so Astro ships
18
+ * them as-is. Without this, Astro treats scripts as ES modules (scoping
19
+ * declarations, breaking onclick handlers) and scopes styles (breaking
20
+ * global CSS like :root variables, animations, etc.).
21
+ */
22
+ function inlineForAstro(html) {
23
+ // Add is:inline to <script> tags that don't already have it
24
+ html = html.replace(/<script(?![^>]*is:inline)([^>]*>)/gi, '<script is:inline$1');
25
+ // Add is:inline to <style> tags that don't already have is:inline or is:global
26
+ html = html.replace(/<style(?![^>]*is:(?:inline|global))([^>]*>)/gi, '<style is:inline$1');
27
+ return html;
28
+ }
29
+
30
+ /**
31
+ * Check if HTML is a full document (has <html> or <!doctype>).
32
+ * If so, extract head content, body content, and html/body attributes
33
+ * so we can merge them into the Astro template properly.
34
+ */
35
+ function parseFullHtmlDocument(html) {
36
+ const isFullDoc = /<!doctype\s+html|<html[\s>]/i.test(html);
37
+ if (!isFullDoc) return null;
38
+
39
+ // Extract <html> tag attributes
40
+ const htmlTagMatch = html.match(/<html([^>]*)>/i);
41
+ const htmlAttrs = htmlTagMatch ? htmlTagMatch[1].trim() : '';
42
+
43
+ // Extract <head> inner content
44
+ const headMatch = html.match(/<head[^>]*>([\s\S]*)<\/head>/i);
45
+ const headContent = headMatch ? headMatch[1].trim() : '';
46
+
47
+ // Extract <body> tag attributes and inner content
48
+ const bodyTagMatch = html.match(/<body([^>]*)>/i);
49
+ const bodyAttrs = bodyTagMatch ? bodyTagMatch[1].trim() : '';
50
+ const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
51
+ const bodyContent = bodyMatch ? bodyMatch[1].trim() : '';
52
+
53
+ return { htmlAttrs, headContent, bodyContent, bodyAttrs };
54
+ }
55
+
16
56
  /**
17
57
  * Landing page types
18
58
  */
@@ -309,6 +349,9 @@ async function generateAstroLanding(projectDir, landingData) {
309
349
 
310
350
  let htmlContent = await readFile(join(sourcePath, mainHtml), 'utf-8');
311
351
 
352
+ // Make all <script> tags in the user's HTML pass through Astro untouched
353
+ htmlContent = inlineForAstro(htmlContent);
354
+
312
355
  // Read CSS files and write to a separate file
313
356
  let cssContent = '';
314
357
  for (const cssFile of cssFiles) {
@@ -327,7 +370,36 @@ async function generateAstroLanding(projectDir, landingData) {
327
370
  jsContent += `// ${jsFile}\n${js}\n\n`;
328
371
  }
329
372
 
330
- // Determine header/footer: hidden ('__hidden__'), custom (string HTML), or default (null)
373
+ // Check if the user's HTML is a full document (has <html>, <head>, <body>)
374
+ const parsed = parseFullHtmlDocument(htmlContent);
375
+
376
+ let astroContent;
377
+
378
+ if (parsed) {
379
+ // Full HTML document: merge the user's head/body into the Astro page
380
+ // instead of nesting an entire HTML document inside another one.
381
+ astroContent = generateAstroFromFullDoc(parsed, { cssFiles, jsContent, navbarContent, footerContent });
382
+ } else {
383
+ // HTML fragment: wrap it in a full Astro page
384
+ astroContent = generateAstroFromFragment(htmlContent, { jsContent, navbarContent, footerContent });
385
+ }
386
+
387
+ // Write to index.astro
388
+ const indexPath = join(projectDir, 'src', 'pages', 'index.astro');
389
+ await writeFile(indexPath, astroContent, 'utf-8');
390
+
391
+ // Copy assets if they exist
392
+ await copyLandingAssets(sourcePath, projectDir);
393
+ }
394
+
395
+ /**
396
+ * Generate Astro page from a full HTML document.
397
+ * Extracts <head> and <body> content, preserves the user's structure.
398
+ */
399
+ function generateAstroFromFullDoc(parsed, { cssFiles, jsContent, navbarContent, footerContent }) {
400
+ const { htmlAttrs, headContent, bodyContent, bodyAttrs } = parsed;
401
+
402
+ // Determine header/footer rendering
331
403
  const navbarIsHidden = navbarContent === '__hidden__';
332
404
  const footerIsHidden = footerContent === '__hidden__';
333
405
  const hasCustomNavbar = !navbarIsHidden && !!navbarContent;
@@ -338,16 +410,64 @@ async function generateAstroLanding(projectDir, landingData) {
338
410
  const headerRender = navbarIsHidden
339
411
  ? ''
340
412
  : hasCustomNavbar
341
- ? `<header class="landing-custom-navbar">\n ${navbarContent}\n </header>`
413
+ ? `<header class="landing-custom-navbar">\n ${inlineForAstro(navbarContent)}\n </header>`
342
414
  : '<Header />';
343
415
  const footerRender = footerIsHidden
344
416
  ? ''
345
417
  : hasCustomFooter
346
- ? `<footer class="landing-custom-footer">\n ${footerContent}\n </footer>`
418
+ ? `<footer class="landing-custom-footer">\n ${inlineForAstro(footerContent)}\n </footer>`
347
419
  : '<Footer />';
348
420
 
349
- // Generate standalone Astro component
350
- const astroContent = `---
421
+ return `---
422
+ // Custom landing page - auto-generated by Lito CLI
423
+ // Source: _landing/ folder (full HTML document)
424
+ import '../styles/landing.css';
425
+ ${headerImport}
426
+ ${footerImport}
427
+ ---
428
+
429
+ <!doctype html>
430
+ <html ${htmlAttrs}>
431
+ <head>
432
+ ${headContent}
433
+ </head>
434
+ <body ${bodyAttrs}>
435
+ ${headerRender}
436
+
437
+ ${bodyContent}
438
+
439
+ ${footerRender}
440
+
441
+ ${jsContent ? `<script is:inline>\n${jsContent}\n</script>` : ''}
442
+ </body>
443
+ </html>
444
+ `;
445
+ }
446
+
447
+ /**
448
+ * Generate Astro page from an HTML fragment.
449
+ * Wraps it in a full Astro page with Lito's defaults.
450
+ */
451
+ function generateAstroFromFragment(htmlContent, { jsContent, navbarContent, footerContent }) {
452
+ const navbarIsHidden = navbarContent === '__hidden__';
453
+ const footerIsHidden = footerContent === '__hidden__';
454
+ const hasCustomNavbar = !navbarIsHidden && !!navbarContent;
455
+ const hasCustomFooter = !footerIsHidden && !!footerContent;
456
+
457
+ const headerImport = navbarIsHidden || hasCustomNavbar ? '' : "import Header from '../components/Header.astro';";
458
+ const footerImport = footerIsHidden || hasCustomFooter ? '' : "import Footer from '../components/Footer.astro';";
459
+ const headerRender = navbarIsHidden
460
+ ? ''
461
+ : hasCustomNavbar
462
+ ? `<header class="landing-custom-navbar">\n ${inlineForAstro(navbarContent)}\n </header>`
463
+ : '<Header />';
464
+ const footerRender = footerIsHidden
465
+ ? ''
466
+ : hasCustomFooter
467
+ ? `<footer class="landing-custom-footer">\n ${inlineForAstro(footerContent)}\n </footer>`
468
+ : '<Footer />';
469
+
470
+ return `---
351
471
  // Custom landing page - auto-generated by Lito CLI
352
472
  // Source: _landing/ folder
353
473
  import '../styles/global.css';
@@ -388,17 +508,10 @@ const config = await getConfigFile();
388
508
 
389
509
  ${footerRender}
390
510
 
391
- ${jsContent ? `<script>\n${jsContent}\n</script>` : ''}
511
+ ${jsContent ? `<script is:inline>\n${jsContent}\n</script>` : ''}
392
512
  </body>
393
513
  </html>
394
514
  `;
395
-
396
- // Write to index.astro
397
- const indexPath = join(projectDir, 'src', 'pages', 'index.astro');
398
- await writeFile(indexPath, astroContent, 'utf-8');
399
-
400
- // Copy assets if they exist
401
- await copyLandingAssets(sourcePath, projectDir);
402
515
  }
403
516
 
404
517
  /**
@@ -708,12 +821,12 @@ async function generateAstroSectionsLanding(projectDir, landingData) {
708
821
  const headerRender = navbarIsHidden
709
822
  ? ''
710
823
  : hasCustomNavbar
711
- ? `<header class="landing-custom-navbar">\n ${navbarContent}\n </header>`
824
+ ? `<header class="landing-custom-navbar">\n ${inlineForAstro(navbarContent)}\n </header>`
712
825
  : '<Header />';
713
826
  const footerRender = footerIsHidden
714
827
  ? ''
715
828
  : hasCustomFooter
716
- ? `<footer class="landing-custom-footer">\n ${footerContent}\n </footer>`
829
+ ? `<footer class="landing-custom-footer">\n ${inlineForAstro(footerContent)}\n </footer>`
717
830
  : '<Footer />';
718
831
 
719
832
  const astroContent = `---