Skip to content

Commit 25f0711

Browse files
kinlaneclaude
andcommitted
Run Locally: relative asset base + launcher scripts so the unzipped app works
- vite base '/' -> './' so built asset (CSS/JS/worker) paths are relative and resolve from any folder — fixes the unzipped copy where absolute /assets/* 404'd when index.html was opened outside the domain root. - zip now ships start.command / start.sh / start.bat launchers (start a local server + open the browser) and a clearer RUN-LOCALLY.txt explaining a server is required (browsers block modules + workers over file://). Relative base is safe on the live site too (served from root). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a1bb8fd commit 25f0711

2 files changed

Lines changed: 53 additions & 13 deletions

File tree

tools/zip-dist.mjs

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22
// the "Run Locally" button can hand a user the whole app as a static bundle they
33
// serve themselves. Runs after `vite build`; uses the system `zip` (present on
44
// macOS and the ubuntu CI runner).
5+
//
6+
// The build uses a relative base ('./'), so assets resolve from any folder. The
7+
// app still needs a real HTTP server — browsers block ES modules and web workers
8+
// over file:// — so we include double-clickable launchers + a how-to.
59
import { execSync } from 'node:child_process';
6-
import { existsSync, writeFileSync } from 'node:fs';
10+
import { existsSync, writeFileSync, chmodSync } from 'node:fs';
711

812
const OUT = 'api-validator.zip';
913

@@ -12,7 +16,7 @@ if (!existsSync('dist')) {
1216
process.exit(1);
1317
}
1418

15-
// A short how-to that travels inside the archive.
19+
// How-to that travels inside the archive.
1620
writeFileSync(
1721
'dist/RUN-LOCALLY.txt',
1822
[
@@ -21,23 +25,57 @@ writeFileSync(
2125
'This is the fully built, static app. It runs entirely in your browser; there',
2226
'is no backend and no keys are required.',
2327
'',
24-
'1. Unzip this archive.',
25-
'2. From the unzipped folder, start any static web server at the folder ROOT:',
28+
'IMPORTANT: you must serve the folder over http — you cannot just double-click',
29+
'index.html. Browsers block JavaScript modules and web workers over file://,',
30+
'so the page would load but the app would not run.',
2631
'',
27-
' python3 -m http.server 8000',
28-
' or',
29-
' npx serve .',
32+
'Easiest: double-click the launcher for your OS (it starts a local server and',
33+
'opens your browser):',
34+
' • macOS: start.command',
35+
' • Windows: start.bat',
36+
' • Linux: start.sh (or run it in a terminal)',
3037
'',
31-
'3. Open http://localhost:8000/ in your browser.',
32-
'',
33-
'(Opening index.html directly via file:// will not work — ES modules need to be',
34-
'served over http, so use a local server as above.)',
38+
'Or do it by hand, from inside this folder:',
39+
' python3 -m http.server 8000 # then open http://localhost:8000/',
40+
' # or: npx serve .',
3541
'',
3642
'Source: https://github.com/api-commons/api-validator',
3743
'',
3844
].join('\n'),
3945
);
4046

47+
// macOS / Linux launcher — serve this folder and open the browser.
48+
writeFileSync(
49+
'dist/start.command',
50+
[
51+
'#!/bin/bash',
52+
'cd "$(dirname "$0")"',
53+
'PORT=8000',
54+
'URL="http://localhost:$PORT/"',
55+
'echo "Serving API Validator at $URL (press Ctrl+C to stop)"',
56+
'( sleep 1; (command -v open >/dev/null && open "$URL") || (command -v xdg-open >/dev/null && xdg-open "$URL") ) &',
57+
'python3 -m http.server "$PORT" 2>/dev/null || python -m http.server "$PORT"',
58+
'',
59+
].join('\n'),
60+
);
61+
// A plain .sh alias for Linux users who prefer it.
62+
writeFileSync('dist/start.sh', 'exec "$(dirname "$0")/start.command"\n');
63+
chmodSync('dist/start.command', 0o755);
64+
chmodSync('dist/start.sh', 0o755);
65+
66+
// Windows launcher.
67+
writeFileSync(
68+
'dist/start.bat',
69+
[
70+
'@echo off',
71+
'cd /d "%~dp0"',
72+
'set PORT=8000',
73+
'start "" "http://localhost:%PORT%/"',
74+
'py -m http.server %PORT% 2>nul || python -m http.server %PORT%',
75+
'',
76+
].join('\r\n'),
77+
);
78+
4179
// Build the archive from inside dist/ (so paths are relative), excluding any
4280
// pre-existing zip. -X drops extra macOS metadata for a clean cross-platform zip.
4381
// Never fail the build over the optional archive — the site still deploys.

vite.config.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { defineConfig } from 'vite';
22
import { nodePolyfills } from 'vite-plugin-node-polyfills';
33

4-
// Static SPA served from a custom domain (validator.spotlight-rules.com), so base '/'.
4+
// Static SPA. Use a RELATIVE base ('./') so the built assets resolve no matter
5+
// where the app is served from — the custom domain root (validator.apicommons.org)
6+
// AND an unzipped "Run Locally" copy served from any folder.
57
export default defineConfig({
6-
base: '/',
8+
base: './',
79
plugins: [
810
nodePolyfills({
911
// The Spotlight (Spectral) engine pulls in a few Node built-ins.

0 commit comments

Comments
 (0)