@life-and-dev/mdsite 0.1.1 → 0.2.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.
Files changed (53) hide show
  1. package/README.md +39 -21
  2. package/dist/commands/commands.test.js +91 -30
  3. package/dist/commands/commands.test.js.map +1 -1
  4. package/dist/commands/init.js +54 -4
  5. package/dist/commands/init.js.map +1 -1
  6. package/dist/commands/prepare.js +22 -14
  7. package/dist/commands/prepare.js.map +1 -1
  8. package/dist/commands/prepare.test.js +33 -39
  9. package/dist/commands/prepare.test.js.map +1 -1
  10. package/dist/commands/preview.d.ts +1 -0
  11. package/dist/commands/preview.js +11 -10
  12. package/dist/commands/preview.js.map +1 -1
  13. package/dist/commands/start.d.ts +1 -0
  14. package/dist/commands/start.js +23 -10
  15. package/dist/commands/start.js.map +1 -1
  16. package/dist/commands/stop.js +6 -3
  17. package/dist/commands/stop.js.map +1 -1
  18. package/dist/commands/workflows.test.js +76 -19
  19. package/dist/commands/workflows.test.js.map +1 -1
  20. package/dist/config/mdsite-config.js +1 -1
  21. package/dist/config/mdsite-config.js.map +1 -1
  22. package/dist/config/mdsite-config.test.js +1 -1
  23. package/dist/config/mdsite-config.test.js.map +1 -1
  24. package/dist/config/menu.js +0 -1
  25. package/dist/config/menu.js.map +1 -1
  26. package/dist/index.js +42 -7
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.test.js +40 -0
  29. package/dist/index.test.js.map +1 -1
  30. package/dist/process/child-process.test.js +3 -3
  31. package/dist/process/child-process.test.js.map +1 -1
  32. package/dist/process/runtime-state.d.ts +6 -5
  33. package/dist/process/runtime-state.js +13 -17
  34. package/dist/process/runtime-state.js.map +1 -1
  35. package/dist/process/runtime-state.test.js +21 -13
  36. package/dist/process/runtime-state.test.js.map +1 -1
  37. package/dist/renderer/mdsite-nuxt.d.ts +2 -0
  38. package/dist/renderer/mdsite-nuxt.js +29 -32
  39. package/dist/renderer/mdsite-nuxt.js.map +1 -1
  40. package/dist/renderer/mdsite-nuxt.test.js +37 -48
  41. package/dist/renderer/mdsite-nuxt.test.js.map +1 -1
  42. package/mdsite-nuxt/app/composables/useAppTheme.ts +22 -3
  43. package/mdsite-nuxt/app/config/themes.ts +38 -0
  44. package/mdsite-nuxt/app/pages/[...slug].vue +4 -2
  45. package/mdsite-nuxt/app/pages/index.vue +4 -2
  46. package/mdsite-nuxt/assets/default-favicon.svg +223 -0
  47. package/mdsite-nuxt/nuxt.config.ts +11 -1
  48. package/mdsite-nuxt/scripts/generate-favicons.test.ts +123 -0
  49. package/mdsite-nuxt/scripts/generate-favicons.ts +60 -61
  50. package/mdsite-nuxt/scripts/renderer-hooks.test.ts +0 -8
  51. package/mdsite-nuxt/scripts/renderer-hooks.ts +1 -2
  52. package/mdsite-nuxt/scripts/sync-content.ts +5 -79
  53. package/package.json +1 -1
@@ -0,0 +1,223 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+
4
+ <svg
5
+ width="348.061"
6
+ height="340"
7
+ viewBox="0 0 34.8061 34"
8
+ version="1.1"
9
+ id="svg1"
10
+ inkscape:version="1.4.3 (1:1.4.3+202512261035+0d15f75042)"
11
+ sodipodi:docname="logo.svg"
12
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
13
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
14
+ xmlns:xlink="http://www.w3.org/1999/xlink"
15
+ xmlns="http://www.w3.org/2000/svg"
16
+ xmlns:svg="http://www.w3.org/2000/svg">
17
+ <sodipodi:namedview
18
+ id="namedview1"
19
+ pagecolor="#505050"
20
+ bordercolor="#eeeeee"
21
+ borderopacity="1"
22
+ inkscape:showpageshadow="0"
23
+ inkscape:pageopacity="0"
24
+ inkscape:pagecheckerboard="0"
25
+ inkscape:deskcolor="#505050"
26
+ inkscape:document-units="px"
27
+ showgrid="true"
28
+ inkscape:zoom="2.9179688"
29
+ inkscape:cx="181.11914"
30
+ inkscape:cy="144.27844"
31
+ inkscape:window-width="3032"
32
+ inkscape:window-height="1696"
33
+ inkscape:window-x="40"
34
+ inkscape:window-y="0"
35
+ inkscape:window-maximized="1"
36
+ inkscape:current-layer="layer1">
37
+ <inkscape:grid
38
+ id="grid1"
39
+ units="px"
40
+ originx="1.2100008"
41
+ originy="1"
42
+ spacingx="1"
43
+ spacingy="1"
44
+ empcolor="#0099e5"
45
+ empopacity="0.30196078"
46
+ color="#0099e5"
47
+ opacity="0.14901961"
48
+ empspacing="5"
49
+ enabled="true"
50
+ visible="true" />
51
+ </sodipodi:namedview>
52
+ <defs
53
+ id="defs1">
54
+ <linearGradient
55
+ id="linearGradient12"
56
+ inkscape:collect="always">
57
+ <stop
58
+ style="stop-color:#000000;stop-opacity:1;"
59
+ offset="0"
60
+ id="stop13" />
61
+ <stop
62
+ style="stop-color:#000000;stop-opacity:0;"
63
+ offset="1"
64
+ id="stop14" />
65
+ </linearGradient>
66
+ <linearGradient
67
+ id="linearGradient4"
68
+ inkscape:collect="always">
69
+ <stop
70
+ style="stop-color:#ffffff;stop-opacity:1;"
71
+ offset="0.49735323"
72
+ id="stop5" />
73
+ <stop
74
+ style="stop-color:#ffffff;stop-opacity:0.75119454;"
75
+ offset="1"
76
+ id="stop6" />
77
+ </linearGradient>
78
+ <linearGradient
79
+ id="linearGradient3"
80
+ inkscape:collect="always">
81
+ <stop
82
+ style="stop-color:#f9f9f9;stop-opacity:1;"
83
+ offset="0.49600685"
84
+ id="stop3" />
85
+ <stop
86
+ style="stop-color:#ffffff;stop-opacity:0.7457487;"
87
+ offset="1"
88
+ id="stop4" />
89
+ </linearGradient>
90
+ <radialGradient
91
+ inkscape:collect="always"
92
+ xlink:href="#linearGradient3"
93
+ id="radialGradient4"
94
+ cx="10.5"
95
+ cy="16"
96
+ fx="10.5"
97
+ fy="16"
98
+ r="10.5"
99
+ gradientTransform="matrix(1,0,0,1.3333333,0,-5.3333333)"
100
+ gradientUnits="userSpaceOnUse" />
101
+ <radialGradient
102
+ inkscape:collect="always"
103
+ xlink:href="#linearGradient4"
104
+ id="radialGradient6"
105
+ cx="24.514727"
106
+ cy="12"
107
+ fx="24.514727"
108
+ fy="12"
109
+ r="7.5147257"
110
+ gradientTransform="matrix(1,0,0,1.3307206,0,-3.9686472)"
111
+ gradientUnits="userSpaceOnUse" />
112
+ <radialGradient
113
+ inkscape:collect="always"
114
+ xlink:href="#linearGradient12"
115
+ id="radialGradient14"
116
+ cx="16"
117
+ cy="16"
118
+ fx="16"
119
+ fy="16"
120
+ r="16"
121
+ gradientUnits="userSpaceOnUse"
122
+ gradientTransform="matrix(1.0624999,0,0,1.0624999,0.21000074,0)" />
123
+ <filter
124
+ style="color-interpolation-filters:sRGB"
125
+ inkscape:label="Drop Shadow"
126
+ id="filter66"
127
+ x="-0.057619048"
128
+ y="-0.055841193"
129
+ width="1.132074"
130
+ height="1.1384681">
131
+ <feFlood
132
+ result="flood"
133
+ in="SourceGraphic"
134
+ flood-opacity="0.498039"
135
+ flood-color="rgb(0,0,0)"
136
+ id="feFlood65" />
137
+ <feGaussianBlur
138
+ result="blur"
139
+ in="SourceGraphic"
140
+ stdDeviation="0.400000"
141
+ id="feGaussianBlur65" />
142
+ <feOffset
143
+ result="offset"
144
+ in="blur"
145
+ dx="0.000000"
146
+ dy="1.000000"
147
+ id="feOffset65" />
148
+ <feComposite
149
+ result="comp1"
150
+ operator="in"
151
+ in="flood"
152
+ in2="offset"
153
+ id="feComposite65" />
154
+ <feComposite
155
+ result="comp2"
156
+ operator="over"
157
+ in="SourceGraphic"
158
+ in2="comp1"
159
+ id="feComposite66" />
160
+ </filter>
161
+ <filter
162
+ style="color-interpolation-filters:sRGB"
163
+ inkscape:label="Drop Shadow"
164
+ id="filter68"
165
+ x="-0.10403264"
166
+ y="-0.060500001"
167
+ width="1.2082745"
168
+ height="1.171">
169
+ <feFlood
170
+ result="flood"
171
+ in="SourceGraphic"
172
+ flood-opacity="0.498039"
173
+ flood-color="rgb(0,0,0)"
174
+ id="feFlood66" />
175
+ <feGaussianBlur
176
+ result="blur"
177
+ in="SourceGraphic"
178
+ stdDeviation="0.400000"
179
+ id="feGaussianBlur66" />
180
+ <feOffset
181
+ result="offset"
182
+ in="blur"
183
+ dx="0.000000"
184
+ dy="1.000000"
185
+ id="feOffset66" />
186
+ <feComposite
187
+ result="comp1"
188
+ operator="in"
189
+ in="flood"
190
+ in2="offset"
191
+ id="feComposite67" />
192
+ <feComposite
193
+ result="comp2"
194
+ operator="over"
195
+ in="SourceGraphic"
196
+ in2="comp1"
197
+ id="feComposite68" />
198
+ </filter>
199
+ </defs>
200
+ <rect
201
+ style="display:inline;fill:url(#radialGradient14);stroke:none;stroke-width:1.0625"
202
+ id="rect2"
203
+ width="34"
204
+ height="34"
205
+ x="0.21000074"
206
+ y="0" />
207
+ <g
208
+ inkscape:label="Layer 1"
209
+ inkscape:groupmode="layer"
210
+ id="layer1"
211
+ transform="translate(1.21,1)">
212
+ <path
213
+ style="fill:url(#radialGradient4);stroke:#000000;stroke-width:0.5;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter66)"
214
+ d="M 0,22 V 2 l 8,8 8,-8 0.03029,21 H 21 l -7,7 -7,-7 h 5.030291 L 12,11 8,15 4,11 v 11 z"
215
+ id="path1"
216
+ sodipodi:nodetypes="cccccccccccccc" />
217
+ <path
218
+ style="fill:url(#radialGradient6);stroke:#000000;stroke-width:0.5;stroke-linecap:square;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter68)"
219
+ d="m 21,18 -4,4 h 11 l 4,-4 V 14 L 28,10 H 23 L 21,8 23,6 h 5 L 32.029451,2 H 21.05087 L 17,5.8781794 V 10.014726 L 21,14 h 5 l 2,2 -2,2 z"
220
+ id="path2"
221
+ sodipodi:nodetypes="ccccccccccccccccccc" />
222
+ </g>
223
+ </svg>
@@ -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' },
@@ -0,0 +1,123 @@
1
+ import path from 'node:path'
2
+ import os from 'node:os'
3
+ import fs from 'node:fs'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
7
+
8
+ import { generateFavicons, resolveFaviconSource } from './generate-favicons.js'
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
11
+ const DEFAULT_FAVICON_PATH = path.resolve(__dirname, '..', 'assets', 'default-favicon.svg')
12
+
13
+ describe('generate-favicons', () => {
14
+ let tmpDir: string
15
+
16
+ beforeEach(() => {
17
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mdsite-favicon-'))
18
+ })
19
+
20
+ afterEach(() => {
21
+ fs.rmSync(tmpDir, { recursive: true, force: true })
22
+ })
23
+
24
+ describe('resolveFaviconSource', () => {
25
+ it('falls back to the bundled default favicon when favicon is an empty string', () => {
26
+ const result = resolveFaviconSource(tmpDir, '')
27
+
28
+ expect(result).not.toBeNull()
29
+ expect(result!.isDefault).toBe(true)
30
+ expect(path.basename(result!.sourcePath)).toBe('default-favicon.svg')
31
+ expect(fs.existsSync(result!.sourcePath)).toBe(true)
32
+ })
33
+
34
+ it('falls back to the bundled default favicon when favicon is only whitespace', () => {
35
+ const result = resolveFaviconSource(tmpDir, ' ')
36
+
37
+ expect(result).not.toBeNull()
38
+ expect(result!.isDefault).toBe(true)
39
+ expect(path.basename(result!.sourcePath)).toBe('default-favicon.svg')
40
+ })
41
+
42
+ it('uses the configured source when the favicon file exists under the content dir', () => {
43
+ const relPath = 'favicon.svg'
44
+ const absPath = path.join(tmpDir, relPath)
45
+ const customSvg =
46
+ '<?xml version="1.0" encoding="UTF-8"?>' +
47
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">' +
48
+ '<rect width="16" height="16" fill="red"/></svg>'
49
+ fs.writeFileSync(absPath, customSvg, 'utf8')
50
+
51
+ const result = resolveFaviconSource(tmpDir, relPath)
52
+
53
+ expect(result).not.toBeNull()
54
+ expect(result!.isDefault).toBe(false)
55
+ expect(result!.sourcePath).toBe(path.resolve(tmpDir, relPath))
56
+ })
57
+
58
+ it('falls back to the bundled default favicon when the configured file does not exist', () => {
59
+ const result = resolveFaviconSource(tmpDir, 'does-not-exist.svg')
60
+
61
+ expect(result).not.toBeNull()
62
+ expect(result!.isDefault).toBe(true)
63
+ expect(path.basename(result!.sourcePath)).toBe('default-favicon.svg')
64
+ expect(fs.existsSync(result!.sourcePath)).toBe(true)
65
+ })
66
+ })
67
+
68
+ describe('generateFavicons', () => {
69
+ it('uses the bundled default favicon and writes all expected assets when config.favicon is empty', async () => {
70
+ const outputDir = path.join(tmpDir, 'output')
71
+
72
+ const ok = await generateFavicons({
73
+ contentDir: tmpDir,
74
+ config: { favicon: '' },
75
+ outputDir,
76
+ })
77
+
78
+ expect(ok).toBe(true)
79
+
80
+ const expectedFiles = [
81
+ 'favicon.svg',
82
+ 'favicon.ico',
83
+ 'apple-touch-icon.png',
84
+ 'icon-192.png',
85
+ 'icon-512.png',
86
+ ]
87
+ for (const file of expectedFiles) {
88
+ const filePath = path.join(outputDir, file)
89
+ expect(fs.existsSync(filePath)).toBe(true)
90
+ expect(fs.statSync(filePath).size).toBeGreaterThan(0)
91
+ }
92
+
93
+ // The default source svg is copied verbatim as favicon.svg
94
+ const defaultSvgContent = fs.readFileSync(DEFAULT_FAVICON_PATH, 'utf8')
95
+ const writtenSvgContent = fs.readFileSync(path.join(outputDir, 'favicon.svg'), 'utf8')
96
+ expect(writtenSvgContent).toBe(defaultSvgContent)
97
+ })
98
+
99
+ it('uses the configured custom source svg over the bundled default', async () => {
100
+ const outputDir = path.join(tmpDir, 'output')
101
+ const customSvg =
102
+ '<?xml version="1.0" encoding="UTF-8"?>' +
103
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">' +
104
+ '<rect width="16" height="16" fill="blue"/></svg>'
105
+ fs.writeFileSync(path.join(tmpDir, 'favicon.svg'), customSvg, 'utf8')
106
+
107
+ const ok = await generateFavicons({
108
+ contentDir: tmpDir,
109
+ config: { favicon: 'favicon.svg' },
110
+ outputDir,
111
+ })
112
+
113
+ expect(ok).toBe(true)
114
+
115
+ const writtenSvgContent = fs.readFileSync(path.join(outputDir, 'favicon.svg'), 'utf8')
116
+ expect(writtenSvgContent).toBe(customSvg)
117
+
118
+ // And it is NOT the bundled default
119
+ const defaultSvgContent = fs.readFileSync(DEFAULT_FAVICON_PATH, 'utf8')
120
+ expect(writtenSvgContent).not.toBe(defaultSvgContent)
121
+ })
122
+ })
123
+ })
@@ -3,12 +3,14 @@
3
3
  import sharp from 'sharp'
4
4
  import fs from 'fs-extra'
5
5
  import path from 'path'
6
- import { fileURLToPath } from 'url'
6
+ import { fileURLToPath } from 'node:url'
7
7
  import { loadMdsiteConfigSync, resolveMdsiteConfigPath } from '../utils/mdsite-config.js'
8
8
 
9
9
  const __filename = fileURLToPath(import.meta.url)
10
10
  const __dirname = path.dirname(__filename)
11
11
 
12
+ const DEFAULT_FAVICON_PATH = fileURLToPath(new URL('../assets/default-favicon.svg', import.meta.url))
13
+
12
14
  const FAVICON_SIZES = {
13
15
  ico: [16, 32],
14
16
  appleTouchIcon: 180,
@@ -16,48 +18,80 @@ const FAVICON_SIZES = {
16
18
  pwaIcon512: 512
17
19
  } as const
18
20
 
21
+ export function resolveFaviconSource(
22
+ contentDir: string,
23
+ favicon: string,
24
+ ): { sourcePath: string; isDefault: boolean } | null {
25
+ if (typeof favicon === 'string' && favicon.trim().length > 0) {
26
+ const candidate = path.resolve(contentDir, favicon)
27
+ if (fs.pathExistsSync(candidate)) {
28
+ return { sourcePath: candidate, isDefault: false }
29
+ }
30
+ }
31
+
32
+ if (fs.pathExistsSync(DEFAULT_FAVICON_PATH)) {
33
+ return { sourcePath: DEFAULT_FAVICON_PATH, isDefault: true }
34
+ }
35
+
36
+ return null
37
+ }
38
+
39
+ export interface GenerateFaviconsOptions {
40
+ contentDir?: string
41
+ config?: { favicon?: string; site?: { name?: string } }
42
+ outputDir?: string
43
+ }
44
+
19
45
  /**
20
46
  * Generate favicons from the active mdsite config
21
47
  */
22
- export async function generateFavicons() {
23
- const { contentDir, config } = loadMdsiteConfigSync()
24
- const logoPath = path.join(contentDir, 'logo.svg')
25
- const faviconDir = path.join(contentDir, 'favicon')
26
-
27
- // Check if logo exists
28
- if (!await fs.pathExists(logoPath)) {
29
- console.error(`❌ Logo not found: ${logoPath}`)
48
+ export async function generateFavicons(options: GenerateFaviconsOptions = {}): Promise<boolean> {
49
+ const resolved = options.contentDir && options.config
50
+ ? { contentDir: options.contentDir, config: options.config }
51
+ : loadMdsiteConfigSync()
52
+ const { contentDir, config } = resolved
53
+ const siteName = (config as { site?: { name?: string } }).site?.name ?? 'site'
54
+
55
+ const resolvedSource = resolveFaviconSource(contentDir, config.favicon ?? '')
56
+
57
+ if (!resolvedSource) {
58
+ console.error('❌ No favicon source available (configured source missing AND bundled default not found).')
30
59
  return false
31
60
  }
32
61
 
33
- await fs.ensureDir(faviconDir)
62
+ const { sourcePath } = resolvedSource
63
+ const publicDir = options.outputDir ?? path.resolve(__dirname, '..', 'public')
64
+ await fs.ensureDir(publicDir)
34
65
 
35
- console.log(`🎨 Generating favicons for site: ${config.site.name}`)
36
- console.log(` Source: ${logoPath}`)
37
- console.log(` Output: ${faviconDir}`)
66
+ if (resolvedSource.isDefault) {
67
+ console.log('ℹ️ No favicon source configured (config.favicon empty or file not found). Using bundled default favicon.')
68
+ }
69
+
70
+ console.log(`🎨 Generating favicons for site: ${siteName}`)
71
+ console.log(` Source: ${sourcePath}`)
72
+ console.log(` Output: ${publicDir}`)
38
73
 
39
74
  try {
40
75
  // Copy SVG as-is (for modern browsers)
41
- const svgTargetPath = path.join(faviconDir, 'favicon.svg')
42
- await fs.copy(logoPath, svgTargetPath)
76
+ const svgTargetPath = path.join(publicDir, 'favicon.svg')
77
+ await fs.copy(sourcePath, svgTargetPath)
43
78
  console.log(` ✓ SVG: favicon.svg`)
44
79
 
45
80
  // Generate ICO (32x32 with transparent padding)
46
- const icoTargetPath = path.join(faviconDir, 'favicon.ico')
47
- const png32Buffer = await sharp(logoPath)
81
+ const icoTargetPath = path.join(publicDir, 'favicon.ico')
82
+ const png32Buffer = await sharp(sourcePath)
48
83
  .resize(32, 32, {
49
84
  fit: 'contain',
50
85
  background: { r: 255, g: 255, b: 255, alpha: 0 }
51
86
  })
52
87
  .png()
53
88
  .toBuffer()
54
-
55
89
  await fs.writeFile(icoTargetPath, png32Buffer)
56
90
  console.log(` ✓ ICO: favicon.ico`)
57
91
 
58
92
  // Generate Apple Touch Icon (180x180 with transparent padding)
59
- const appleTouchPath = path.join(faviconDir, 'apple-touch-icon.png')
60
- await sharp(logoPath)
93
+ const appleTouchPath = path.join(publicDir, 'apple-touch-icon.png')
94
+ await sharp(sourcePath)
61
95
  .resize(FAVICON_SIZES.appleTouchIcon, FAVICON_SIZES.appleTouchIcon, {
62
96
  fit: 'contain',
63
97
  background: { r: 255, g: 255, b: 255, alpha: 0 }
@@ -67,8 +101,8 @@ export async function generateFavicons() {
67
101
  console.log(` ✓ Apple Touch Icon: apple-touch-icon.png`)
68
102
 
69
103
  // Generate PWA Icon 192x192
70
- const icon192Path = path.join(faviconDir, 'icon-192.png')
71
- await sharp(logoPath)
104
+ const icon192Path = path.join(publicDir, 'icon-192.png')
105
+ await sharp(sourcePath)
72
106
  .resize(FAVICON_SIZES.pwaIcon192, FAVICON_SIZES.pwaIcon192, {
73
107
  fit: 'contain',
74
108
  background: { r: 255, g: 255, b: 255, alpha: 0 }
@@ -78,8 +112,8 @@ export async function generateFavicons() {
78
112
  console.log(` ✓ PWA Icon 192: icon-192.png`)
79
113
 
80
114
  // Generate PWA Icon 512x512
81
- const icon512Path = path.join(faviconDir, 'icon-512.png')
82
- await sharp(logoPath)
115
+ const icon512Path = path.join(publicDir, 'icon-512.png')
116
+ await sharp(sourcePath)
83
117
  .resize(FAVICON_SIZES.pwaIcon512, FAVICON_SIZES.pwaIcon512, {
84
118
  fit: 'contain',
85
119
  background: { r: 255, g: 255, b: 255, alpha: 0 }
@@ -88,48 +122,14 @@ export async function generateFavicons() {
88
122
  .toFile(icon512Path)
89
123
  console.log(` ✓ PWA Icon 512: icon-512.png`)
90
124
 
91
- console.log(`✅ Favicons generated successfully for ${config.site.name}\n`)
125
+ console.log(`✅ Favicons generated successfully for ${siteName}\n`)
92
126
  return true
93
127
  } catch (error) {
94
- console.error(`❌ Failed to generate favicons for ${config.site.name}:`, error)
128
+ console.error(`❌ Failed to generate favicons for ${siteName}:`, error)
95
129
  return false
96
130
  }
97
131
  }
98
132
 
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
133
  /**
134
134
  * Generate web manifest for PWA support
135
135
  */
@@ -183,7 +183,6 @@ if (import.meta.url === `file://${process.argv[1]}`) {
183
183
  ; (async () => {
184
184
  const success = await generateFavicons()
185
185
  if (success) {
186
- await copyFaviconsToPublic()
187
186
  await generateWebManifest(config.site.name)
188
187
  } else {
189
188
  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
  }