Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
900da7a
Move springboard dev to single-port ModuleRunner
Feb 25, 2026
51fe57e
Fix dev plugin stream and module-graph typings
Mar 22, 2026
c6b22d8
Add e2e dev-route HMR test for registerServerModule
Mar 22, 2026
ac6c546
docs: clarify registerServerModule contract
Mar 25, 2026
76d916a
apps/vite-test: add start script and configurable port
Mar 25, 2026
b2a694f
Committed the two requested files as `apps/vite-test: add start scrip…
Mar 25, 2026
1543cb5
apps/vite-test: add debug log for module load
Mar 25, 2026
2335c8e
fix: use single-port dev server in vite plugin
Mar 25, 2026
b3b9628
fix: install vite dev middleware before spa fallback
Mar 25, 2026
6a8c1c4
refactor: remove unused vite plugin modules
Mar 25, 2026
c44e8be
Committed the deletions as `refactor: remove unused vite plugin modul…
Mar 25, 2026
98cb178
refactor: store generated vite files in node_modules
Mar 25, 2026
8fb45c6
edit scripts
Mar 25, 2026
1616cba
fix: serve dev web entry through vite transform
Mar 25, 2026
7b0b74c
build: support SongDrive Vite migration
Apr 23, 2026
b7ebb0d
fix: improve jamtools core vitest module resolution
May 6, 2026
d05024d
test: tighten macro input test bootstrap
May 6, 2026
83e635a
chore: initialize beads workflow for springboard
May 6, 2026
782b48d
feat: scaffold defineModule and entrypoint descriptors
May 8, 2026
5bd9eb0
feat: support async springboard entrypoint descriptors
May 10, 2026
6901287
feat: add expo-native react native host shell
May 10, 2026
75792e6
feat: add expo auth and push helpers
May 10, 2026
f2559ab
Update lockfile for Expo-native Springboard deps
May 12, 2026
7e08d2e
Limit Springboard CI to Springboard packages
May 15, 2026
d817a56
Add shared mobile WebView host E2E fixtures
Jun 8, 2026
a0a9a92
Make Springboard git refs easier to consume
Jun 8, 2026
78f4025
Include Springboard build configs in git package
Jun 8, 2026
1705b90
Declare Expo config plugins for mobile E2E
Jun 8, 2026
698d605
Fix Springboard mobile E2E Android CLI deps
Jun 8, 2026
b5eb1e8
Export Springboard React Native platform entry
Jun 8, 2026
edf4ae9
Fix mobile E2E Expo autolinking
Jun 8, 2026
7712122
Configure mobile E2E React Native autolinking
Jun 8, 2026
b707f4c
Fix Node CrossWS adapter interop
Jun 8, 2026
8f048ac
Build mobile E2E release APK fallback
Jun 8, 2026
364c4bd
Fix mobile E2E Metro bundling fallback
Jun 8, 2026
635ba86
Stabilize mobile E2E fixture Metro config
Jun 8, 2026
f696daa
Add descriptor-based Springboard test helper
Jun 8, 2026
8ef2dbb
Use direct mobile WebView host export
Jun 8, 2026
572ec0c
Stabilize mobile E2E readiness wait
Jun 8, 2026
c8c4e8a
Stabilize mobile E2E readiness check
Jun 8, 2026
6b770ae
Remove extra module dependency plumbing
Jun 13, 2026
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
34 changes: 29 additions & 5 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,31 @@
{
"enabledMcpjsonServers": [
"batchit"
],
"hooks": {
"PreCompact": [
{
"hooks": [
{
"command": "bd prime",
"type": "command"
}
],
"matcher": ""
}
],
"SessionStart": [
{
"hooks": [
{
"command": "bd prime",
"type": "command"
}
],
"matcher": ""
}
]
},
"permissions": {
"allow": [
"WebFetch(domain:github.com)",
Expand All @@ -23,8 +50,5 @@
"mcp__batchit__batch_execute"
],
"deny": []
},
"enabledMcpjsonServers": [
"batchit"
]
}
}
}
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- name: Install modules
run: pnpm i
- name: Build app
run: npx turbo run build
run: npx turbo run build --filter=springboard --filter=@springboard/vite-plugin --filter=create-springboard-app
types:
runs-on: ubuntu-latest
steps:
Expand All @@ -45,9 +45,9 @@ jobs:
- name: Install modules
run: pnpm i
- name: Build app
run: npx turbo run build
run: npx turbo run build --filter=springboard --filter=@springboard/vite-plugin --filter=create-springboard-app
- name: Check Types
run: npx turbo run check-types
run: npx turbo run check-types --filter=springboard --filter=@springboard/vite-plugin
lint:
runs-on: ubuntu-latest
steps:
Expand All @@ -64,9 +64,9 @@ jobs:
- name: Install modules
run: pnpm i
- name: Build app
run: npx turbo run build
run: npx turbo run build --filter=springboard --filter=@springboard/vite-plugin --filter=create-springboard-app
- name: Run eslint
run: npx turbo run lint
run: npx turbo run lint --filter=springboard
test:
runs-on: ubuntu-latest
steps:
Expand All @@ -83,6 +83,6 @@ jobs:
- name: Install modules
run: pnpm i
- name: Build app
run: npx turbo run build
run: npx turbo run build --filter=springboard --filter=@springboard/vite-plugin --filter=create-springboard-app
- name: Run tests
run: npx turbo run test
run: npx turbo run test --filter=springboard
177 changes: 177 additions & 0 deletions .github/workflows/mobile_android_e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
name: mobile_android_e2e

on:
push:
pull_request:
workflow_dispatch:

jobs:
android-mobile-e2e:
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
mode:
- local-assets
- remote-server
env:
SPRINGBOARD_MOBILE_E2E_MODE: ${{ matrix.mode }}
MOBILE_E2E_SITE_URL: http://10.0.2.2:1337
HOST_SITE_URL: http://127.0.0.1:1337
PORT: '1337'
steps:
- name: Checkout repo
uses: actions/checkout@v4

- name: Enable KVM group permissions
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.13.2

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17

- name: Install Android SDK
uses: android-actions/setup-android@v3

- name: Setup Expo and EAS
uses: expo/expo-github-action@v8
with:
eas-version: latest
packager: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build Springboard packages
run: pnpm --filter springboard build

- name: Build Android APK (EAS local when authenticated)
working-directory: apps/mobile-e2e
shell: bash
run: |
artifact_dir="$GITHUB_WORKSPACE/artifacts/mobile-e2e/${SPRINGBOARD_MOBILE_E2E_MODE}"
mkdir -p "$artifact_dir"

if [ -n "${EXPO_TOKEN:-}" ]; then
eas build \
--non-interactive \
--wait \
--platform android \
--profile "$SPRINGBOARD_MOBILE_E2E_MODE" \
--local \
--output "$artifact_dir/app.apk"
else
echo "::notice::EXPO_TOKEN is not configured, so EAS CLI cannot run in CI. Falling back to Expo prebuild + Gradle to keep the Android E2E fixture compiling and runnable on PRs."
skip_dependency_update="expo,react,react-native,react-native-webview,expo-asset,expo-constants,expo-file-system,expo-splash-screen,react-native-safe-area-context,@react-native-community/cli"
pnpm exec expo prebuild \
--platform android \
--clean \
--no-install \
--skip-dependency-update "$skip_dependency_update"
mkdir -p android/app/src/main/assets
pnpm exec react-native bundle \
--platform android \
--dev false \
--entry-file "$PWD/index.js" \
--config metro.config.js \
--bundle-output android/app/src/main/assets/index.android.bundle \
--assets-dest android/app/src/main/res
(cd android && ./gradlew assembleRelease --no-daemon --stacktrace)
release_apk="$(find android/app/build/outputs/apk/release -type f -name '*.apk' | head -n 1)"
if [ -z "$release_apk" ]; then
echo "No release APK found" >&2
find android/app/build/outputs/apk -maxdepth 4 -type f >&2 || true
exit 1
fi
cp "$release_apk" "$artifact_dir/app.apk"
fi
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
EXPO_GITHUB_ACTIONS_RUN: 'true'
EAS_LOCAL_BUILD_ARTIFACTS_DIR: ${{ github.workspace }}/artifacts/mobile-e2e/${{ matrix.mode }}
EXPO_MOBILE_E2E_MODE: ${{ matrix.mode }}
EXPO_PUBLIC_SITE_URL: ${{ env.MOBILE_E2E_SITE_URL }}
NODE_ENV: production

- name: Locate APK
shell: bash
run: |
apk_path="$(find "artifacts/mobile-e2e/${SPRINGBOARD_MOBILE_E2E_MODE}" -type f -name '*.apk' | head -n 1)"
if [ -z "$apk_path" ]; then
echo "No APK found" >&2
find "artifacts/mobile-e2e/${SPRINGBOARD_MOBILE_E2E_MODE}" -maxdepth 4 -type f >&2 || true
exit 1
fi
echo "MOBILE_APK_PATH=$GITHUB_WORKSPACE/$apk_path" >> "$GITHUB_ENV"

- name: Install mobile E2E dependencies
working-directory: tests/mobile-e2e
run: npm ci

- name: Install Appium Android driver
working-directory: tests/mobile-e2e
run: npx appium driver install uiautomator2

- name: Start remote fixture server
if: matrix.mode == 'remote-server'
shell: bash
run: |
mkdir -p "artifacts/mobile-e2e/${SPRINGBOARD_MOBILE_E2E_MODE}"
nohup node tests/mobile-e2e/remote-server.mjs > "artifacts/mobile-e2e/${SPRINGBOARD_MOBILE_E2E_MODE}/server.log" 2>&1 &
echo $! > "artifacts/mobile-e2e/${SPRINGBOARD_MOBILE_E2E_MODE}/server.pid"
for i in {1..30}; do
if curl -fsS "$HOST_SITE_URL" >/dev/null; then
echo "Remote fixture server is ready at $HOST_SITE_URL"
exit 0
fi
sleep 1
done
cat "artifacts/mobile-e2e/${SPRINGBOARD_MOBILE_E2E_MODE}/server.log" >&2 || true
exit 1

- name: Run Android WebView E2E test
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 35
target: google_apis
arch: x86_64
profile: pixel_6
disable-animations: true
script: |
adb devices
set +e
npm --prefix tests/mobile-e2e test
status=$?
adb logcat -d > "artifacts/mobile-e2e/${SPRINGBOARD_MOBILE_E2E_MODE}/logcat.txt" || true
exit $status

- name: Stop remote fixture server
if: always() && matrix.mode == 'remote-server'
run: |
if [ -f "artifacts/mobile-e2e/${SPRINGBOARD_MOBILE_E2E_MODE}/server.pid" ]; then
kill "$(cat "artifacts/mobile-e2e/${SPRINGBOARD_MOBILE_E2E_MODE}/server.pid")" || true
fi

- name: Upload mobile E2E artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: springboard-mobile-android-e2e-${{ matrix.mode }}
path: artifacts/mobile-e2e/${{ matrix.mode }}
8 changes: 4 additions & 4 deletions .github/workflows/publish_to_npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ jobs:
run: pnpm i

- name: Build
run: npx turbo run build
run: npx turbo run build --filter=springboard --filter=@springboard/vite-plugin --filter=create-springboard-app

- name: Check types
run: npx turbo run check-types
run: npx turbo run check-types --filter=springboard --filter=@springboard/vite-plugin

- name: Test
run: npx turbo run test
run: npx turbo run test --filter=springboard

- name: Run publish script with tag
run: ./scripts/run-all-folders.sh ${{ github.ref_name }} --mode npm
run: ./scripts/run-all-folders.sh ${{ github.ref_name }} --mode npm --packages springboard,create-springboard-app
88 changes: 88 additions & 0 deletions .springboard/node-dev-entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import process from 'node:process';

import crosswsNode from 'crossws/adapters/node';

import { initApp } from 'springboard/server/hono_app';
import { makeWebsocketServerCoreDependenciesWithSqlite } from 'springboard/platforms/node/services/ws_server_core_dependencies';
import { LocalJsonNodeKVStoreService } from 'springboard/platforms/node/services/node_kvstore_service';
import { CoreDependencies, Springboard } from 'springboard/core';
import {
springboard,
clearRegisteredModules,
clearRegisteredClassModules,
clearRegisteredSplashScreen,
} from 'springboard/core/engine/register';
import { resetServerRegistry } from 'springboard/server/register';

export type DevServerHandle = {
fetch: (request: Request) => Promise<Response>;
ws: ReturnType<typeof crosswsNode>;
dispose: () => Promise<void>;
};

springboard.reset();
clearRegisteredModules();
clearRegisteredClassModules();
clearRegisteredSplashScreen();
resetServerRegistry();

await import('../src/server-entry.ts');

export async function createDevServer(): Promise<DevServerHandle> {
const nodeKvDeps = await makeWebsocketServerCoreDependenciesWithSqlite();
const useWebSocketsForRpc = import.meta.env.VITE_USE_WEBSOCKETS_FOR_RPC === 'true';

let wsNode: ReturnType<typeof crosswsNode>;

const { app, serverAppDependencies, injectResources, createWebSocketHooks } = initApp({
broadcastMessage: (message) => {
return wsNode.publish('event', message);
},
remoteKV: nodeKvDeps.kvStoreFromKysely,
userAgentKV: new LocalJsonNodeKVStoreService('userAgent'),
enableStaticRoutes: false,
});

app.notFound((c) => {
c.header('x-springboard-fallback', '1');
return c.text('', 404);
});

wsNode = crosswsNode({
hooks: createWebSocketHooks(useWebSocketsForRpc),
});

const coreDeps: CoreDependencies = {
log: console.log,
showError: console.error,
storage: serverAppDependencies.storage,
isMaestro: () => true,
rpc: serverAppDependencies.rpc,
};

Object.assign(coreDeps, serverAppDependencies);

const engine = new Springboard(coreDeps);

injectResources({
engine,
serveStaticFile: async (c, _fileName, headers) => {
Object.entries(headers).forEach(([key, value]) => {
c.header(key, value);
});
c.status(404);
return c.text('Not found');
},
getEnvValue: (name) => process.env[name],
});

await engine.initialize();

return {
fetch: app.fetch,
ws: wsNode,
dispose: async () => {
wsNode.closeAll();
},
};
}
Loading
Loading