From 003e46116df96c8b47147474eeeb45cb3e2fab14 Mon Sep 17 00:00:00 2001 From: stiffneckjim <22213990+stiffneckjim@users.noreply.github.com> Date: Tue, 26 May 2026 18:06:33 +0100 Subject: [PATCH 1/2] feat: add asset watcher and image embedding guidance for sponsor logos - Watch assets/ directory during pnpm dev for automatic rebuilds - Document relative image path patterns for local and GitHub Pages - Add gitflow reminder to workflow section in README --- README.md | 33 ++++++- assets/sponsors/sponsors-desklodge.png | Bin 0 -> 9310 bytes assets/sponsors/sponsors-io-academy.webp | Bin 0 -> 13678 bytes assets/sponsors/sponsors-tuppenny-well.svg | 77 +++++++++++++++ scripts/dev.mjs | 103 ++++++++++++++++++++- slides.md | 32 +++++-- 6 files changed, 234 insertions(+), 11 deletions(-) create mode 100644 assets/sponsors/sponsors-desklodge.png create mode 100644 assets/sponsors/sponsors-io-academy.webp create mode 100644 assets/sponsors/sponsors-tuppenny-well.svg diff --git a/README.md b/README.md index 8f66ba1..672a850 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ pnpm install pnpm dev ``` -`pnpm dev` builds `dist/index.html` (including Mermaid transformation), serves `dist/`, and rebuilds when `slides.md` changes. +`pnpm dev` builds `dist/index.html` (including Mermaid transformation), +serves `dist/`, and rebuilds when `slides.md` changes. By default it uses port `8080`; if `8080` is busy it automatically uses the next available port and prints the URL. @@ -36,6 +37,29 @@ pnpm export The HTML build is written to `dist/index.html`. The PDF export is written to `dist/slides.pdf`. +## Images and sponsor logos + +Store images under `assets/` and reference them from `slides.md` +with relative paths such as `./assets/sponsors/acme.png`. + +That works in both places because: + +- local development serves `dist/index.html` +- the build copies `assets/` to `dist/assets/` +- GitHub Pages publishes the same built files from `dist/` + +Avoid absolute URLs such as `/assets/acme.png`, because a project +site is published under `/intro-to-home-networking/` rather than the +domain root. + +Standard Markdown image: + +```md +![width:220px](./assets/sponsors/acme.png) +``` + +While `pnpm dev` is running, changes to files inside `assets/` trigger a rebuild automatically. + ## GitHub Pages deployment This repo is designed to deploy from GitHub Actions to GitHub Pages. @@ -60,6 +84,11 @@ This repository follows gitflow for version control: - Feature/fix branches: branch from `develop` with naming pattern `feature/` or `fix/`. **Workflow:** + +Follow gitflow strictly for day-to-day changes: create a new feature or fix +branch from `develop` before you start editing, and avoid making working +changes directly on `develop`. + 1. Create a feature or fix branch from `develop`. 2. Make your changes, test locally with `pnpm dev`. 3. Push the branch and open a PR against `develop`. @@ -68,4 +97,4 @@ This repository follows gitflow for version control: ## Next edits -If you want to expand the deck later, add images or diagrams under an `assets/` directory and reference them from `slides.md`. \ No newline at end of file +If you want to expand the deck later, add images or diagrams under an `assets/` directory and reference them from `slides.md`. diff --git a/assets/sponsors/sponsors-desklodge.png b/assets/sponsors/sponsors-desklodge.png new file mode 100644 index 0000000000000000000000000000000000000000..f395724f09f93285dc0bd8c98a856a4bc0a3721a GIT binary patch literal 9310 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 literal 0 HcmV?d00001 diff --git a/assets/sponsors/sponsors-io-academy.webp b/assets/sponsors/sponsors-io-academy.webp new file mode 100644 index 0000000000000000000000000000000000000000..a4cc1575e36563cd3c3552225ab12c9d147ccfdc GIT binary patch literal 13678 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 E08|u7&j0`b literal 0 HcmV?d00001 diff --git a/assets/sponsors/sponsors-tuppenny-well.svg b/assets/sponsors/sponsors-tuppenny-well.svg new file mode 100644 index 0000000..a2f6ca7 --- /dev/null +++ b/assets/sponsors/sponsors-tuppenny-well.svg @@ -0,0 +1,77 @@ + + + + diff --git a/scripts/dev.mjs b/scripts/dev.mjs index 5814f0c..e005f64 100644 --- a/scripts/dev.mjs +++ b/scripts/dev.mjs @@ -1,16 +1,21 @@ import { execFileSync, spawn } from 'node:child_process'; import net from 'node:net'; -import { watchFile } from 'node:fs'; +import { watch, watchFile } from 'node:fs'; +import { access, readdir } from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const rootDir = process.cwd(); const slidesFile = path.join(rootDir, 'slides.md'); +const assetsDir = path.join(rootDir, 'assets'); const buildScript = path.join(__dirname, 'build.mjs'); let isBuilding = false; let rebuildPending = false; +let rebuildTimer; + +const assetWatchers = new Map(); function isPortAvailable(port) { return new Promise((resolve) => { @@ -59,8 +64,82 @@ function buildDeck() { } } +function queueBuild(message) { + if (rebuildTimer) { + clearTimeout(rebuildTimer); + } + + rebuildTimer = setTimeout(() => { + console.log(`[dev] ${message}; rebuilding...`); + buildDeck(); + }, 100); +} + +async function collectAssetDirectories(directory, directories = []) { + directories.push(directory); + + const entries = await readdir(directory, { withFileTypes: true }); + for (const entry of entries) { + if (!entry.isDirectory()) { + continue; + } + + await collectAssetDirectories(path.join(directory, entry.name), directories); + } + + return directories; +} + +async function syncAssetWatchers() { + try { + await access(assetsDir); + } catch { + for (const watcher of assetWatchers.values()) { + watcher.close(); + } + + assetWatchers.clear(); + return; + } + + const directories = await collectAssetDirectories(assetsDir); + const expectedDirectories = new Set(directories); + + for (const watchedDirectory of assetWatchers.keys()) { + if (expectedDirectories.has(watchedDirectory)) { + continue; + } + + assetWatchers.get(watchedDirectory)?.close(); + assetWatchers.delete(watchedDirectory); + } + + for (const directory of directories) { + if (assetWatchers.has(directory)) { + continue; + } + + const watcher = watch(directory, (eventType, filename) => { + const changedPath = filename ? path.relative(rootDir, path.join(directory, filename.toString())) : path.relative(rootDir, directory); + queueBuild(`asset ${eventType} detected in ${changedPath}`); + + if (eventType === 'rename') { + void syncAssetWatchers(); + } + }); + + watcher.on('error', (error) => { + console.error(`[dev] Asset watcher error in ${path.relative(rootDir, directory)}:`); + console.error(error); + }); + + assetWatchers.set(directory, watcher); + } +} + async function main() { buildDeck(); + await syncAssetWatchers(); const requestedPort = Number(process.env.PORT ?? '8080'); const port = await findAvailablePort(requestedPort); @@ -76,12 +155,30 @@ async function main() { watchFile(slidesFile, { interval: 500 }, (current, previous) => { if (current.mtimeMs !== previous.mtimeMs) { - console.log('[dev] slides.md changed; rebuilding...'); - buildDeck(); + queueBuild('slides.md changed'); } }); + const rootWatcher = watch(rootDir, (eventType, filename) => { + if (filename?.toString() !== 'assets') { + return; + } + + void syncAssetWatchers(); + queueBuild(`assets directory ${eventType} detected`); + }); + function shutdown(code = 0) { + if (rebuildTimer) { + clearTimeout(rebuildTimer); + } + + rootWatcher.close(); + + for (const watcher of assetWatchers.values()) { + watcher.close(); + } + server.kill('SIGTERM'); process.exit(code); } diff --git a/slides.md b/slides.md index f16f9a3..0ad9e5f 100644 --- a/slides.md +++ b/slides.md @@ -94,17 +94,37 @@ style: | ## 1. Welcome & Context -- A brief introduction -- Our sponsors -- Rolling Q&A +### A bit about me ---- +I'm on [LinkedIn](www.linkedin.com/in/jamesrennison) + +### Our sponsors + +**The generous hosts of our IRL meetups** + +![width:220px](./assets/sponsors/sponsors-desklodge.png) + +**The magnificent financial contributors** + +![width:220px](./assets/sponsors/sponsors-io-academy.webp) -### Why home networking matters +**The newest sponsor and hosts of our website** + +![width:220px](./assets/sponsors/sponsors-tuppenny-well.svg) + +### Any questions? + +Rolling Q&A - please don't stand on ceremony --- -### Common pain points: ads, tracking, slow and insecure DNS +## Why home networking matters + +### Common pain points + +- Dubious hardware from your ISP +- Ads and tracking +- Slow and insecure DNS --- From be463ec56f0c18d5f9cbdf18addf8667fda118a3 Mon Sep 17 00:00:00 2001 From: James Rennison <22213990+stiffneckjim@users.noreply.github.com> Date: Tue, 26 May 2026 18:11:21 +0100 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- scripts/dev.mjs | 76 +++++++++++++++++++++++++++---------------------- slides.md | 2 +- 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/scripts/dev.mjs b/scripts/dev.mjs index e005f64..31f679e 100644 --- a/scripts/dev.mjs +++ b/scripts/dev.mjs @@ -90,51 +90,59 @@ async function collectAssetDirectories(directory, directories = []) { return directories; } -async function syncAssetWatchers() { - try { - await access(assetsDir); - } catch { - for (const watcher of assetWatchers.values()) { - watcher.close(); +let syncAssetWatchersQueue = Promise.resolve(); + +function syncAssetWatchers() { + const run = async () => { + try { + await access(assetsDir); + } catch { + for (const watcher of assetWatchers.values()) { + watcher.close(); + } + + assetWatchers.clear(); + return; } - assetWatchers.clear(); - return; - } + const directories = await collectAssetDirectories(assetsDir); + const expectedDirectories = new Set(directories); - const directories = await collectAssetDirectories(assetsDir); - const expectedDirectories = new Set(directories); + for (const watchedDirectory of assetWatchers.keys()) { + if (expectedDirectories.has(watchedDirectory)) { + continue; + } - for (const watchedDirectory of assetWatchers.keys()) { - if (expectedDirectories.has(watchedDirectory)) { - continue; + assetWatchers.get(watchedDirectory)?.close(); + assetWatchers.delete(watchedDirectory); } - assetWatchers.get(watchedDirectory)?.close(); - assetWatchers.delete(watchedDirectory); - } + for (const directory of directories) { + if (assetWatchers.has(directory)) { + continue; + } - for (const directory of directories) { - if (assetWatchers.has(directory)) { - continue; - } + const watcher = watch(directory, (eventType, filename) => { + const changedPath = filename ? path.relative(rootDir, path.join(directory, filename.toString())) : path.relative(rootDir, directory); + queueBuild(`asset ${eventType} detected in ${changedPath}`); - const watcher = watch(directory, (eventType, filename) => { - const changedPath = filename ? path.relative(rootDir, path.join(directory, filename.toString())) : path.relative(rootDir, directory); - queueBuild(`asset ${eventType} detected in ${changedPath}`); + if (eventType === 'rename') { + void syncAssetWatchers(); + } + }); - if (eventType === 'rename') { - void syncAssetWatchers(); - } - }); + watcher.on('error', (error) => { + console.error(`[dev] Asset watcher error in ${path.relative(rootDir, directory)}:`); + console.error(error); + }); - watcher.on('error', (error) => { - console.error(`[dev] Asset watcher error in ${path.relative(rootDir, directory)}:`); - console.error(error); - }); + assetWatchers.set(directory, watcher); + } + }; - assetWatchers.set(directory, watcher); - } + const syncPromise = syncAssetWatchersQueue.then(run, run); + syncAssetWatchersQueue = syncPromise.catch(() => {}); + return syncPromise; } async function main() { diff --git a/slides.md b/slides.md index 0ad9e5f..25dca99 100644 --- a/slides.md +++ b/slides.md @@ -96,7 +96,7 @@ style: | ### A bit about me -I'm on [LinkedIn](www.linkedin.com/in/jamesrennison) +I'm on [LinkedIn](https://www.linkedin.com/in/jamesrennison) ### Our sponsors