Skip to content

Commit 5f78b43

Browse files
committed
chore(build): add Vercel preview builds for framework test apps
1 parent 308aef5 commit 5f78b43

4 files changed

Lines changed: 374 additions & 1 deletion

File tree

core/scripts/vercel-build.sh

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
#!/bin/bash
2+
#
3+
# Vercel preview build script
4+
#
5+
# Builds core component tests (same as before) plus framework test apps
6+
# (Angular, React, Vue) so they're all accessible from a single preview URL.
7+
#
8+
# Core tests: /src/components/{name}/test/{scenario}
9+
# Angular test app: /angular/
10+
# React test app: /react/
11+
# Vue test app: /vue/
12+
#
13+
set -e
14+
15+
# Vercel places core/ at /vercel/path1 (bind mount). The full repo clone
16+
# lives at /vercel/path0. We can't rely on `..` to reach it, so we search.
17+
CORE_DIR=$(pwd)
18+
OUTPUT_DIR="${CORE_DIR}/../_vercel_output"
19+
20+
# Find the actual repo root (the directory containing packages/)
21+
REPO_ROOT=""
22+
for candidate in /vercel/path0 /vercel/path1 "${CORE_DIR}/.."; do
23+
if [ -d "${candidate}/packages" ]; then
24+
REPO_ROOT="${candidate}"
25+
break
26+
fi
27+
done
28+
29+
echo "=== Ionic Framework Preview Build ==="
30+
echo "Core dir: ${CORE_DIR}"
31+
echo "Repo root: ${REPO_ROOT:-NOT FOUND}"
32+
33+
rm -rf "${OUTPUT_DIR}"
34+
mkdir -p "${OUTPUT_DIR}"
35+
36+
# Step 1 - Build Core (dependencies already installed by Vercel installCommand)
37+
echo ""
38+
echo "--- Step 1: Building Core ---"
39+
npm run build
40+
41+
# Copy core files to output. The test HTML files use relative paths like
42+
# ../../../../../dist/ionic/ionic.esm.js so the directory structure must
43+
# be preserved exactly.
44+
echo "Copying core output..."
45+
cp -r "${CORE_DIR}/src" "${OUTPUT_DIR}/src"
46+
cp -r "${CORE_DIR}/dist" "${OUTPUT_DIR}/dist"
47+
cp -r "${CORE_DIR}/css" "${OUTPUT_DIR}/css"
48+
mkdir -p "${OUTPUT_DIR}/scripts"
49+
cp -r "${CORE_DIR}/scripts/testing" "${OUTPUT_DIR}/scripts/testing"
50+
51+
# Generate directory index pages so users can browse core test pages.
52+
# Creates an index.html in every directory under src/components/ that
53+
# doesn't already have one. Only includes child directories that eventually
54+
# lead to a test page (an index.html). Prunes snapshot dirs and dead ends.
55+
echo "Generating directory indexes for core tests..."
56+
generate_dir_index() {
57+
local dir="$1"
58+
local url_path="$2"
59+
# Skip if an index.html already exists (it's an actual test page)
60+
[ -f "${dir}/index.html" ] && return
61+
62+
local entries=""
63+
for child in "${dir}"/*/; do
64+
[ -d "${child}" ] || continue
65+
local name=$(basename "${child}")
66+
# Skip snapshot directories and hidden dirs
67+
case "${name}" in *-snapshots|.*) continue ;; esac
68+
# Only include if there's at least one index.html somewhere underneath
69+
find "${child}" -name "index.html" -print -quit | grep -q . || continue
70+
entries="${entries}<a href=\"${name}/\">${name}/</a>\n"
71+
done
72+
73+
[ -z "${entries}" ] && return
74+
75+
cat > "${dir}/index.html" << IDXEOF
76+
<!DOCTYPE html>
77+
<html lang="en">
78+
<head>
79+
<meta charset="UTF-8">
80+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
81+
<title>Index of ${url_path}</title>
82+
<style>
83+
body { font-family: monospace; padding: 1.5rem; background: #f5f5f5; }
84+
h1 { font-size: 1rem; margin-bottom: 1rem; color: #333; }
85+
a { display: block; padding: 4px 0; color: #1565c0; text-decoration: none; }
86+
a:hover { text-decoration: underline; }
87+
.up { margin-bottom: 0.5rem; color: #666; }
88+
</style>
89+
</head>
90+
<body>
91+
<h1>Index of ${url_path}</h1>
92+
<a class="up" href="../">../</a>
93+
$(echo -e "${entries}")
94+
</body>
95+
</html>
96+
IDXEOF
97+
}
98+
99+
# Walk all directories under src/ (bottom-up so parent indexes reflect pruned children)
100+
find "${OUTPUT_DIR}/src" -depth -type d | while IFS= read -r dir; do
101+
url_path="${dir#${OUTPUT_DIR}}"
102+
generate_dir_index "${dir}" "${url_path}/"
103+
done
104+
105+
# Vercel mounts core/ at a separate path (path1) from the repo clone (path0).
106+
# Framework packages reference core via relative paths (../../core/css etc.),
107+
# which resolve to path0/core/ -- not path1/ where we just built.
108+
# Symlink path0/core -> path1 so those references find the build outputs.
109+
if [ -n "${REPO_ROOT}" ] && [ "${CORE_DIR}" != "${REPO_ROOT}/core" ] && [ -d "${REPO_ROOT}/core" ]; then
110+
echo "Linking ${REPO_ROOT}/core -> ${CORE_DIR} (so framework builds find core outputs)"
111+
rm -rf "${REPO_ROOT}/core"
112+
ln -s "${CORE_DIR}" "${REPO_ROOT}/core"
113+
fi
114+
115+
# Check if the full repo is available
116+
if [ -z "${REPO_ROOT}" ]; then
117+
echo ""
118+
echo "WARNING: Could not find repo root (no directory with packages/ found)"
119+
echo "Only core tests will be deployed (framework test apps require the full repo)."
120+
121+
# Generate landing page and exit -- core tests are still useful
122+
cat > "${OUTPUT_DIR}/index.html" << 'LANDING_EOF'
123+
<!DOCTYPE html>
124+
<html lang="en"><head><meta charset="UTF-8"><title>Ionic Framework - Preview</title></head>
125+
<body><h1>Ionic Framework Preview</h1><p>Core tests only. Browse to /src/components/{name}/test/{scenario}/</p></body>
126+
</html>
127+
LANDING_EOF
128+
129+
echo "=== Preview build complete (core only) ==="
130+
exit 0
131+
fi
132+
133+
# Step 2 - Build Framework Packages (parallel)
134+
echo ""
135+
echo "--- Step 2: Building Framework Packages ---"
136+
137+
build_angular_pkgs() {
138+
(cd "${REPO_ROOT}/packages/angular" && npm install && npm run build) || return 1
139+
(cd "${REPO_ROOT}/packages/angular-server" && npm install && npm run build) || return 1
140+
}
141+
142+
build_react_pkgs() {
143+
(cd "${REPO_ROOT}/packages/react" && npm install && npm run build) || return 1
144+
(cd "${REPO_ROOT}/packages/react-router" && npm install && npm run build) || return 1
145+
}
146+
147+
build_vue_pkgs() {
148+
(cd "${REPO_ROOT}/packages/vue" && npm install && npm run build) || return 1
149+
(cd "${REPO_ROOT}/packages/vue-router" && npm install && npm run build) || return 1
150+
}
151+
152+
build_angular_pkgs > /tmp/vercel-angular-pkg.log 2>&1 &
153+
PID_ANG=$!
154+
build_react_pkgs > /tmp/vercel-react-pkg.log 2>&1 &
155+
PID_REACT=$!
156+
build_vue_pkgs > /tmp/vercel-vue-pkg.log 2>&1 &
157+
PID_VUE=$!
158+
159+
ANG_PKG_OK=true; REACT_PKG_OK=true; VUE_PKG_OK=true
160+
wait $PID_ANG || { echo "Angular packages failed:"; tail -30 /tmp/vercel-angular-pkg.log; ANG_PKG_OK=false; }
161+
wait $PID_REACT || { echo "React packages failed:"; tail -30 /tmp/vercel-react-pkg.log; REACT_PKG_OK=false; }
162+
wait $PID_VUE || { echo "Vue packages failed:"; tail -30 /tmp/vercel-vue-pkg.log; VUE_PKG_OK=false; }
163+
164+
if ! $ANG_PKG_OK || ! $REACT_PKG_OK || ! $VUE_PKG_OK; then
165+
echo "ERROR: Some framework package builds failed."
166+
echo "Core tests will still be deployed. Skipping failed framework test apps."
167+
else
168+
echo "All framework packages built."
169+
fi
170+
171+
# Step 3 - Build Framework Test Apps (parallel)
172+
echo ""
173+
echo "--- Step 3: Building Framework Test Apps ---"
174+
175+
# Find the best available app version for a given package
176+
pick_app() {
177+
local test_dir="$1"
178+
shift
179+
for v in "$@"; do
180+
if [ -d "${test_dir}/apps/${v}" ]; then
181+
echo "${v}"
182+
return 0
183+
fi
184+
done
185+
return 1
186+
}
187+
188+
build_angular_test() {
189+
local APP
190+
APP=$(pick_app "${REPO_ROOT}/packages/angular/test" ng20 ng19 ng18) || {
191+
echo "[angular] No test app found, skipping."
192+
return 0
193+
}
194+
echo "[angular] Building ${APP}..."
195+
196+
cd "${REPO_ROOT}/packages/angular/test"
197+
./build.sh "${APP}"
198+
cd "build/${APP}"
199+
npm install
200+
npm run sync
201+
# --base-href sets <base href="/angular/"> so Angular Router works under the sub-path
202+
npm run build -- --base-href /angular/
203+
204+
# Output path assumes the 'browser' builder. If migrated to 'application' builder, update this.
205+
if [ ! -d "dist/test-app/browser" ]; then
206+
echo "[angular] ERROR: Expected output at dist/test-app/browser/ not found."
207+
return 1
208+
fi
209+
mkdir -p "${OUTPUT_DIR}/angular"
210+
cp -r dist/test-app/browser/* "${OUTPUT_DIR}/angular/"
211+
echo "[angular] Done."
212+
}
213+
214+
build_react_test() {
215+
local APP
216+
APP=$(pick_app "${REPO_ROOT}/packages/react/test" react19 react18) || {
217+
echo "[react] No test app found, skipping."
218+
return 0
219+
}
220+
echo "[react] Building ${APP}..."
221+
222+
cd "${REPO_ROOT}/packages/react/test"
223+
./build.sh "${APP}"
224+
cd "build/${APP}"
225+
npm install
226+
npm run sync
227+
# --base sets Vite's base URL; import.meta.env.BASE_URL is read by IonReactRouter basename
228+
npx vite build --base /react/
229+
230+
mkdir -p "${OUTPUT_DIR}/react"
231+
cp -r dist/* "${OUTPUT_DIR}/react/"
232+
echo "[react] Done."
233+
}
234+
235+
build_vue_test() {
236+
local APP
237+
APP=$(pick_app "${REPO_ROOT}/packages/vue/test" vue3) || {
238+
echo "[vue] No test app found, skipping."
239+
return 0
240+
}
241+
echo "[vue] Building ${APP}..."
242+
243+
cd "${REPO_ROOT}/packages/vue/test"
244+
./build.sh "${APP}"
245+
cd "build/${APP}"
246+
npm install
247+
npm run sync
248+
# Vue Router already reads import.meta.env.BASE_URL which Vite sets from --base
249+
npx vite build --base /vue/
250+
251+
mkdir -p "${OUTPUT_DIR}/vue"
252+
cp -r dist/* "${OUTPUT_DIR}/vue/"
253+
echo "[vue] Done."
254+
}
255+
256+
# TODO: Add build_react_router_test() when reactrouter6-* apps are added to
257+
# packages/react-router/test/apps/
258+
259+
TEST_FAILED=""
260+
261+
if $ANG_PKG_OK; then
262+
build_angular_test > /tmp/vercel-angular-test.log 2>&1 &
263+
PID_ANG_TEST=$!
264+
fi
265+
if $REACT_PKG_OK; then
266+
build_react_test > /tmp/vercel-react-test.log 2>&1 &
267+
PID_REACT_TEST=$!
268+
fi
269+
if $VUE_PKG_OK; then
270+
build_vue_test > /tmp/vercel-vue-test.log 2>&1 &
271+
PID_VUE_TEST=$!
272+
fi
273+
274+
if $ANG_PKG_OK; then
275+
wait $PID_ANG_TEST || { echo "Angular test app failed:"; tail -30 /tmp/vercel-angular-test.log; TEST_FAILED="${TEST_FAILED} angular"; }
276+
fi
277+
if $REACT_PKG_OK; then
278+
wait $PID_REACT_TEST || { echo "React test app failed:"; tail -30 /tmp/vercel-react-test.log; TEST_FAILED="${TEST_FAILED} react"; }
279+
fi
280+
if $VUE_PKG_OK; then
281+
wait $PID_VUE_TEST || { echo "Vue test app failed:"; tail -30 /tmp/vercel-vue-test.log; TEST_FAILED="${TEST_FAILED} vue"; }
282+
fi
283+
284+
if [ -n "${TEST_FAILED}" ]; then
285+
echo ""
286+
echo "WARNING: Some test app builds failed:${TEST_FAILED}"
287+
echo "Core tests and successful framework apps will still be deployed."
288+
fi
289+
290+
# Step 4 - Landing Page
291+
echo ""
292+
echo "--- Step 4: Generating landing page ---"
293+
294+
cat > "${OUTPUT_DIR}/index.html" << 'LANDING_EOF'
295+
<!DOCTYPE html>
296+
<html lang="en">
297+
<head>
298+
<meta charset="UTF-8">
299+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
300+
<title>Ionic Framework - Preview</title>
301+
<style>
302+
* { margin: 0; padding: 0; box-sizing: border-box; }
303+
body {
304+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
305+
background: #f5f5f5; padding: 2rem; color: #333;
306+
}
307+
.container { max-width: 680px; margin: 0 auto; }
308+
h1 { font-size: 1.4rem; margin-bottom: 0.25rem; }
309+
.subtitle { color: #666; margin-bottom: 1.5rem; font-size: 0.875rem; }
310+
.cards { display: grid; gap: 0.75rem; }
311+
a.card {
312+
background: #fff; border-radius: 8px; padding: 1rem 1.25rem;
313+
text-decoration: none; color: inherit;
314+
border: 1px solid #e0e0e0; transition: border-color 0.15s;
315+
}
316+
a.card:hover { border-color: #4f8ef7; }
317+
.card h2 { font-size: 1rem; margin-bottom: 0.2rem; display: flex; align-items: center; gap: 0.5rem; }
318+
.card p { color: #666; font-size: 0.8rem; }
319+
hr { border: none; border-top: 1px solid #e0e0e0; margin: 1rem 0; }
320+
.tip { font-size: 0.75rem; color: #999; margin-top: 1rem; }
321+
</style>
322+
</head>
323+
<body>
324+
<div class="container">
325+
<h1>Ionic Framework Preview</h1>
326+
<p class="subtitle">Component test apps for manual validation</p>
327+
<div class="cards">
328+
<a class="card" href="/src/components/">
329+
<h2>Core Components</h2>
330+
<p>Browse to /src/components/{name}/test/{scenario}/</p>
331+
</a>
332+
<hr>
333+
<a class="card" href="/angular/">
334+
<h2>Angular</h2>
335+
<p>@ionic/angular standalone + lazy-loaded component tests</p>
336+
</a>
337+
<a class="card" href="/react/">
338+
<h2>React</h2>
339+
<p>@ionic/react overlays, hooks, tabs, form controls</p>
340+
</a>
341+
<a class="card" href="/vue/">
342+
<h2>Vue</h2>
343+
<p>@ionic/vue overlays, router, tabs, lifecycle</p>
344+
</a>
345+
</div>
346+
</div>
347+
</body>
348+
</html>
349+
LANDING_EOF
350+
351+
echo ""
352+
echo "=== Preview build complete ==="
353+
ls -la "${OUTPUT_DIR}"

core/vercel.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"$schema": "https://openapi.vercel.sh/vercel.json",
3+
"framework": null,
4+
"installCommand": "npm install",
5+
"buildCommand": "bash scripts/vercel-build.sh",
6+
"outputDirectory": "../_vercel_output",
7+
"rewrites": [
8+
{ "source": "/angular/:path*", "destination": "/angular/index.html" },
9+
{ "source": "/react/:path*", "destination": "/react/index.html" },
10+
{ "source": "/vue/:path*", "destination": "/vue/index.html" }
11+
]
12+
}

packages/react/test/base/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ setupIonicReact();
4444

4545
const App: React.FC = () => (
4646
<IonApp>
47-
<IonReactRouter>
47+
<IonReactRouter basename={import.meta.env?.BASE_URL?.replace(/\/$/, '') || undefined}>
4848
<IonRouterOutlet>
4949
<Route exact path="/" component={Main} />
5050
<Route path="/overlay-hooks" component={OverlayHooks} />
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
11
/// <reference types="react-scripts" />
2+
3+
interface ImportMetaEnv {
4+
readonly BASE_URL?: string;
5+
}
6+
7+
interface ImportMeta {
8+
readonly env?: ImportMetaEnv;
9+
}

0 commit comments

Comments
 (0)