@life-and-dev/mdsite 0.1.0 → 0.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.
Files changed (50) hide show
  1. package/README.md +40 -22
  2. package/dist/commands/commands.test.js +103 -31
  3. package/dist/commands/commands.test.js.map +1 -1
  4. package/dist/commands/init.d.ts +1 -1
  5. package/dist/commands/init.js +64 -2
  6. package/dist/commands/init.js.map +1 -1
  7. package/dist/commands/prepare.js +22 -14
  8. package/dist/commands/prepare.js.map +1 -1
  9. package/dist/commands/prepare.test.js +33 -39
  10. package/dist/commands/prepare.test.js.map +1 -1
  11. package/dist/commands/preview.d.ts +1 -0
  12. package/dist/commands/preview.js +11 -10
  13. package/dist/commands/preview.js.map +1 -1
  14. package/dist/commands/start.d.ts +1 -0
  15. package/dist/commands/start.js +23 -10
  16. package/dist/commands/start.js.map +1 -1
  17. package/dist/commands/stop.js +6 -3
  18. package/dist/commands/stop.js.map +1 -1
  19. package/dist/commands/workflows.test.js +81 -21
  20. package/dist/commands/workflows.test.js.map +1 -1
  21. package/dist/config/mdsite-config.js +1 -1
  22. package/dist/config/mdsite-config.js.map +1 -1
  23. package/dist/config/mdsite-config.test.js +1 -1
  24. package/dist/config/mdsite-config.test.js.map +1 -1
  25. package/dist/config/menu.js +0 -1
  26. package/dist/config/menu.js.map +1 -1
  27. package/dist/index.js +44 -10
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.test.js +40 -0
  30. package/dist/index.test.js.map +1 -1
  31. package/dist/process/child-process.test.js +3 -3
  32. package/dist/process/child-process.test.js.map +1 -1
  33. package/dist/process/runtime-state.d.ts +6 -5
  34. package/dist/process/runtime-state.js +13 -17
  35. package/dist/process/runtime-state.js.map +1 -1
  36. package/dist/process/runtime-state.test.js +21 -13
  37. package/dist/process/runtime-state.test.js.map +1 -1
  38. package/dist/renderer/mdsite-nuxt.d.ts +2 -0
  39. package/dist/renderer/mdsite-nuxt.js +29 -32
  40. package/dist/renderer/mdsite-nuxt.js.map +1 -1
  41. package/dist/renderer/mdsite-nuxt.test.js +37 -48
  42. package/dist/renderer/mdsite-nuxt.test.js.map +1 -1
  43. package/mdsite-nuxt/app/composables/useAppTheme.ts +22 -3
  44. package/mdsite-nuxt/app/config/themes.ts +38 -0
  45. package/mdsite-nuxt/nuxt.config.ts +11 -1
  46. package/mdsite-nuxt/scripts/generate-favicons.ts +24 -55
  47. package/mdsite-nuxt/scripts/renderer-hooks.test.ts +0 -8
  48. package/mdsite-nuxt/scripts/renderer-hooks.ts +1 -2
  49. package/mdsite-nuxt/scripts/sync-content.ts +5 -79
  50. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  // https://nuxt.com/docs/api/configuration/nuxt-config
2
2
  import path from 'path'
3
- import { getDomainThemes } from './app/config/themes'
3
+ import { buildDarkOverrideCss, getDomainThemes } from './app/config/themes'
4
4
  import { createBibleReferencePatterns } from './app/utils/bible-book-names'
5
5
  import { runBuildFallbackHooks } from './scripts/renderer-hooks'
6
6
  import { withBasePath } from './utils/base-url'
@@ -59,6 +59,16 @@ export default defineNuxtConfig({
59
59
  app: {
60
60
  baseURL: appBaseURL,
61
61
  head: {
62
+ style: [
63
+ { innerHTML: buildDarkOverrideCss() }
64
+ ],
65
+ script: [
66
+ {
67
+ tagPosition: 'head',
68
+ tagPriority: 'critical',
69
+ innerHTML: `(function(){try{var t=localStorage.getItem('theme-preference');if(t!=='light'&&t!=='dark'){t=(window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches)?'dark':'light';}document.documentElement.setAttribute('data-mdsite-theme',t);}catch(e){document.documentElement.setAttribute('data-mdsite-theme','light');}})();`
70
+ }
71
+ ],
62
72
  link: [
63
73
  { rel: 'icon', type: 'image/svg+xml', href: withBasePath('/favicon.svg', appBaseURL), sizes: 'any' },
64
74
  { rel: 'icon', type: 'image/x-icon', href: withBasePath('/favicon.ico', appBaseURL), sizes: '32x32' },
@@ -19,45 +19,49 @@ const FAVICON_SIZES = {
19
19
  /**
20
20
  * Generate favicons from the active mdsite config
21
21
  */
22
- export async function generateFavicons() {
22
+ export async function generateFavicons(): Promise<boolean> {
23
23
  const { contentDir, config } = loadMdsiteConfigSync()
24
- const logoPath = path.join(contentDir, 'logo.svg')
25
- const faviconDir = path.join(contentDir, 'favicon')
26
24
 
27
- // Check if logo exists
28
- if (!await fs.pathExists(logoPath)) {
29
- console.error(`❌ Logo not found: ${logoPath}`)
25
+ if (!config.favicon || !config.favicon.trim()) {
26
+ console.warn(`⚠️ No favicon source configured (config.favicon is empty). Skipping favicon generation.`)
30
27
  return false
31
28
  }
32
29
 
33
- await fs.ensureDir(faviconDir)
30
+ const sourcePath = path.resolve(contentDir, config.favicon)
31
+
32
+ if (!await fs.pathExists(sourcePath)) {
33
+ console.error(`❌ Favicon source not found: ${sourcePath}`)
34
+ return false
35
+ }
36
+
37
+ const publicDir = path.resolve(__dirname, '..', 'public')
38
+ await fs.ensureDir(publicDir)
34
39
 
35
40
  console.log(`🎨 Generating favicons for site: ${config.site.name}`)
36
- console.log(` Source: ${logoPath}`)
37
- console.log(` Output: ${faviconDir}`)
41
+ console.log(` Source: ${sourcePath}`)
42
+ console.log(` Output: ${publicDir}`)
38
43
 
39
44
  try {
40
45
  // Copy SVG as-is (for modern browsers)
41
- const svgTargetPath = path.join(faviconDir, 'favicon.svg')
42
- await fs.copy(logoPath, svgTargetPath)
46
+ const svgTargetPath = path.join(publicDir, 'favicon.svg')
47
+ await fs.copy(sourcePath, svgTargetPath)
43
48
  console.log(` ✓ SVG: favicon.svg`)
44
49
 
45
50
  // Generate ICO (32x32 with transparent padding)
46
- const icoTargetPath = path.join(faviconDir, 'favicon.ico')
47
- const png32Buffer = await sharp(logoPath)
51
+ const icoTargetPath = path.join(publicDir, 'favicon.ico')
52
+ const png32Buffer = await sharp(sourcePath)
48
53
  .resize(32, 32, {
49
54
  fit: 'contain',
50
55
  background: { r: 255, g: 255, b: 255, alpha: 0 }
51
56
  })
52
57
  .png()
53
58
  .toBuffer()
54
-
55
59
  await fs.writeFile(icoTargetPath, png32Buffer)
56
60
  console.log(` ✓ ICO: favicon.ico`)
57
61
 
58
62
  // Generate Apple Touch Icon (180x180 with transparent padding)
59
- const appleTouchPath = path.join(faviconDir, 'apple-touch-icon.png')
60
- await sharp(logoPath)
63
+ const appleTouchPath = path.join(publicDir, 'apple-touch-icon.png')
64
+ await sharp(sourcePath)
61
65
  .resize(FAVICON_SIZES.appleTouchIcon, FAVICON_SIZES.appleTouchIcon, {
62
66
  fit: 'contain',
63
67
  background: { r: 255, g: 255, b: 255, alpha: 0 }
@@ -67,8 +71,8 @@ export async function generateFavicons() {
67
71
  console.log(` ✓ Apple Touch Icon: apple-touch-icon.png`)
68
72
 
69
73
  // Generate PWA Icon 192x192
70
- const icon192Path = path.join(faviconDir, 'icon-192.png')
71
- await sharp(logoPath)
74
+ const icon192Path = path.join(publicDir, 'icon-192.png')
75
+ await sharp(sourcePath)
72
76
  .resize(FAVICON_SIZES.pwaIcon192, FAVICON_SIZES.pwaIcon192, {
73
77
  fit: 'contain',
74
78
  background: { r: 255, g: 255, b: 255, alpha: 0 }
@@ -78,8 +82,8 @@ export async function generateFavicons() {
78
82
  console.log(` ✓ PWA Icon 192: icon-192.png`)
79
83
 
80
84
  // Generate PWA Icon 512x512
81
- const icon512Path = path.join(faviconDir, 'icon-512.png')
82
- await sharp(logoPath)
85
+ const icon512Path = path.join(publicDir, 'icon-512.png')
86
+ await sharp(sourcePath)
83
87
  .resize(FAVICON_SIZES.pwaIcon512, FAVICON_SIZES.pwaIcon512, {
84
88
  fit: 'contain',
85
89
  background: { r: 255, g: 255, b: 255, alpha: 0 }
@@ -96,40 +100,6 @@ export async function generateFavicons() {
96
100
  }
97
101
  }
98
102
 
99
- /**
100
- * Copy favicon files from content submodule to public directory
101
- */
102
- export async function copyFaviconsToPublic() {
103
- const projectRoot = path.resolve(__dirname, '..')
104
- const { contentDir, config } = loadMdsiteConfigSync()
105
- const faviconDir = path.join(contentDir, 'favicon')
106
- const publicDir = path.join(projectRoot, 'public')
107
-
108
- console.log(`📋 Copying ${config.site.name} favicons to public...`)
109
-
110
- const files = [
111
- 'favicon.svg',
112
- 'favicon.ico',
113
- 'apple-touch-icon.png',
114
- 'icon-192.png',
115
- 'icon-512.png'
116
- ]
117
-
118
- for (const file of files) {
119
- const sourcePath = path.join(faviconDir, file)
120
- const targetPath = path.join(publicDir, file)
121
-
122
- if (await fs.pathExists(sourcePath)) {
123
- await fs.copy(sourcePath, targetPath)
124
- console.log(` ✓ ${file}`)
125
- } else {
126
- console.warn(` ⚠ Missing: ${file}`)
127
- }
128
- }
129
-
130
- console.log(`✅ Favicon copy complete\n`)
131
- }
132
-
133
103
  /**
134
104
  * Generate web manifest for PWA support
135
105
  */
@@ -183,7 +153,6 @@ if (import.meta.url === `file://${process.argv[1]}`) {
183
153
  ; (async () => {
184
154
  const success = await generateFavicons()
185
155
  if (success) {
186
- await copyFaviconsToPublic()
187
156
  await generateWebManifest(config.site.name)
188
157
  } else {
189
158
  process.exit(1)
@@ -4,7 +4,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
4
4
 
5
5
  const {
6
6
  buildContentDataMock,
7
- copyFaviconsToPublicMock,
8
7
  existsSyncMock,
9
8
  generateFaviconsMock,
10
9
  generateWebManifestMock,
@@ -20,7 +19,6 @@ const {
20
19
  writeFileSyncMock,
21
20
  } = vi.hoisted(() => ({
22
21
  buildContentDataMock: vi.fn(),
23
- copyFaviconsToPublicMock: vi.fn(),
24
22
  existsSyncMock: vi.fn(),
25
23
  generateFaviconsMock: vi.fn(),
26
24
  generateWebManifestMock: vi.fn(),
@@ -60,7 +58,6 @@ vi.mock('./generate-indices.js', () => ({
60
58
  }))
61
59
 
62
60
  vi.mock('./generate-favicons.js', () => ({
63
- copyFaviconsToPublic: copyFaviconsToPublicMock,
64
61
  generateFavicons: generateFaviconsMock,
65
62
  generateWebManifest: generateWebManifestMock,
66
63
  }))
@@ -99,7 +96,6 @@ describe('renderer hooks orchestration', () => {
99
96
  stringifyYamlMock.mockReturnValue('compat-config')
100
97
  generateFaviconsMock.mockResolvedValue(true)
101
98
  buildContentDataMock.mockResolvedValue(undefined)
102
- copyFaviconsToPublicMock.mockResolvedValue(undefined)
103
99
  generateWebManifestMock.mockResolvedValue(undefined)
104
100
  startWatcherMock.mockResolvedValue(undefined)
105
101
  syncContentMock.mockResolvedValue(undefined)
@@ -255,7 +251,6 @@ describe('renderer hooks orchestration', () => {
255
251
  expect(syncContentMock).toHaveBeenCalledTimes(1)
256
252
  expect(buildContentDataMock).toHaveBeenCalledTimes(1)
257
253
  expect(generateFaviconsMock).toHaveBeenCalledTimes(1)
258
- expect(copyFaviconsToPublicMock).toHaveBeenCalledTimes(1)
259
254
  expect(generateWebManifestMock).toHaveBeenCalledWith('Docs')
260
255
  expect(syncContentMock.mock.invocationCallOrder[0]).toBeLessThan(buildContentDataMock.mock.invocationCallOrder[0])
261
256
  expect(buildContentDataMock.mock.invocationCallOrder[0]).toBeLessThan(generateFaviconsMock.mock.invocationCallOrder[0])
@@ -268,7 +263,6 @@ describe('renderer hooks orchestration', () => {
268
263
  expect(syncContentMock).toHaveBeenCalledTimes(1)
269
264
  expect(buildContentDataMock).toHaveBeenCalledTimes(1)
270
265
  expect(generateFaviconsMock).toHaveBeenCalledTimes(1)
271
- expect(copyFaviconsToPublicMock).toHaveBeenCalledTimes(1)
272
266
  expect(generateWebManifestMock).toHaveBeenCalledWith('Docs')
273
267
  expect(startWatcherMock).not.toHaveBeenCalled()
274
268
  expect(rmMock).not.toHaveBeenCalled()
@@ -292,7 +286,6 @@ describe('renderer hooks orchestration', () => {
292
286
 
293
287
  expect(buildContentDataMock).toHaveBeenCalledTimes(1)
294
288
  expect(generateFaviconsMock).toHaveBeenCalledTimes(1)
295
- expect(copyFaviconsToPublicMock).not.toHaveBeenCalled()
296
289
  expect(generateWebManifestMock).not.toHaveBeenCalled()
297
290
  })
298
291
 
@@ -304,7 +297,6 @@ describe('renderer hooks orchestration', () => {
304
297
  expect(syncContentMock).toHaveBeenCalledTimes(1)
305
298
  expect(buildContentDataMock).toHaveBeenCalledTimes(1)
306
299
  expect(generateFaviconsMock).toHaveBeenCalledTimes(1)
307
- expect(copyFaviconsToPublicMock).not.toHaveBeenCalled()
308
300
  expect(generateWebManifestMock).not.toHaveBeenCalled()
309
301
  expect(process.env.MDSITE_RENDERER_ORCHESTRATED).toBe('1')
310
302
  })
@@ -3,7 +3,7 @@ import path from 'path'
3
3
  import YAML from 'yaml'
4
4
 
5
5
  import { buildContentData } from './generate-indices.js'
6
- import { generateFavicons, copyFaviconsToPublic, generateWebManifest } from './generate-favicons.js'
6
+ import { generateFavicons, generateWebManifest } from './generate-favicons.js'
7
7
  import { startWatcher, syncContent } from './sync-content.js'
8
8
  import { loadMdsiteConfigSync, resolveMdsiteConfigPath } from '../utils/mdsite-config.js'
9
9
 
@@ -95,7 +95,6 @@ async function generateFaviconAssets(siteName: string): Promise<void> {
95
95
  const success = await generateFavicons()
96
96
 
97
97
  if (success) {
98
- await copyFaviconsToPublic()
99
98
  await generateWebManifest(siteName)
100
99
  console.log(`✅ Favicons ready for ${siteName}\n`)
101
100
  }
@@ -18,15 +18,6 @@ const STATIC_FILES = [
18
18
  'site.webmanifest',
19
19
  'robots.txt'
20
20
  ]
21
- const LOGO_FILE = 'logo.svg'
22
- const FAVICON_DIR = 'favicon'
23
- const FAVICON_FILES = [
24
- 'favicon.svg',
25
- 'favicon.ico',
26
- 'apple-touch-icon.png',
27
- 'icon-192.png',
28
- 'icon-512.png'
29
- ]
30
21
 
31
22
  // Debounce timers for JSON regeneration (5 second delay)
32
23
  let navigationDebounceTimer: NodeJS.Timeout | null = null
@@ -118,45 +109,26 @@ export async function generateJsonFiles() {
118
109
  }
119
110
 
120
111
  /**
121
- * Copy all images and favicons from content to public directory (one-time)
112
+ * Copy all images from content to public directory (one-time)
122
113
  */
123
114
  export async function copyAllImages() {
124
115
  const sourceDir = getSourceDir()
125
116
  const targetDir = getTargetDir()
126
117
 
127
- console.log(`📦 Copying images and favicons from: ${sourceDir}`)
118
+ console.log(`📦 Copying images from: ${sourceDir}`)
128
119
  console.log(`📦 Target directory: ${targetDir}`)
129
120
 
130
121
  let copiedCount = 0
131
122
 
132
- // Copy images (excluding logo.svg and favicon directory - handled separately)
133
123
  for (const ext of IMAGE_EXTS) {
134
124
  const files = await getAllFiles(sourceDir, ext)
135
125
 
136
126
  for (const sourcePath of files) {
137
-
138
- // Skip files in favicon directory - handled separately
139
- if (sourcePath.includes(`${path.sep}${FAVICON_DIR}${path.sep}`)) {
140
- continue
141
- }
142
-
143
127
  await copyImage(sourcePath, false)
144
128
  copiedCount++
145
129
  }
146
130
  }
147
131
 
148
- // Copy favicon files
149
- const faviconDir = path.join(sourceDir, FAVICON_DIR)
150
- if (await fs.pathExists(faviconDir)) {
151
- for (const faviconFile of FAVICON_FILES) {
152
- const sourcePath = path.join(faviconDir, faviconFile)
153
- if (await fs.pathExists(sourcePath)) {
154
- await copyFaviconFile(sourcePath, false)
155
- copiedCount++
156
- }
157
- }
158
- }
159
-
160
132
  console.log(`✓ Copied ${copiedCount} file(s)\n`)
161
133
  }
162
134
 
@@ -221,9 +193,7 @@ export async function startWatcher() {
221
193
  watcher
222
194
  .on('add', (filePath) => {
223
195
  const fileName = path.basename(filePath)
224
- if (fileName === LOGO_FILE) {
225
- handleLogoChange(filePath, 'added')
226
- } else if (fileName.endsWith('.md')) {
196
+ if (fileName.endsWith('.md')) {
227
197
  // Markdown file added - regenerate both navigation and search
228
198
  console.log(`📝 Markdown added: ${fileName}`)
229
199
  regenerateNavigation()
@@ -234,9 +204,7 @@ export async function startWatcher() {
234
204
  })
235
205
  .on('change', (filePath) => {
236
206
  const fileName = path.basename(filePath)
237
- if (fileName === LOGO_FILE) {
238
- handleLogoChange(filePath, 'updated')
239
- } else if (fileName.endsWith('.md')) {
207
+ if (fileName.endsWith('.md')) {
240
208
  // Markdown file changed - regenerate both navigation and search
241
209
  console.log(`📝 Markdown updated: ${fileName}`)
242
210
  regenerateNavigation()
@@ -247,9 +215,7 @@ export async function startWatcher() {
247
215
  })
248
216
  .on('unlink', (filePath) => {
249
217
  const fileName = path.basename(filePath)
250
- if (fileName === LOGO_FILE) {
251
- console.log(`🗑️ Logo removed: ${fileName}`)
252
- } else if (fileName.endsWith('.md')) {
218
+ if (fileName.endsWith('.md')) {
253
219
  // Markdown file deleted - regenerate both navigation and search
254
220
  console.log(`📝 Markdown deleted: ${fileName}`)
255
221
  regenerateNavigation()
@@ -363,46 +329,6 @@ async function isDraftOnlyImage(imagePath: string): Promise<boolean> {
363
329
  return !hasPublishedVersion && hasDraftVersion
364
330
  }
365
331
 
366
- /**
367
- * Copy a single favicon file from content to public
368
- */
369
- async function copyFaviconFile(sourcePath: string, log: boolean = true, action: string = 'copied') {
370
- try {
371
- const targetDir = getTargetDir()
372
- const fileName = path.basename(sourcePath)
373
- const targetPath = path.join(targetDir, fileName)
374
-
375
- await fs.ensureDir(targetDir)
376
- await fs.copy(sourcePath, targetPath)
377
-
378
- if (log) {
379
- console.log(`✓ Favicon ${action}: ${fileName}`)
380
- }
381
- } catch (error) {
382
- console.error(`❌ Failed to copy favicon ${sourcePath}:`, error)
383
- }
384
- }
385
-
386
- /**
387
- * Handle logo.svg changes - regenerate favicons
388
- */
389
- async function handleLogoChange(logoPath: string, action: string) {
390
- const domain = getContentDomain()
391
- console.log(`🎨 Logo ${action}, regenerating favicons for ${domain}...`)
392
-
393
- try {
394
- const { generateFavicons, copyFaviconsToPublic } = await import('./generate-favicons.js')
395
- const success = await generateFavicons(domain)
396
-
397
- if (success) {
398
- await copyFaviconsToPublic(domain)
399
- console.log(`✓ Favicons regenerated for ${domain}\n`)
400
- }
401
- } catch (error) {
402
- console.error(`❌ Failed to regenerate favicons:`, error)
403
- }
404
- }
405
-
406
332
  /**
407
333
  * Get all files with specific extension recursively
408
334
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@life-and-dev/mdsite",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "Local-first CLI that orchestrates mdsite-nuxt",