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
1 change: 1 addition & 0 deletions .claude/skills/dev-workflow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ npm run docs:preview # preview the build
npm run docs:build:versions -- --only latest,dev,v1.3.0 # versioned site as CI builds it (omit --only for all tags)
npm run docs:preview:versions # serve dist under /robotframework-dashboard/ like Pages
npm run docs:build:versions -- --cache-dir .docs-dist-cache # skip versions whose build is already cached (CI does this)
npm run docs:build:versions -- --only dev --no-example # keep the committed example instead of rebuilding it
```

`package.json` exists **only** for the docs site — it has nothing to do with bundling dashboard JS.
Expand Down
2 changes: 2 additions & 0 deletions .claude/skills/release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Do not change the ASCII art banner lines above it.
## Step 3 — Regenerate the example dashboard and database

The example dashboard and database live in `example/robot_dashboard.html` and `example/robot_results.db`.
They are what every *released* docs version serves; the `/dev/` docs rebuild the example from `main` on every
deploy (`scripts/docs/build-versioned-docs.mjs`), so this step is only about the release itself.

Run from anywhere (any OS):

Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ jobs:
cache: npm
- name: Setup Pages
uses: actions/configure-pages@v6
# The /dev/ build rebuilds example/robot_dashboard.html from its worktree, so the
# unreleased example shows what main does instead of what the last release did. Only
# the dependencies of the package are needed: scripts/example.py runs it from source.
- name: Setup Python
uses: actions/setup-python@v7
with:
python-version: '3.12'
- name: Install the package dependencies for the dev example dashboard
run: pip install .
- name: Install dependencies
run: npm ci
# Released versions never change: their finished builds are kept between
Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,5 +248,7 @@ npm run docs:build:versions
npm run docs:build:versions -- --only latest,dev,v1.3.0
npm run docs:build:versions -- --only latest,legacy # latest + all README-only releases
npm run docs:build:versions -- --cache-dir .docs-dist-cache # reuse finished builds between runs, like CI
npm run docs:build:versions -- --only dev --no-example # skip rebuilding the example dashboard
npm run docs:preview:versions
```
The `dev` version rebuilds `example/robot_dashboard.html` from its own worktree first (`scripts/example.py`), so the example under `/dev/` shows what `main` does rather than what the last release did. Every released version keeps the example that was committed for it. The rebuild needs a python with the package dependencies installed; without one the build keeps the committed example and says so, and `--no-example` skips it on purpose. Neither writes into your checkout: the rebuild happens in the temporary worktree.
57 changes: 53 additions & 4 deletions scripts/docs/build-versioned-docs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,17 @@
// node scripts/docs/build-versioned-docs.mjs --only legacy only the README-only versions
// node scripts/docs/build-versioned-docs.mjs --main-ref origin/main
// node scripts/docs/build-versioned-docs.mjs --cache-dir .docs-dist-cache
// node scripts/docs/build-versioned-docs.mjs --no-example keep the committed example dashboard
//
// Env: DOCS_MAIN_REF (same as --main-ref, default "main"),
// DOCS_CACHE_DIR (same as --cache-dir).
// DOCS_CACHE_DIR (same as --cache-dir),
// DOCS_SKIP_EXAMPLE (same as --no-example),
// PYTHON (interpreter used to rebuild the `dev` example dashboard).
//
// The example dashboard is committed and only regenerated when a release is cut, so every
// released version serves the example its own code produced. `dev` documents unreleased main,
// where that file is behind by every merge since the last release, so its example is rebuilt
// from the worktree (scripts/example.py) before the docs are built.
//
// Cache: released versions never change, so with a cache dir each finished
// build is stored there under a fingerprint (commit + everything overlaid +
Expand Down Expand Up @@ -71,11 +79,17 @@ const PATCHES = {
};

function parse_args(argv) {
const args = { only: null, mainRef: process.env.DOCS_MAIN_REF || 'main', cacheDir: process.env.DOCS_CACHE_DIR || null };
const args = {
only: null,
mainRef: process.env.DOCS_MAIN_REF || 'main',
cacheDir: process.env.DOCS_CACHE_DIR || null,
example: !process.env.DOCS_SKIP_EXAMPLE,
};
for (let i = 0; i < argv.length; i++) {
if (argv[i] === '--only') args.only = new Set(argv[++i].split(',').map((s) => s.trim()).filter(Boolean));
else if (argv[i] === '--main-ref') args.mainRef = argv[++i];
else if (argv[i] === '--cache-dir') args.cacheDir = argv[++i];
else if (argv[i] === '--no-example') args.example = false;
else throw new Error(`Unknown argument: ${argv[i]}`);
}
if (args.cacheDir) args.cacheDir = resolve(repoRoot, args.cacheDir);
Expand Down Expand Up @@ -305,7 +319,40 @@ function strip_shared_assets(worktree, build, latest) {
return shared;
}

function build_one(build, env, latest) {
// scripts/example.py imports the fixtures with the source of the worktree it runs in
// (python -m, cwd first on sys.path), so the example matches the code the docs describe.
// It needs the dependencies of the package, not the package itself.
function python_for_example() {
for (const candidate of [process.env.PYTHON, 'python', 'python3']) {
if (!candidate) continue;
try {
execFileSync(candidate, ['-c', 'import robot'], { stdio: 'ignore' });
return candidate;
} catch {
// no such interpreter, or one without robotframework: try the next
}
}
return null;
}

function rebuild_example(worktree, args) {
if (!args.example) {
console.log('[example] --no-example: keeping the committed example dashboard');
return;
}
const python = python_for_example();
if (!python) {
// a docs build is not worth a python install locally, but in CI it means the workflow
// stopped installing one and dev would quietly serve a stale example again
if (process.env.CI) throw new Error('No python with robotframework found to rebuild the example dashboard');
console.log('[example] no python with robotframework: keeping the committed example dashboard');
return;
}
console.log('[example] rebuilding example/robot_dashboard.html from this worktree');
execFileSync(python, ['scripts/example.py'], { cwd: worktree, stdio: 'inherit' });
}

function build_one(build, env, latest, args) {
const worktree = resolve(worktreesDir, build.name.replace(/[^\w.-]/g, '_'));
const outDir = resolve(distDir, build.prefix);
console.log(`\n=== ${build.name} (${build.ref}) -> ${SITE_BASE}${build.prefix}`);
Expand All @@ -317,6 +364,8 @@ function build_one(build, env, latest) {
overlay(worktree);
apply_patches(worktree, build);
const sharedAssets = strip_shared_assets(worktree, build, latest);
// before copy-static.mjs, which copies example/robot_dashboard.html into docs/public
if (build.name === 'dev') rebuild_example(worktree, args);
execFileSync(process.execPath, [resolve(worktree, 'scripts/docs/copy-static.mjs')], {
cwd: worktree,
stdio: 'inherit',
Expand Down Expand Up @@ -377,7 +426,7 @@ function main() {
restored++;
continue;
}
build_one(build, env, latest);
build_one(build, env, latest, args);
if (cacheable) {
store_in_cache(args.cacheDir, build, print);
// tells CI that the cache dir changed and is worth saving again
Expand Down