From d9d64212ac76da232ba2b395a836266c1ccee2e0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Oct 2025 04:38:40 +0000 Subject: [PATCH 1/7] Initial plan From 784f80b515cfc3a34cc0c5294017a9176d8097b2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Oct 2025 04:58:36 +0000 Subject: [PATCH 2/7] Add favicon generation from logo feature --- packages/hyperbook/build.ts | 40 +++ .../hyperbook/helpers/generate-favicons.ts | 38 +++ packages/hyperbook/package.json | 5 +- packages/markdown/src/rehypeHtmlStructure.ts | 10 + .../tests/__snapshots__/process.test.ts.snap | 18 +- pnpm-lock.yaml | 322 ++++++++++++++++-- 6 files changed, 396 insertions(+), 37 deletions(-) create mode 100644 packages/hyperbook/helpers/generate-favicons.ts diff --git a/packages/hyperbook/build.ts b/packages/hyperbook/build.ts index 934b2c95..9dda19a1 100644 --- a/packages/hyperbook/build.ts +++ b/packages/hyperbook/build.ts @@ -378,6 +378,46 @@ async function runBuild( } process.stdout.write("\n"); + // Generate favicons if logo exists and no favicon.ico is present + const faviconPath = path.join(rootOut, "favicon.ico"); + let faviconExists = false; + try { + await fs.access(faviconPath); + faviconExists = true; + } catch (e) { + // Favicon doesn't exist + } + + if (!faviconExists && hyperbookJson.logo) { + console.log(`${chalk.blue(`[${prefix}]`)} Generating favicons from logo.`); + // Resolve logo path - it can be relative to public folder or absolute URL + let logoPath = hyperbookJson.logo; + + // If logo starts with /, it's in the public folder + if (logoPath.startsWith("/")) { + logoPath = path.join(root, "public", logoPath); + } else if (!logoPath.includes("://")) { + // Relative path + logoPath = path.join(root, logoPath); + } + + // Only generate if logo is a local file (not a URL) + if (!hyperbookJson.logo.includes("://")) { + try { + await fs.access(logoPath); + const { generateFavicons } = await import("./helpers/generate-favicons"); + await generateFavicons(logoPath, rootOut); + console.log( + `${chalk.green(`[${prefix}]`)} Favicons generated successfully.`, + ); + } catch (e) { + console.log( + `${chalk.yellow(`[${prefix}]`)} Warning: Could not generate favicons. Logo file not found: ${logoPath}`, + ); + } + } + } + i = 1; for (let directive of directives) { const assetsDirectivePath = path.join(assetsPath, `directive-${directive}`); diff --git a/packages/hyperbook/helpers/generate-favicons.ts b/packages/hyperbook/helpers/generate-favicons.ts new file mode 100644 index 00000000..62673895 --- /dev/null +++ b/packages/hyperbook/helpers/generate-favicons.ts @@ -0,0 +1,38 @@ +import path from "path"; +import fs from "fs/promises"; +import { makeDir } from "./make-dir"; + +export async function generateFavicons( + logoPath: string, + outputDir: string, +): Promise { + // Dynamic import to avoid bundling issues with sharp + const { favicons } = await import("favicons"); + + const config = { + path: "/", + icons: { + android: false, + appleIcon: false, + appleStartup: false, + favicons: true, + windows: false, + yandex: false, + }, + }; + + const response = await favicons(logoPath, config); + + // Create output directory if it doesn't exist + await makeDir(outputDir, { recursive: true }); + + // Write favicon files + for (const file of response.images) { + await fs.writeFile(path.join(outputDir, file.name), file.contents); + } + + // Write HTML files (manifest, browserconfig, etc.) if needed + for (const file of response.files) { + await fs.writeFile(path.join(outputDir, file.name), file.contents); + } +} diff --git a/packages/hyperbook/package.json b/packages/hyperbook/package.json index 5e3641c9..c054cf74 100644 --- a/packages/hyperbook/package.json +++ b/packages/hyperbook/package.json @@ -28,7 +28,10 @@ "version": "pnpm build", "lint": "tsc --noEmit", "dev": "ncc build ./index.ts -w -o dist/", - "build": "rimraf dist && ncc build ./index.ts -o ./dist/ --no-cache --no-source-map-register && node postbuild.mjs" + "build": "rimraf dist && ncc build ./index.ts -o ./dist/ --no-cache --no-source-map-register --external favicons --external sharp && node postbuild.mjs" + }, + "dependencies": { + "favicons": "^7.2.0" }, "devDependencies": { "@hyperbook/fs": "workspace:*", diff --git a/packages/markdown/src/rehypeHtmlStructure.ts b/packages/markdown/src/rehypeHtmlStructure.ts index 168345fa..5eaea82b 100644 --- a/packages/markdown/src/rehypeHtmlStructure.ts +++ b/packages/markdown/src/rehypeHtmlStructure.ts @@ -270,6 +270,16 @@ export default (ctx: HyperbookContext) => () => { }, children: [], }, + { + type: "element", + tagName: "link", + properties: { + rel: "icon", + type: "image/x-icon", + href: makeUrl(["favicon.ico"], "public"), + }, + children: [], + }, { type: "element", tagName: "link", diff --git a/packages/markdown/tests/__snapshots__/process.test.ts.snap b/packages/markdown/tests/__snapshots__/process.test.ts.snap index 789f932a..afc4b888 100644 --- a/packages/markdown/tests/__snapshots__/process.test.ts.snap +++ b/packages/markdown/tests/__snapshots__/process.test.ts.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`process > should add showLineNumbers 1`] = ` -"Markdown Referenz - Hyperbook Dokumenation