Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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.
Expand All @@ -60,6 +84,11 @@ This repository follows gitflow for version control:
- Feature/fix branches: branch from `develop` with naming pattern `feature/<slug>` or `fix/<slug>`.

**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`.
Expand All @@ -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`.
If you want to expand the deck later, add images or diagrams under an `assets/` directory and reference them from `slides.md`.
Binary file added assets/sponsors/sponsors-desklodge.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/sponsors/sponsors-io-academy.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
77 changes: 77 additions & 0 deletions assets/sponsors/sponsors-tuppenny-well.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
111 changes: 108 additions & 3 deletions scripts/dev.mjs
Original file line number Diff line number Diff line change
@@ -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) => {
Expand Down Expand Up @@ -59,8 +64,90 @@ 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;
}

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;
}

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);
}
};

const syncPromise = syncAssetWatchersQueue.then(run, run);
syncAssetWatchersQueue = syncPromise.catch(() => {});
return syncPromise;
}

async function main() {
buildDeck();
await syncAssetWatchers();

const requestedPort = Number(process.env.PORT ?? '8080');
const port = await findAvailablePort(requestedPort);
Expand All @@ -76,12 +163,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);
}
Expand Down
32 changes: 26 additions & 6 deletions slides.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,37 @@ style: |

## 1. Welcome & Context

- A brief introduction
- Our sponsors
- Rolling Q&A
### A bit about me

---
I'm on [LinkedIn](https://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

---

Expand Down