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
42 changes: 32 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,16 @@ on:
pull_request:
branches: [main]

# Allow one concurrent deployment; newer runs supersede in-progress ones.
concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
name: Build & Validate
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
node-version-file: '.nvmrc'
cache: 'npm'
- run: npm ci
- run: npm run build
Expand All @@ -31,26 +27,45 @@ jobs:
name: Version matches tag
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
node-version-file: '.nvmrc'
- run: bash scripts/check-version.sh "${GITHUB_REF_NAME}"

test:
name: E2E Tests
runs-on: ubuntu-latest
needs: build
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
node-version-file: '.nvmrc'
cache: 'npm'
- run: npm ci
- run: npm run build
- run: npx playwright install chromium --with-deps
# Cache the Playwright browser by its version so we don't re-download
# Chromium on every run (the download/apt step is the slowest and most
# stall-prone part of CI). On a cache hit we only install the system deps.
- name: Resolve Playwright version
id: pw
run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> "$GITHUB_OUTPUT"
- name: Cache Playwright browsers
id: pw-cache
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.pw.outputs.version }}
- name: Install Playwright (browser + system deps)
if: steps.pw-cache.outputs.cache-hit != 'true'
run: npx playwright install chromium --with-deps
- name: Install Playwright system deps (cached browser)
if: steps.pw-cache.outputs.cache-hit == 'true'
run: npx playwright install-deps chromium
- run: npm test

# Assemble the full Pages site (root = newest release tag, /preview = main)
Expand All @@ -60,6 +75,13 @@ jobs:
needs: [build, test]
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
runs-on: ubuntu-latest
timeout-minutes: 15
# Serialize deployments only (one Pages deploy at a time); queued deploys
# wait rather than cancel. Scoped to this job so a slow/stuck test run never
# blocks the whole CI pipeline.
concurrency:
group: pages
cancel-in-progress: false
permissions:
contents: read
pages: write
Expand All @@ -74,7 +96,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '20'
node-version-file: '.nvmrc'
cache: 'npm'
- run: npm ci
- name: Assemble Pages site (release + preview)
Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
## Getting Started

```bash
git clone https://github.com/opencoachingformat/editor.git
cd editor
git clone https://github.com/opencoachingformat/ocf-editor.git
cd ocf-editor
npm install
npm run build
open index.html
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Schema](https://img.shields.io/badge/Schema-v1.0.0-green.svg)](opencoachingformat-v1.schema.json)
[![CI](https://github.com/opencoachingformat/editor/actions/workflows/ci.yml/badge.svg)](https://github.com/opencoachingformat/editor/actions/workflows/ci.yml)
[![CI](https://github.com/opencoachingformat/ocf-editor/actions/workflows/ci.yml/badge.svg)](https://github.com/opencoachingformat/ocf-editor/actions/workflows/ci.yml)

Reference web editor for the [Open Coaching Format](https://github.com/opencoachingformat/spec) — an open standard for basketball drill diagrams and animations.

Expand All @@ -29,8 +29,8 @@ Reference web editor for the [Open Coaching Format](https://github.com/opencoach
## Quick Start

```bash
git clone https://github.com/opencoachingformat/editor.git
cd editor
git clone https://github.com/opencoachingformat/ocf-editor.git
cd ocf-editor
npm install
npm run build
# Open index.html in your browser
Expand Down 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
12 changes: 8 additions & 4 deletions doc/arc42.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ animations.

> Status: living document. Last substantive update reflects work up to and
> including the mobile-touch / rendering fixes and the release/preview CI
> pipeline (app version `0.2.0`).
> pipeline (app version `0.2.1`).

---

Expand Down 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.0</span>
<span class="app-version"></span>
</div>
<div class="header-center">
<div class="hdr-group">
Expand Down
15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
{
"name": "ocf-editor",
"version": "0.2.0",
"version": "0.2.1",
"description": "Reference web editor for the Open Coaching Format",
"private": true,
"homepage": "https://opencoachingformat.github.io/editor",
"homepage": "https://opencoachingformat.github.io/ocf-editor",
"repository": {
"type": "git",
"url": "https://github.com/opencoachingformat/editor.git"
"url": "https://github.com/opencoachingformat/ocf-editor.git"
},
"bugs": {
"url": "https://github.com/opencoachingformat/editor/issues"
"url": "https://github.com/opencoachingformat/ocf-editor/issues"
},
"license": "MIT",
"engines": {
"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.0');
await expect(page.locator('.app-version')).toHaveText(`v${version}`);
});

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