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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ Until the first release tag exists, the root mirrors the preview build.

To cut a release:

1. Bump `version` in `package.json` (and the `.app-version` badge in `index.html`).
1. Bump `version` in `package.json` — the only place it lives. The build injects
it into the in-app badge (`scripts/build.js`), and the E2E test derives it.
2. Commit and merge to `main`.
3. Tag the merge commit and push the tag:
```bash
Expand Down
10 changes: 7 additions & 3 deletions doc/arc42.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,8 @@ flowchart TD

### Release process

1. Bump `version` in `package.json` and the `.app-version` badge in `index.html`.
1. Bump `version` in `package.json` (single source — the build injects it into
the badge and the E2E test derives it).
2. Merge to `main` (updates `/preview`).
3. `git tag vX.Y.Z && git push origin vX.Y.Z` → version-check gate → deploy to root.

Expand All @@ -321,8 +322,11 @@ flowchart TD
notifications; the singleton is reset in tests for order-independence.
- **Validation.** Structural validator with no external deps; CI validates all
`examples/*.ocf.json`.
- **Versioning.** Version lives in `package.json`, the in-app badge, and the
version test; the CI gate keeps a release tag consistent with `package.json`.
- **Versioning.** The version lives solely in `package.json`. `scripts/build.js`
injects it into the bundle at build time (esbuild `define` → `__APP_VERSION__`,
read from `$npm_package_version`), `main.js` writes it into the badge, and the
E2E test derives the expected value from `package.json`. The CI gate keeps a
release tag consistent with `package.json`.

---

Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</svg>
<h1 class="app-title">OCF<span class="app-title-thin">editor</span></h1>
</div>
<span class="app-version">v0.2.1</span>
<span class="app-version"></span>
</div>
<div class="header-center">
<div class="hdr-group">
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
"node": ">=22"
},
"scripts": {
"build": "esbuild src/main.js --bundle --outfile=ocf-bundle.js --format=iife --global-name=OCFEditor --sourcemap",
"watch": "esbuild src/main.js --bundle --outfile=ocf-bundle.js --format=iife --global-name=OCFEditor --sourcemap --watch",
"build": "node scripts/build.js",
"watch": "node scripts/build.js --watch",
"validate-examples": "node scripts/validate-examples.js",
"test": "npx playwright test",
"test:ui": "npx playwright test --ui"
Expand Down
30 changes: 15 additions & 15 deletions scripts/build-pages.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,35 @@ set -euo pipefail

OUT="${1:-_site}"
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ESBUILD="$ROOT/node_modules/.bin/esbuild"

# Files that make up a deployable site (everything index.html references).
STATIC_FILES=(style.css opencoachingformat-v1.schema.json)

# Build one variant from a git ref into a target directory, stamping the version.
# $1 = git ref (tag or branch), $2 = target dir, $3 = version label for the badge
# Build one variant from a git ref into a target directory.
# $1 = git ref (tag or branch), $2 = target dir, $3 = version label (optional;
# empty → scripts/build.js derives "v<version>" from that ref's package.json).
build_variant() {
local ref="$1" dest="$2" version="$3"
local ref="$1" dest="$2" label="$3"
local src
src="$(mktemp -d)"
echo "→ building '$ref' as version '$version' into $dest"
echo "→ building '$ref' (version: ${label:-from package.json}) into $dest"
git archive "$ref" | tar -x -C "$src"

mkdir -p "$dest"
"$ESBUILD" "$src/src/main.js" --bundle \
--outfile="$dest/ocf-bundle.js" --format=iife \
--global-name=OCFEditor --sourcemap
# Build with this checkout's scripts/build.js (it has esbuild via node_modules)
# against the extracted ref as the working directory, so the version is taken
# from the ref's own package.json — or from APP_VERSION when we pass a label.
# Older refs whose main.js predates build-time injection simply keep their
# static badge, which for a release tag already equals the tag version.
( cd "$src" && APP_VERSION="$label" node "$ROOT/scripts/build.js" )

mkdir -p "$dest"
cp "$src/ocf-bundle.js" "$src/ocf-bundle.js.map" "$dest"/
cp "$src/index.html" "$dest/index.html"
for f in "${STATIC_FILES[@]}"; do
[ -e "$src/$f" ] && cp "$src/$f" "$dest/"
done
[ -d "$src/examples" ] && cp -r "$src/examples" "$dest/examples"

# Stamp the in-app version badge so the deployed build self-reports its version.
if [ -n "$version" ]; then
sed -i "s#<span class=\"app-version\">[^<]*</span>#<span class=\"app-version\">${version}</span>#" "$dest/index.html"
fi

rm -rf "$src"
}

Expand All @@ -65,7 +64,8 @@ fi
LATEST_TAG="$(git tag -l 'v*' --sort=-v:refname | head -n1 || true)"

if [ -n "$LATEST_TAG" ]; then
build_variant "$LATEST_TAG" "$OUT" "$LATEST_TAG"
# Empty label → build.js stamps "v<version>" from the tag's package.json.
build_variant "$LATEST_TAG" "$OUT" ""
else
# No release yet → root shows the preview build so the site is never empty.
echo "→ no v* tag found; root will mirror the preview build"
Expand Down
50 changes: 50 additions & 0 deletions scripts/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env node
//
// Build the OCF Editor bundle with esbuild.
//
// The app version is injected at build time from a single source of truth —
// package.json — so it never has to be kept in sync by hand. npm exposes the
// version as $npm_package_version when this runs via `npm run build`; when the
// script is invoked directly (e.g. by build-pages.sh against a checked-out
// release) it falls back to the package.json in the current working directory.
//
// The display string can be overridden with the APP_VERSION env var (used by
// build-pages.sh to label the /preview build), otherwise it is `v<version>`.
//
// Usage: node scripts/build.js [--watch]
'use strict';

const path = require('path');
const esbuild = require('esbuild');

// Resolve project files relative to the working directory, so this script can
// build either the repo root (npm run build) or an extracted release tree.
const cwd = process.cwd();
const pkg = require(path.join(cwd, 'package.json'));

const version =
process.env.APP_VERSION || `v${process.env.npm_package_version || pkg.version}`;

const options = {
entryPoints: [path.join(cwd, 'src/main.js')],
bundle: true,
outfile: path.join(cwd, 'ocf-bundle.js'),
format: 'iife',
globalName: 'OCFEditor',
sourcemap: true,
define: { __APP_VERSION__: JSON.stringify(version) },
};

(async () => {
if (process.argv.includes('--watch')) {
const ctx = await esbuild.context(options);
await ctx.watch();
console.log(`esbuild: watching for changes (version ${version})…`);
} else {
await esbuild.build(options);
console.log(`esbuild: built ocf-bundle.js (version ${version})`);
}
})().catch((err) => {
console.error(err);
process.exit(1);
});
5 changes: 5 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ export function init() {
playerControlsEl = document.getElementById('player-controls');
modeToggle = document.getElementById('mode-toggle');

// Show the app version, injected at build time from package.json (see
// scripts/build.js). Single source of truth — no hand-maintained copies.
const versionEl = document.querySelector('.app-version');
if (versionEl) versionEl.textContent = __APP_VERSION__;

// Init UI components
new Palette(paletteEl, editorState);
new Toolbar(toolbarEl, editorState);
Expand Down
7 changes: 6 additions & 1 deletion tests/editor.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
*/

import { test, expect } from '@playwright/test';
import { readFileSync } from 'node:fs';

// Single source of truth: the version the app should display comes from
// package.json (injected into the bundle at build time by scripts/build.js).
const { version } = JSON.parse(readFileSync('package.json', 'utf8'));

// ─── Helpers ─────────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -31,7 +36,7 @@ test.describe('App loads', () => {
test('page title and version are correct', async ({ page }) => {
await openEditor(page);
await expect(page).toHaveTitle(/OCF Editor/);
await expect(page.locator('.app-version')).toHaveText('v0.2.1');
await expect(page.locator('.app-version')).toHaveText(`v${version}`);
});

test('court SVG is rendered (no 404 on bundle)', async ({ page }) => {
Expand Down