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
5 changes: 4 additions & 1 deletion .github/workflows/agent-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ on:
pull_request:
paths:
- '2.0/**'
- '1.0/mkdocs.yml'
- 'scripts/**'
- '.github/workflows/agent-docs.yml'
workflow_dispatch:

Expand All @@ -19,8 +21,9 @@ jobs:
with:
python-version: '3.11'
- name: Install documentation tools
run: python -m pip install -r 2.0/requirements-agent-docs.txt
run: python -m pip install mkdocs-material -r 2.0/requirements-agent-docs.txt
- name: Check Markdown exports and documentation contracts
run: |
python -m unittest discover -s scripts
python -m unittest discover -s 2.0/scripts -p 'test_agent_docs.py'
python 2.0/scripts/check_agent_contract.py --live
2 changes: 1 addition & 1 deletion .github/workflows/workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
run: |
# The build image enables pip user installs; --target needs that disabled.
wodby ci run -e HOME=/home/wodby -i wodby/mkdocs -- \
sh -c "PIP_USER=false python3 -m pip install --target /tmp/wodby-docs-python -r 2.0/requirements-agent-docs.txt && export PYTHONPATH=/tmp/wodby-docs-python && mkdir sites && cd 1.0 && sh scripts/check-docs.sh -d ../sites/1.0 && cd ../2.0 && python3 -m unittest discover -s scripts -p test_agent_docs.py && python3 scripts/check_agent_contract.py && mkdocs build --strict -d ../sites/2.0"
sh -c "PIP_USER=false python3 -m pip install --target /tmp/wodby-docs-python -r 2.0/requirements-agent-docs.txt && export PYTHONPATH=/tmp/wodby-docs-python && python3 -m unittest discover -s scripts && mkdir sites && cd 1.0 && sh scripts/check-docs.sh -d ../sites/1.0 && cd ../2.0 && python3 -m unittest discover -s scripts -p test_agent_docs.py && python3 scripts/check_agent_contract.py && mkdocs build --strict -d ../sites/2.0"

- name: Build image
run: wodby ci build nginx -f Dockerfile
Expand Down
3 changes: 3 additions & 0 deletions 1.0/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ theme:
name: Switch to light mode
custom_dir: 'theme'

hooks:
- ../scripts/version_assets.py

extra_css: ["assets/wodby.css"]
extra_javascript: ["assets/intercom.js"]

Expand Down
8 changes: 4 additions & 4 deletions 1.0/theme/assets/wodby.css
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,13 @@ body { -webkit-font-smoothing: antialiased; }
}

/* The two-tone mark sits on white so its original colors work in either theme. */
.md-header .md-logo:has(img[src$="/logo.svg"]),
.md-nav--primary .md-nav__title .md-logo:has(img[src$="/logo.svg"]) {
.md-header .md-logo:has(img[src*="/images/logo."]),
.md-nav--primary .md-nav__title .md-logo:has(img[src*="/images/logo."]) {
background: #ffffff;
border: 1px solid var(--docs-border);
}
.md-header .md-logo img[src$="/logo.svg"],
.md-nav--primary .md-nav__title .md-logo img[src$="/logo.svg"] {
.md-header .md-logo img[src*="/images/logo."],
.md-nav--primary .md-nav__title .md-logo img[src*="/images/logo."] {
width: 2rem;
height: 1.4rem;
object-fit: contain;
Expand Down
1 change: 1 addition & 0 deletions 2.0/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ plugins:
'services/volumes.md': 'services/configuration.md#volumes'

hooks:
- ../scripts/version_assets.py
- scripts/include_generated_reference_sitemap.py
- scripts/agent_docs.py

Expand Down
8 changes: 4 additions & 4 deletions 2.0/theme/assets/wodby.css
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,13 @@ body { -webkit-font-smoothing: antialiased; }
}

/* The two-tone mark sits on white so its original colors work in either theme. */
.md-header .md-logo:has(img[src$="/logo.svg"]),
.md-nav--primary .md-nav__title .md-logo:has(img[src$="/logo.svg"]) {
.md-header .md-logo:has(img[src*="/images/logo."]),
.md-nav--primary .md-nav__title .md-logo:has(img[src*="/images/logo."]) {
background: #ffffff;
border: 1px solid var(--docs-border);
}
.md-header .md-logo img[src$="/logo.svg"],
.md-nav--primary .md-nav__title .md-logo img[src$="/logo.svg"] {
.md-header .md-logo img[src*="/images/logo."],
.md-nav--primary .md-nav__title .md-logo img[src*="/images/logo."] {
width: 2rem;
height: 1.4rem;
object-fit: contain;
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,10 @@
See https://wodby.com/docs

Docs built using [mkdocs](http://www.mkdocs.org) with [material theme](https://github.com/squidfunk/mkdocs-material)

Custom theme CSS, JavaScript, logos, and favicons receive content-hashed filenames
at build time through `scripts/version_assets.py` (MkDocs 1.6 or newer). Edit the
source assets normally; their public URLs change automatically when their contents
change. Material's bundled assets already use versioned filenames.

Run the asset-versioning checks with `python -m unittest discover -s scripts`.
73 changes: 73 additions & 0 deletions scripts/test_version_assets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Verify asset changes invalidate URLs without invalidating unchanged assets."""

from pathlib import Path
from tempfile import TemporaryDirectory
import unittest

from mkdocs.config import load_config
from mkdocs.structure.files import Files

import version_assets


class AssetVersionTests(unittest.TestCase):
def setUp(self):
self.temp = TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
(self.root / 'docs').mkdir()
self.assets = self.root / 'theme' / 'assets'
self.assets.mkdir(parents=True)
for name, content in [('style.css', 'body { color: blue; }'),
('app.js', 'console.log("ready");'),
('logo.svg', '<svg/>')]:
(self.assets / name).write_text(content)
cfg = self.root / 'mkdocs.yml'
cfg.write_text('''site_name: Test
site_dir: output
theme:
name: material
custom_dir: theme
logo: assets/logo.svg
favicon: assets/logo.svg
extra_css: [assets/style.css, 'https://example.com/external.css']
extra_javascript: [assets/app.js]
''')
self.config = load_config(str(cfg))
self.config.plugins._current_plugin = "asset-version-test"
version_assets._source_urls.clear()

def build_assets(self):
return version_assets.on_files(Files([]), config=self.config)

def test_local_assets_are_emitted_and_external_urls_preserved(self):
files = self.build_assets()
urls = [self.config.extra_css[0], self.config.extra_javascript[0],
self.config.theme['logo'], self.config.theme['favicon']]
self.assertEqual(len(files), 3)
for url in urls:
self.assertRegex(str(url), r'\.[0-9a-f]{16}\.')
self.assertIsNotNone(files.get_file_from_path(str(url)))
self.assertEqual(self.config.extra_css[1], 'https://example.com/external.css')
self.assertEqual(files.get_file_from_path(urls[0]).content_bytes,
(self.assets / 'style.css').read_bytes())

def test_unchanged_rebuild_keeps_urls(self):
self.build_assets()
previous = list(self.config.extra_css)
self.build_assets()
self.assertEqual(previous, self.config.extra_css)

def test_changed_asset_gets_new_url_on_reused_config(self):
self.build_assets()
css = self.config.extra_css[0]
logo = self.config.theme['logo']
(self.assets / 'style.css').write_text('body { color: green; }')
files = self.build_assets()
self.assertNotEqual(css, self.config.extra_css[0])
self.assertEqual(logo, self.config.theme['logo'])
self.assertIsNotNone(files.get_file_from_path(self.config.extra_css[0]))


if __name__ == '__main__':
unittest.main()
40 changes: 40 additions & 0 deletions scripts/version_assets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Give custom theme assets content-addressed URLs for safe long-lived caching."""

from hashlib import sha256
from pathlib import Path
from urllib.parse import urlsplit

from mkdocs.structure.files import File

# MkDocs serve can reuse the mutated config between builds.
_source_urls = {}


def on_files(files, *, config):
theme_dir = Path(config.theme.custom_dir)
generated = set()

def version(url):
original = _source_urls.get(str(url), str(url))
parsed = urlsplit(original)
if parsed.scheme or parsed.netloc or original.startswith('/'):
return url
source = theme_dir / parsed.path
if not source.is_file():
return url
content = source.read_bytes()
path = Path(parsed.path)
digest = sha256(content).hexdigest()[:16]
target = path.with_name(f'{path.stem}.{digest}{path.suffix}').as_posix()
if target not in generated:
files.append(File.generated(config, target, content=content))
generated.add(target)
_source_urls[target] = original
return target

config.extra_css = [version(url) for url in config.extra_css]
config.extra_javascript = [version(url) for url in config.extra_javascript]
for name in ('logo', 'favicon'):
if config.theme.get(name):
config.theme[name] = version(config.theme[name])
return files
Loading