@litodocs/cli 1.0.0 → 1.2.0

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.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Beautiful documentation sites from Markdown. Fast, simple, and open-source.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -60,6 +60,18 @@ const DEFAULT_FRAMEWORK_CONFIGS = {
60
60
  // HMR trigger file that will be updated when content changes
61
61
  hmrTriggerFile: 'src/.lito-hmr-trigger.js',
62
62
  },
63
+ nuxt: {
64
+ name: 'nuxt',
65
+ commands: {
66
+ dev: ['nuxt', ['dev']],
67
+ build: ['nuxt', ['build']],
68
+ },
69
+ contentDir: 'content',
70
+ publicDir: 'public',
71
+ configFile: 'nuxt.config.ts',
72
+ layoutInjection: false,
73
+ useMDX: true,
74
+ },
63
75
  };
64
76
 
65
77
  /**
@@ -99,6 +111,11 @@ export async function detectFramework(projectDir) {
99
111
  return DEFAULT_FRAMEWORK_CONFIGS.astro;
100
112
  }
101
113
 
114
+ if (await pathExists(join(projectDir, 'nuxt.config.ts')) ||
115
+ await pathExists(join(projectDir, 'nuxt.config.js'))) {
116
+ return DEFAULT_FRAMEWORK_CONFIGS.nuxt;
117
+ }
118
+
102
119
  if (await pathExists(join(projectDir, 'next.config.js')) ||
103
120
  await pathExists(join(projectDir, 'next.config.mjs')) ||
104
121
  await pathExists(join(projectDir, 'next.config.ts'))) {
@@ -113,6 +130,7 @@ export async function detectFramework(projectDir) {
113
130
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
114
131
 
115
132
  if (deps['next']) return DEFAULT_FRAMEWORK_CONFIGS.next;
133
+ if (deps['nuxt']) return DEFAULT_FRAMEWORK_CONFIGS.nuxt;
116
134
  if (deps['astro']) return DEFAULT_FRAMEWORK_CONFIGS.astro;
117
135
  if (deps['vue'] && deps['vite']) return DEFAULT_FRAMEWORK_CONFIGS.vue;
118
136
  if (deps['react'] && deps['vite']) return DEFAULT_FRAMEWORK_CONFIGS.react;
@@ -162,6 +180,8 @@ export async function runFrameworkDev(projectDir, frameworkConfig, port = '4321'
162
180
  devArgs.push('--port', port);
163
181
  } else if (frameworkConfig.name === 'next') {
164
182
  devArgs.push('-p', port);
183
+ } else if (frameworkConfig.name === 'nuxt') {
184
+ devArgs.push('--port', port);
165
185
  } else {
166
186
  // Vite-based frameworks
167
187
  devArgs.push('--port', port);
@@ -189,6 +209,8 @@ export function getOutputDir(frameworkConfig) {
189
209
  return 'dist';
190
210
  case 'next':
191
211
  return '.next';
212
+ case 'nuxt':
213
+ return '.output/public';
192
214
  case 'react':
193
215
  case 'vue':
194
216
  return 'dist';
@@ -202,7 +224,7 @@ export function getOutputDir(frameworkConfig) {
202
224
  */
203
225
  export function needsSearchIndex(frameworkConfig) {
204
226
  // Only static-output frameworks need pagefind
205
- return ['astro', 'react', 'vue'].includes(frameworkConfig.name);
227
+ return ['astro', 'react', 'vue', 'nuxt'].includes(frameworkConfig.name);
206
228
  }
207
229
 
208
230
  export { DEFAULT_FRAMEWORK_CONFIGS };
@@ -98,11 +98,17 @@ export async function syncCustomLanding(sourcePath, projectDir, frameworkConfig,
98
98
  // Read all files from _landing/
99
99
  const files = await readdir(landingSource, { withFileTypes: true });
100
100
 
101
+ // Check if navbar/footer are explicitly hidden
102
+ const navbarHidden = landingConfig.navbar === false;
103
+ const footerHidden = landingConfig.footer === false;
104
+
101
105
  // Separate files by type
102
106
  const htmlFiles = [];
103
107
  const cssFiles = [];
104
108
  const jsFiles = [];
105
109
  const assetFiles = [];
110
+ let navbarHtml = null;
111
+ let footerHtml = null;
106
112
 
107
113
  for (const file of files) {
108
114
  if (file.isDirectory()) {
@@ -114,8 +120,17 @@ export async function syncCustomLanding(sourcePath, projectDir, frameworkConfig,
114
120
  }
115
121
 
116
122
  const ext = extname(file.name).toLowerCase();
123
+ const name = basename(file.name, ext).toLowerCase();
124
+
117
125
  if (ext === '.html' || ext === '.htm') {
118
- htmlFiles.push(file.name);
126
+ // Detect custom navbar/footer HTML files (skip if hidden)
127
+ if (!navbarHidden && (name === 'navbar' || name === 'nav' || name === 'header')) {
128
+ navbarHtml = file.name;
129
+ } else if (!footerHidden && name === 'footer') {
130
+ footerHtml = file.name;
131
+ } else if (!((name === 'navbar' || name === 'nav' || name === 'header') || name === 'footer')) {
132
+ htmlFiles.push(file.name);
133
+ }
119
134
  } else if (ext === '.css') {
120
135
  cssFiles.push(file.name);
121
136
  } else if (ext === '.js' || ext === '.mjs') {
@@ -123,6 +138,31 @@ export async function syncCustomLanding(sourcePath, projectDir, frameworkConfig,
123
138
  }
124
139
  }
125
140
 
141
+ // Read custom navbar/footer content if present
142
+ let navbarContent = navbarHidden ? '__hidden__' : null;
143
+ let footerContent = footerHidden ? '__hidden__' : null;
144
+
145
+ if (!navbarHidden && navbarHtml) {
146
+ navbarContent = await readFile(join(landingSource, navbarHtml), 'utf-8');
147
+ }
148
+ if (!footerHidden && footerHtml) {
149
+ footerContent = await readFile(join(landingSource, footerHtml), 'utf-8');
150
+ }
151
+
152
+ // Also check config for custom navbar/footer (skip if hidden)
153
+ if (!navbarHidden && !navbarContent && landingConfig.navbar?.html) {
154
+ const navPath = join(landingSource, '..', landingConfig.navbar.html);
155
+ if (await pathExists(navPath)) {
156
+ navbarContent = await readFile(navPath, 'utf-8');
157
+ }
158
+ }
159
+ if (!footerHidden && !footerContent && landingConfig.footer?.html) {
160
+ const footerPath = join(landingSource, '..', landingConfig.footer.html);
161
+ if (await pathExists(footerPath)) {
162
+ footerContent = await readFile(footerPath, 'utf-8');
163
+ }
164
+ }
165
+
126
166
  // Generate landing page based on framework
127
167
  await generateLandingForFramework(
128
168
  projectDir,
@@ -133,6 +173,8 @@ export async function syncCustomLanding(sourcePath, projectDir, frameworkConfig,
133
173
  cssFiles,
134
174
  jsFiles,
135
175
  assetFiles,
176
+ navbarContent,
177
+ footerContent,
136
178
  config: landingConfig,
137
179
  }
138
180
  );
@@ -170,12 +212,55 @@ export async function syncSectionsLanding(sourcePath, projectDir, frameworkConfi
170
212
  }
171
213
  }
172
214
 
215
+ // Check if navbar/footer are explicitly hidden
216
+ const navbarHidden = landingConfig.navbar === false;
217
+ const footerHidden = landingConfig.footer === false;
218
+
219
+ // Check for custom navbar/footer in _landing/ folder
220
+ const landingSource = join(sourcePath, landingConfig.source || '_landing');
221
+ let navbarContent = navbarHidden ? '__hidden__' : null;
222
+ let footerContent = footerHidden ? '__hidden__' : null;
223
+
224
+ if (!navbarHidden) {
225
+ const navbarNames = ['navbar.html', 'nav.html', 'header.html'];
226
+ for (const name of navbarNames) {
227
+ const navPath = join(landingSource, name);
228
+ if (await pathExists(navPath)) {
229
+ navbarContent = await readFile(navPath, 'utf-8');
230
+ break;
231
+ }
232
+ }
233
+ }
234
+
235
+ if (!footerHidden) {
236
+ const footerPath = join(landingSource, 'footer.html');
237
+ if (await pathExists(footerPath)) {
238
+ footerContent = await readFile(footerPath, 'utf-8');
239
+ }
240
+ }
241
+
242
+ // Also check config for custom navbar/footer (skip if hidden)
243
+ if (!navbarHidden && !navbarContent && landingConfig.navbar?.html) {
244
+ const navPath = join(sourcePath, landingConfig.navbar.html);
245
+ if (await pathExists(navPath)) {
246
+ navbarContent = await readFile(navPath, 'utf-8');
247
+ }
248
+ }
249
+ if (!footerHidden && !footerContent && landingConfig.footer?.html) {
250
+ const fPath = join(sourcePath, landingConfig.footer.html);
251
+ if (await pathExists(fPath)) {
252
+ footerContent = await readFile(fPath, 'utf-8');
253
+ }
254
+ }
255
+
173
256
  // Generate sections landing for framework
174
257
  await generateSectionsLandingForFramework(
175
258
  projectDir,
176
259
  frameworkConfig,
177
260
  {
178
261
  sections: processedSections,
262
+ navbarContent,
263
+ footerContent,
179
264
  config: landingConfig,
180
265
  }
181
266
  );
@@ -213,7 +298,7 @@ async function generateLandingForFramework(projectDir, frameworkConfig, landingD
213
298
  * Generate Astro landing page (standalone, no template imports)
214
299
  */
215
300
  async function generateAstroLanding(projectDir, landingData) {
216
- const { sourcePath, htmlFiles, cssFiles, jsFiles, config } = landingData;
301
+ const { sourcePath, htmlFiles, cssFiles, jsFiles, navbarContent, footerContent, config } = landingData;
217
302
 
218
303
  // Read main HTML file
219
304
  const mainHtml = htmlFiles.includes('index.html') ? 'index.html' : htmlFiles[0];
@@ -224,13 +309,17 @@ async function generateAstroLanding(projectDir, landingData) {
224
309
 
225
310
  let htmlContent = await readFile(join(sourcePath, mainHtml), 'utf-8');
226
311
 
227
- // Read CSS files
312
+ // Read CSS files and write to a separate file
228
313
  let cssContent = '';
229
314
  for (const cssFile of cssFiles) {
230
315
  const css = await readFile(join(sourcePath, cssFile), 'utf-8');
231
316
  cssContent += `/* ${cssFile} */\n${css}\n\n`;
232
317
  }
233
318
 
319
+ // Write landing CSS to a separate file so Vite processes it through the full pipeline
320
+ const landingCssPath = join(projectDir, 'src', 'styles', 'landing.css');
321
+ await writeFile(landingCssPath, cssContent, 'utf-8');
322
+
234
323
  // Read JS files
235
324
  let jsContent = '';
236
325
  for (const jsFile of jsFiles) {
@@ -238,13 +327,33 @@ async function generateAstroLanding(projectDir, landingData) {
238
327
  jsContent += `// ${jsFile}\n${js}\n\n`;
239
328
  }
240
329
 
330
+ // Determine header/footer: hidden ('__hidden__'), custom (string HTML), or default (null)
331
+ const navbarIsHidden = navbarContent === '__hidden__';
332
+ const footerIsHidden = footerContent === '__hidden__';
333
+ const hasCustomNavbar = !navbarIsHidden && !!navbarContent;
334
+ const hasCustomFooter = !footerIsHidden && !!footerContent;
335
+
336
+ const headerImport = navbarIsHidden || hasCustomNavbar ? '' : "import Header from '../components/Header.astro';";
337
+ const footerImport = footerIsHidden || hasCustomFooter ? '' : "import Footer from '../components/Footer.astro';";
338
+ const headerRender = navbarIsHidden
339
+ ? ''
340
+ : hasCustomNavbar
341
+ ? `<header class="landing-custom-navbar">\n ${navbarContent}\n </header>`
342
+ : '<Header />';
343
+ const footerRender = footerIsHidden
344
+ ? ''
345
+ : hasCustomFooter
346
+ ? `<footer class="landing-custom-footer">\n ${footerContent}\n </footer>`
347
+ : '<Footer />';
348
+
241
349
  // Generate standalone Astro component
242
350
  const astroContent = `---
243
351
  // Custom landing page - auto-generated by Lito CLI
244
352
  // Source: _landing/ folder
245
353
  import '../styles/global.css';
246
- import Header from '../components/Header.astro';
247
- import Footer from '../components/Footer.astro';
354
+ import '../styles/landing.css';
355
+ ${headerImport}
356
+ ${footerImport}
248
357
  import { getConfigFile } from '../utils/config';
249
358
 
250
359
  const config = await getConfigFile();
@@ -269,18 +378,15 @@ const config = await getConfigFile();
269
378
  document.documentElement.classList.add('light');
270
379
  }
271
380
  </script>
272
- <style>
273
- ${cssContent}
274
- </style>
275
381
  </head>
276
382
  <body class="bg-background text-foreground font-sans antialiased">
277
- <Header />
383
+ ${headerRender}
278
384
 
279
385
  <main class="landing-custom">
280
386
  ${htmlContent}
281
387
  </main>
282
388
 
283
- <Footer />
389
+ ${footerRender}
284
390
 
285
391
  ${jsContent ? `<script>\n${jsContent}\n</script>` : ''}
286
392
  </body>
@@ -571,7 +677,7 @@ async function generateSectionsLandingForFramework(projectDir, frameworkConfig,
571
677
  * Generate Astro sections landing page
572
678
  */
573
679
  async function generateAstroSectionsLanding(projectDir, landingData) {
574
- const { sections, config } = landingData;
680
+ const { sections, navbarContent, footerContent, config } = landingData;
575
681
 
576
682
  // Generate section renders
577
683
  const sectionRenders = sections.map((section, index) => {
@@ -591,11 +697,30 @@ async function generateAstroSectionsLanding(projectDir, landingData) {
591
697
  }
592
698
  }).join('\n');
593
699
 
700
+ // Determine header/footer: hidden ('__hidden__'), custom (string HTML), or default (null)
701
+ const navbarIsHidden = navbarContent === '__hidden__';
702
+ const footerIsHidden = footerContent === '__hidden__';
703
+ const hasCustomNavbar = !navbarIsHidden && !!navbarContent;
704
+ const hasCustomFooter = !footerIsHidden && !!footerContent;
705
+
706
+ const headerImport = navbarIsHidden || hasCustomNavbar ? '' : "import Header from '../components/Header.astro';";
707
+ const footerImport = footerIsHidden || hasCustomFooter ? '' : "import Footer from '../components/Footer.astro';";
708
+ const headerRender = navbarIsHidden
709
+ ? ''
710
+ : hasCustomNavbar
711
+ ? `<header class="landing-custom-navbar">\n ${navbarContent}\n </header>`
712
+ : '<Header />';
713
+ const footerRender = footerIsHidden
714
+ ? ''
715
+ : hasCustomFooter
716
+ ? `<footer class="landing-custom-footer">\n ${footerContent}\n </footer>`
717
+ : '<Footer />';
718
+
594
719
  const astroContent = `---
595
720
  // Sections-based landing page - auto-generated by Lito CLI
596
721
  import '../styles/global.css';
597
- import Header from '../components/Header.astro';
598
- import Footer from '../components/Footer.astro';
722
+ ${headerImport}
723
+ ${footerImport}
599
724
  import { getConfigFile } from '../utils/config';
600
725
 
601
726
  const config = await getConfigFile();
@@ -621,13 +746,13 @@ const config = await getConfigFile();
621
746
  </script>
622
747
  </head>
623
748
  <body class="bg-background text-foreground font-sans antialiased">
624
- <Header />
749
+ ${headerRender}
625
750
 
626
751
  <main class="landing-sections">
627
752
  ${sectionRenders}
628
753
  </main>
629
754
 
630
- <Footer />
755
+ ${footerRender}
631
756
  </body>
632
757
  </html>
633
758
  `;
@@ -1,6 +1,6 @@
1
1
  import pkg from 'fs-extra';
2
2
  const { ensureDir, emptyDir, copy, remove } = pkg;
3
- import { tmpdir } from 'os';
3
+ import { homedir } from 'os';
4
4
  import { join, basename } from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { dirname } from 'path';
@@ -8,8 +8,9 @@ import { dirname } from 'path';
8
8
  const __filename = fileURLToPath(import.meta.url);
9
9
  const __dirname = dirname(__filename);
10
10
 
11
- // Use a consistent directory path instead of random temp directories
12
- const LITO_DIR = join(tmpdir(), '.lito');
11
+ // Use a directory under the user's home to avoid resolution issues
12
+ // with bundlers (e.g. Turbopack) that fail to resolve node_modules from /tmp
13
+ const LITO_DIR = join(homedir(), '.lito', 'dev-project');
13
14
 
14
15
  export async function scaffoldProject(customTemplatePath = null) {
15
16
  // Ensure the directory exists (creates if it doesn't)
package/src/core/sync.js CHANGED
@@ -13,7 +13,7 @@ const KNOWN_LOCALES = [
13
13
  ];
14
14
 
15
15
  // Special folders that are not content
16
- const SPECIAL_FOLDERS = ['_assets', '_css', '_images', '_static', '_landing', 'public'];
16
+ const SPECIAL_FOLDERS = ['_assets', '_css', '_images', '_static', '_landing', '_navbar', '_footer', 'public'];
17
17
 
18
18
  /**
19
19
  * Get i18n configuration from docs-config.json