@litodocs/cli 0.6.0 → 0.7.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.
@@ -2,12 +2,18 @@ import pkg from 'fs-extra';
2
2
  const { copy, ensureDir } = pkg;
3
3
  import { join } from 'path';
4
4
 
5
- export async function copyOutput(projectDir, outputPath) {
6
- const distPath = join(projectDir, 'dist');
7
-
5
+ /**
6
+ * Copy built output to the specified output path
7
+ * @param {string} projectDir - The scaffolded project directory
8
+ * @param {string} outputPath - User's desired output path
9
+ * @param {string} buildOutputDir - Framework's build output directory (default: 'dist')
10
+ */
11
+ export async function copyOutput(projectDir, outputPath, buildOutputDir = 'dist') {
12
+ const distPath = join(projectDir, buildOutputDir);
13
+
8
14
  // Ensure output directory exists
9
15
  await ensureDir(outputPath);
10
-
16
+
11
17
  // Copy built files to output
12
18
  await copy(distPath, outputPath, {
13
19
  overwrite: true,
package/src/core/sync.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import pkg from 'fs-extra';
2
2
  const { copy, ensureDir, remove, readFile, writeFile, pathExists } = pkg;
3
3
  import { join, relative, sep } from 'path';
4
- import { readdir, stat } from 'fs/promises';
4
+ import { readdir, stat, utimes } from 'fs/promises';
5
+ import { syncLanding } from './landing-sync.js';
5
6
 
6
7
  // Known locale codes (ISO 639-1)
7
8
  const KNOWN_LOCALES = [
@@ -12,7 +13,7 @@ const KNOWN_LOCALES = [
12
13
  ];
13
14
 
14
15
  // Special folders that are not content
15
- const SPECIAL_FOLDERS = ['_assets', '_css', '_images', '_static', 'public'];
16
+ const SPECIAL_FOLDERS = ['_assets', '_css', '_images', '_static', '_landing', 'public'];
16
17
 
17
18
  /**
18
19
  * Get i18n configuration from docs-config.json
@@ -30,6 +31,21 @@ async function getI18nConfig(sourcePath) {
30
31
  return { defaultLocale: 'en', locales: ['en'] };
31
32
  }
32
33
 
34
+ /**
35
+ * Get full docs-config.json
36
+ */
37
+ async function getDocsConfig(sourcePath) {
38
+ const configPath = join(sourcePath, 'docs-config.json');
39
+ if (await pathExists(configPath)) {
40
+ try {
41
+ return JSON.parse(await readFile(configPath, 'utf-8'));
42
+ } catch {
43
+ return {};
44
+ }
45
+ }
46
+ return {};
47
+ }
48
+
33
49
  /**
34
50
  * Get versioning configuration from docs-config.json
35
51
  */
@@ -97,8 +113,17 @@ async function detectVersionFolders(sourcePath, versioningConfig) {
97
113
  return versionFolders;
98
114
  }
99
115
 
100
- export async function syncDocs(sourcePath, projectDir) {
101
- const targetPath = join(projectDir, 'src', 'pages');
116
+ /**
117
+ * Sync documentation files to the project
118
+ * @param {string} sourcePath - User's docs directory
119
+ * @param {string} projectDir - Scaffolded project directory
120
+ * @param {object} frameworkConfig - Framework configuration (optional, defaults to Astro)
121
+ */
122
+ export async function syncDocs(sourcePath, projectDir, frameworkConfig = null) {
123
+ // Default to Astro's src/pages for backward compatibility
124
+ const contentDir = frameworkConfig?.contentDir || 'src/pages';
125
+ const targetPath = join(projectDir, contentDir);
126
+ const shouldInjectLayout = frameworkConfig?.layoutInjection !== false;
102
127
 
103
128
  // Clear existing pages (except index if it exists in template)
104
129
  await ensureDir(targetPath);
@@ -159,8 +184,10 @@ export async function syncDocs(sourcePath, projectDir) {
159
184
  },
160
185
  });
161
186
 
162
- // Inject layout into version markdown files
163
- await injectLayoutIntoMarkdown(versionTargetPath, targetPath, null, [], version);
187
+ // Inject layout into version markdown files (only for frameworks that need it)
188
+ if (shouldInjectLayout) {
189
+ await injectLayoutIntoMarkdown(versionTargetPath, targetPath, null, [], version, frameworkConfig);
190
+ }
164
191
  }));
165
192
 
166
193
  // Sync locale folders
@@ -177,12 +204,16 @@ export async function syncDocs(sourcePath, projectDir) {
177
204
  },
178
205
  });
179
206
 
180
- // Inject layout into locale markdown files
181
- await injectLayoutIntoMarkdown(localeTargetPath, targetPath, locale);
207
+ // Inject layout into locale markdown files (only for frameworks that need it)
208
+ if (shouldInjectLayout) {
209
+ await injectLayoutIntoMarkdown(localeTargetPath, targetPath, locale, [], null, frameworkConfig);
210
+ }
182
211
  }));
183
212
 
184
- // Inject layout into default locale markdown files
185
- await injectLayoutIntoMarkdown(targetPath, targetPath, null, excludeFolders);
213
+ // Inject layout into default locale markdown files (only for frameworks that need it)
214
+ if (shouldInjectLayout) {
215
+ await injectLayoutIntoMarkdown(targetPath, targetPath, null, excludeFolders, null, frameworkConfig);
216
+ }
186
217
 
187
218
  // Check for custom landing page conflict
188
219
  const hasUserIndex = ['index.md', 'index.mdx'].some(file =>
@@ -198,6 +229,18 @@ export async function syncDocs(sourcePath, projectDir) {
198
229
 
199
230
  // Sync user assets (images, css, static files)
200
231
  await syncUserAssets(sourcePath, projectDir);
232
+
233
+ // Sync landing page (custom HTML/CSS/JS or sections)
234
+ const docsConfig = await getDocsConfig(sourcePath);
235
+ if (docsConfig.landing) {
236
+ await syncLanding(sourcePath, projectDir, frameworkConfig, docsConfig);
237
+ }
238
+
239
+ // Trigger HMR for non-Astro frameworks (Vite-based)
240
+ // Vite doesn't automatically watch content directories, so we need to trigger a reload
241
+ if (frameworkConfig && frameworkConfig.name !== 'astro') {
242
+ await triggerHMR(projectDir, frameworkConfig);
243
+ }
201
244
  }
202
245
 
203
246
  /**
@@ -283,7 +326,27 @@ async function syncUserAssets(sourcePath, projectDir) {
283
326
  const customImport = '@import "./custom.css";';
284
327
 
285
328
  if (!globalCss.includes(customImport)) {
286
- globalCss = globalCss.trimEnd() + '\n\n/* User custom styles */\n' + customImport + '\n';
329
+ // @import must come at the very top of the file (before @tailwind directives)
330
+ // Only @charset can precede @import
331
+ const lines = globalCss.split('\n');
332
+ let insertIndex = 0;
333
+
334
+ // Find position after @charset if it exists (only thing allowed before @import)
335
+ for (let i = 0; i < lines.length; i++) {
336
+ const trimmed = lines[i].trim();
337
+ if (trimmed.startsWith('@charset')) {
338
+ insertIndex = i + 1;
339
+ break;
340
+ }
341
+ // Stop at first non-empty, non-comment line
342
+ if (trimmed && !trimmed.startsWith('/*') && !trimmed.startsWith('*') && !trimmed.startsWith('//')) {
343
+ break;
344
+ }
345
+ }
346
+
347
+ // Insert the import at the top (after @charset if present)
348
+ lines.splice(insertIndex, 0, '/* User custom styles */', customImport, '');
349
+ globalCss = lines.join('\n');
287
350
  await writeFile(globalCssPath, globalCss, 'utf-8');
288
351
  }
289
352
  }
@@ -299,10 +362,15 @@ async function syncUserAssets(sourcePath, projectDir) {
299
362
  * @param {string|null} locale - Current locale (null for default)
300
363
  * @param {string[]} skipFolders - Folders to skip (locale folders when processing root)
301
364
  * @param {string|null} version - Current version (null for non-versioned)
365
+ * @param {object|null} frameworkConfig - Framework configuration for layout paths
302
366
  */
303
- async function injectLayoutIntoMarkdown(dir, rootDir, locale = null, skipFolders = [], version = null) {
367
+ async function injectLayoutIntoMarkdown(dir, rootDir, locale = null, skipFolders = [], version = null, frameworkConfig = null) {
304
368
  const entries = await readdir(dir, { withFileTypes: true });
305
369
 
370
+ // Get layout paths from framework config or use defaults
371
+ const baseLayoutPath = frameworkConfig?.layoutPath || 'layouts/MarkdownLayout.astro';
372
+ const apiLayoutPath = frameworkConfig?.apiLayoutPath || 'layouts/APILayout.astro';
373
+
306
374
  await Promise.all(entries.map(async (entry) => {
307
375
  const fullPath = join(dir, entry.name);
308
376
 
@@ -311,14 +379,14 @@ async function injectLayoutIntoMarkdown(dir, rootDir, locale = null, skipFolders
311
379
  if (skipFolders.includes(entry.name)) {
312
380
  return;
313
381
  }
314
- await injectLayoutIntoMarkdown(fullPath, rootDir, locale, [], version);
382
+ await injectLayoutIntoMarkdown(fullPath, rootDir, locale, [], version, frameworkConfig);
315
383
  } else if (entry.name.endsWith('.md') || entry.name.endsWith('.mdx')) {
316
384
  let content = await readFile(fullPath, 'utf-8');
317
385
 
318
386
  // Calculate relative path to layout
319
387
  const relPath = relative(rootDir, fullPath);
320
388
  const depth = relPath.split(sep).length - 1;
321
- const layoutPath = '../'.repeat(depth + 1) + 'layouts/MarkdownLayout.astro';
389
+ const layoutPath = '../'.repeat(depth + 1) + baseLayoutPath;
322
390
 
323
391
  // Check if file already has frontmatter
324
392
  if (content.startsWith('---')) {
@@ -330,7 +398,7 @@ async function injectLayoutIntoMarkdown(dir, rootDir, locale = null, skipFolders
330
398
  // Determine which layout to use
331
399
  let targetLayout = layoutPath;
332
400
  if (frontmatterBlock.includes('api:')) {
333
- targetLayout = '../'.repeat(depth + 1) + 'layouts/APILayout.astro';
401
+ targetLayout = '../'.repeat(depth + 1) + apiLayoutPath;
334
402
  }
335
403
 
336
404
  // Add locale to frontmatter if present
@@ -362,3 +430,61 @@ async function injectLayoutIntoMarkdown(dir, rootDir, locale = null, skipFolders
362
430
  }
363
431
  }));
364
432
  }
433
+
434
+ /**
435
+ * Trigger HMR for Vite-based frameworks
436
+ * Vite watches certain files for changes - we touch a trigger file to force a reload
437
+ * @param {string} projectDir - Project directory
438
+ * @param {object} frameworkConfig - Framework configuration
439
+ */
440
+ async function triggerHMR(projectDir, frameworkConfig) {
441
+ // Use framework-specific HMR trigger file if defined
442
+ const triggerFilePath = frameworkConfig.hmrTriggerFile
443
+ ? join(projectDir, frameworkConfig.hmrTriggerFile)
444
+ : join(projectDir, 'src', '.lito-hmr-trigger.js');
445
+
446
+ try {
447
+ // Ensure parent directory exists
448
+ await ensureDir(join(projectDir, 'src'));
449
+
450
+ // Write a timestamp to the trigger file
451
+ // This file should be imported in the app's entry point
452
+ const timestamp = Date.now();
453
+ const content = `// Auto-generated by Lito CLI for HMR triggering
454
+ // This file is updated when docs content changes
455
+ // Import this file in your app's entry point to enable content hot reload
456
+ export const LITO_CONTENT_VERSION = ${timestamp};
457
+ export const LITO_LAST_UPDATE = "${new Date().toISOString()}";
458
+
459
+ // Force Vite to treat this as a module that triggers HMR
460
+ if (import.meta.hot) {
461
+ import.meta.hot.accept();
462
+ }
463
+ `;
464
+
465
+ await writeFile(triggerFilePath, content, 'utf-8');
466
+ } catch {
467
+ // Fallback: Touch the main entry file or vite config
468
+ const fallbackFiles = [
469
+ join(projectDir, 'src', 'main.jsx'),
470
+ join(projectDir, 'src', 'main.tsx'),
471
+ join(projectDir, 'src', 'App.jsx'),
472
+ join(projectDir, 'src', 'App.tsx'),
473
+ join(projectDir, 'src', 'index.jsx'),
474
+ join(projectDir, 'src', 'index.tsx'),
475
+ ];
476
+
477
+ for (const file of fallbackFiles) {
478
+ if (await pathExists(file)) {
479
+ try {
480
+ // Touch the file by updating its mtime
481
+ const now = new Date();
482
+ await utimes(file, now, now);
483
+ break;
484
+ } catch {
485
+ // Continue to next file
486
+ }
487
+ }
488
+ }
489
+ }
490
+ }
@@ -9,12 +9,15 @@
9
9
  */
10
10
 
11
11
  export const TEMPLATE_REGISTRY = {
12
- // Default template (fetched from GitHub)
13
- 'default': 'github:Lito-docs/template',
12
+ // Default template - Astro-based
13
+ 'default': 'github:Lito-docs/lito-astro-template',
14
14
 
15
- // Official templates
16
- // 'modern': 'github:devrohit06/lito-theme-modern',
17
- // 'minimal': 'github:devrohit06/lito-theme-minimal',
15
+ // Framework-specific templates
16
+ 'astro': 'github:Lito-docs/lito-astro-template',
17
+ 'react': 'github:Lito-docs/lito-react-template',
18
+ 'next': 'github:Lito-docs/lito-next-template',
19
+ 'vue': 'github:Lito-docs/lito-vue-template',
20
+ 'nuxt': 'github:Lito-docs/lito-nuxt-template',
18
21
  };
19
22
 
20
23
  /**