feat(deploy): run shared toolkit socket
Lint and Build Checks / Lint, Test, and Build (push) Failing after 364h2m28s
Lint and Build Checks / Lint, Test, and Build (push) Failing after 364h2m28s
This commit is contained in:
@@ -0,0 +1 @@
|
||||
VITE_TOOLKIT_SOCKET_URL=http://bone-kingdom.tabitha.tealthrone:5174
|
||||
@@ -21,18 +21,23 @@ steps:
|
||||
|
||||
validate:
|
||||
image: node:24-bookworm
|
||||
environment:
|
||||
VITE_TOOLKIT_SOCKET_URL: http://bone-kingdom.tabitha.tealthrone:5174
|
||||
commands:
|
||||
- npm ci
|
||||
- npm run lint
|
||||
- npm run test -- --runInBand
|
||||
- npm run web:build
|
||||
- npm run deploy:frontend-health
|
||||
|
||||
deploy:
|
||||
image: node:24-bookworm
|
||||
environment:
|
||||
DEPLOY_TARGET_URL: http://bone-kingdom.tabitha.tealthrone/
|
||||
DEPLOY_TOOLKIT_SOCKET_URL: http://bone-kingdom.tabitha.tealthrone:5174
|
||||
DEPLOY_HOST: 100.108.87.106
|
||||
DEPLOY_USER: Tabitha
|
||||
DEPLOY_REPO_PATH: /Users/Tabitha/.openclaw/workspace_father/blb-throne-of-the-bone-king
|
||||
DEPLOY_PATH: /Users/Tabitha/.openclaw/workspace_father/blb-throne-of-the-bone-king/dist/renderer
|
||||
DEPLOY_SSH_PRIVATE_KEY:
|
||||
from_secret: deploy_ssh_private_key
|
||||
@@ -46,8 +51,19 @@ steps:
|
||||
- ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
|
||||
- ssh "$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p '$DEPLOY_PATH'"
|
||||
- rsync -az --delete dist/renderer/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/"
|
||||
- rsync -az package.json package-lock.json "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_REPO_PATH/"
|
||||
- rsync -az --delete socket/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_REPO_PATH/socket/"
|
||||
- rsync -az --delete scripts/deploy/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_REPO_PATH/scripts/deploy/"
|
||||
- rsync -az --ignore-existing src/data/toolkit/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_REPO_PATH/src/data/toolkit/"
|
||||
- rsync -az --ignore-existing src/assets/images/uploads/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_REPO_PATH/src/assets/images/uploads/"
|
||||
- rsync -az --ignore-existing src/assets/music/uploads/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_REPO_PATH/src/assets/music/uploads/"
|
||||
- ssh "$DEPLOY_USER@$DEPLOY_HOST" "cd '$DEPLOY_REPO_PATH' && TOOLKIT_SOCKET_HOST=0.0.0.0 TOOLKIT_SOCKET_PORT=5174 TOOLKIT_SOCKET_CORS_ORIGIN='http://127.0.0.1:5173,http://localhost:5173,http://bone-kingdom.tabitha.tealthrone' sh scripts/deploy/restart-toolkit-socket.sh '$DEPLOY_REPO_PATH'"
|
||||
- curl -fsS "$DEPLOY_TOOLKIT_SOCKET_URL/health" >/tmp/bone-kingdom-socket-health.json
|
||||
- grep -q '"ok":true' /tmp/bone-kingdom-socket-health.json
|
||||
- curl -fsS "$DEPLOY_TARGET_URL" >/tmp/bone-kingdom-index.html
|
||||
- grep -q '<div id="root">' /tmp/bone-kingdom-index.html
|
||||
- curl -fsS "${DEPLOY_TARGET_URL%/}/health" >/tmp/bone-kingdom-frontend-health.json
|
||||
- grep -q '"ok":true' /tmp/bone-kingdom-frontend-health.json
|
||||
depends_on:
|
||||
- validate
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ __tests__/ # Jest tests
|
||||
- Change Log content is sourced from `src/data/toolkit/changelog.json`.
|
||||
- Credits Toolkit data is sourced from `src/data/toolkit/credits.json` and synced through the socket server.
|
||||
- Toolkit Images and Audio records can upload files through the socket into `src/assets/images/uploads/` and `src/assets/music/uploads/`.
|
||||
- Deployed Toolkit authoring uses `http://bone-kingdom.tabitha.tealthrone:5174` and intentionally runs from the shared repo workspace so edits update development JSON/assets while coding continues.
|
||||
- Toolkit seed datasets live under `src/data/toolkit/` and are exported through `src/data/index.ts`.
|
||||
- Shared data interfaces live under `src/data/types/`.
|
||||
|
||||
@@ -132,6 +133,8 @@ npm run web
|
||||
npm run dev:toolkit
|
||||
npm run dev:all
|
||||
npm run dev:clear
|
||||
npm run deploy:frontend-health
|
||||
npm run deploy:toolkit
|
||||
npm run electron:dev
|
||||
npm run web:build
|
||||
npm run electron:build
|
||||
|
||||
@@ -124,6 +124,27 @@ Socket health check:
|
||||
curl http://127.0.0.1:5174/health
|
||||
```
|
||||
|
||||
## Shared Deploy Toolkit Socket
|
||||
|
||||
The hosted browser app at `http://bone-kingdom.tabitha.tealthrone/` is built with `VITE_TOOLKIT_SOCKET_URL=http://bone-kingdom.tabitha.tealthrone:5174`. That socket is intentionally restarted from the shared repo workspace instead of from `dist/`, so Toolkit edits made by another person update the development JSON and upload folders directly:
|
||||
|
||||
- JSON content: `src/data/toolkit/`
|
||||
- Uploaded images: `src/assets/images/uploads/`
|
||||
- Uploaded audio: `src/assets/music/uploads/`
|
||||
|
||||
Woodpecker deploys the static renderer, restarts the shared Toolkit socket with `scripts/deploy/restart-toolkit-socket.sh`, and validates both:
|
||||
|
||||
```bash
|
||||
curl http://bone-kingdom.tabitha.tealthrone/health
|
||||
curl http://bone-kingdom.tabitha.tealthrone:5174/health
|
||||
```
|
||||
|
||||
For a manual shared-socket restart on the host:
|
||||
|
||||
```bash
|
||||
TOOLKIT_SOCKET_HOST=0.0.0.0 npm run deploy:toolkit
|
||||
```
|
||||
|
||||
## Assets
|
||||
|
||||
Image assets live in `src/assets/images/`. Fonts live in `src/assets/fonts/`. Music lives in `src/assets/music/`. Browser SCSS entrypoints live in `src/assets/scss/`.
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { getToolkitSocketUrl } from '../src/socket/toolkitSocketClient';
|
||||
|
||||
const originalToolkitSocketUrl = process.env.TOOLKIT_SOCKET_URL;
|
||||
const originalLocation = Object.getOwnPropertyDescriptor(
|
||||
globalThis,
|
||||
'location',
|
||||
);
|
||||
|
||||
function setTestLocation(hostname: string, protocol = 'http:') {
|
||||
Object.defineProperty(globalThis, 'location', {
|
||||
configurable: true,
|
||||
value: {
|
||||
hostname,
|
||||
protocol,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function restoreTestLocation() {
|
||||
if (originalLocation == null) {
|
||||
Reflect.deleteProperty(globalThis, 'location');
|
||||
return;
|
||||
}
|
||||
|
||||
Object.defineProperty(globalThis, 'location', originalLocation);
|
||||
}
|
||||
|
||||
describe('toolkit socket client configuration', () => {
|
||||
afterEach(() => {
|
||||
if (originalToolkitSocketUrl == null) {
|
||||
delete process.env.TOOLKIT_SOCKET_URL;
|
||||
} else {
|
||||
process.env.TOOLKIT_SOCKET_URL = originalToolkitSocketUrl;
|
||||
}
|
||||
|
||||
restoreTestLocation();
|
||||
});
|
||||
|
||||
it('uses explicit toolkit socket URL configuration first', () => {
|
||||
process.env.TOOLKIT_SOCKET_URL = 'http://socket.example.test:5174';
|
||||
setTestLocation('bone-kingdom.tabitha.tealthrone');
|
||||
|
||||
expect(getToolkitSocketUrl()).toBe('http://socket.example.test:5174');
|
||||
});
|
||||
|
||||
it('uses local socket defaults for localhost development', () => {
|
||||
delete process.env.TOOLKIT_SOCKET_URL;
|
||||
setTestLocation('localhost');
|
||||
|
||||
expect(getToolkitSocketUrl()).toBe('http://127.0.0.1:5174');
|
||||
});
|
||||
|
||||
it('derives the deployed socket URL from the current browser host', () => {
|
||||
delete process.env.TOOLKIT_SOCKET_URL;
|
||||
setTestLocation('bone-kingdom.tabitha.tealthrone');
|
||||
|
||||
expect(getToolkitSocketUrl()).toBe(
|
||||
'http://bone-kingdom.tabitha.tealthrone:5174',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -178,6 +178,7 @@ Canonical list lives in `manifest.llm.json` → `knownGaps`. Current highlights:
|
||||
- `wiki/` currently stores reference images and loose wiki/design assets
|
||||
- Toolkit JSON files now contain starter data based on the current wiki schemas; Credits, Changelog, and Legal live under `src/data/toolkit/`
|
||||
- Toolkit media uploads are written by the local socket server under `src/assets/images/uploads/` and `src/assets/music/uploads/`
|
||||
- Deployed Toolkit authoring uses a shared socket at `http://bone-kingdom.tabitha.tealthrone:5174` that runs from the repo workspace and writes to development JSON/assets
|
||||
|
||||
## Notes for LLMs
|
||||
- **Read `manifest.llm.json` for task routing** — use `taskRoutes` to pick the narrowest file set
|
||||
@@ -189,6 +190,7 @@ Canonical list lives in `manifest.llm.json` → `knownGaps`. Current highlights:
|
||||
- **Persistence is platform-split** — extend `persistStorage.ts` / `persistStorage.web.ts`, not stores directly
|
||||
- `src/data/AGENTS.md` contains extra rules for JSON content and Toolkit seed data
|
||||
- Data consumers should import from `src/data/index.ts`; keep `src/data/types/` aligned with JSON schema changes
|
||||
- Keep the deployed Toolkit socket tied to the shared repo workspace; do not move it to static `dist/` output or it will stop updating editable JSON/assets for parallel work
|
||||
- Before committing or pushing meaningful deliverables, update `src/data/toolkit/changelog.json`; Husky bumps build metadata but does not write release notes
|
||||
- iOS Pods and `node_modules/` are not committed; run `pod install` after pulling native dep changes
|
||||
- `dist/` build artifacts are gitignored
|
||||
|
||||
+17
-46
@@ -54,12 +54,7 @@
|
||||
"purpose": "Human-facing overview for setup, commands, architecture, toolkit behavior, and validation."
|
||||
}
|
||||
],
|
||||
"globalReadOrder": [
|
||||
"llm.txt",
|
||||
"manifest.llm.json",
|
||||
"AGENTS.md",
|
||||
"README.md"
|
||||
],
|
||||
"globalReadOrder": ["llm.txt", "manifest.llm.json", "AGENTS.md", "README.md"],
|
||||
"globalReadOrderSemantics": "Broad orientation order. Task routers should prefer taskRoutes read arrays to avoid over-reading.",
|
||||
"documentationFiles": [
|
||||
{
|
||||
@@ -122,6 +117,8 @@
|
||||
"entry": "socket/server.mjs",
|
||||
"devCommand": "npm run dev:toolkit",
|
||||
"healthUrl": "http://127.0.0.1:5174/health",
|
||||
"deployCommand": "npm run deploy:toolkit",
|
||||
"deployHealthUrl": "http://bone-kingdom.tabitha.tealthrone:5174/health",
|
||||
"primary": true
|
||||
},
|
||||
{
|
||||
@@ -167,6 +164,8 @@
|
||||
"dev:toolkit": "node socket/server.mjs",
|
||||
"dev:all": "concurrently -k -n app,toolkit -c blue,magenta \"npm run dev\" \"npm run dev:toolkit\"",
|
||||
"dev:clear": "node socket/clear.mjs",
|
||||
"deploy:frontend-health": "node scripts/deploy/write-frontend-health.mjs",
|
||||
"deploy:toolkit": "sh scripts/deploy/restart-toolkit-socket.sh",
|
||||
"electron:dev": "npm run electron:compile && cross-env NODE_ENV=development VITE_DEV_SERVER_URL=http://127.0.0.1:5173 concurrently -k \"vite --host 127.0.0.1\" \"wait-on http://127.0.0.1:5173 && electron .\"",
|
||||
"electron:build": "npm run web:build && npm run electron:compile",
|
||||
"electron:compile": "tsc -p tsconfig.electron.json",
|
||||
@@ -178,11 +177,7 @@
|
||||
},
|
||||
"architecture": {
|
||||
"rootComponent": "App.tsx",
|
||||
"componentTree": [
|
||||
"SafeAreaProvider",
|
||||
"GameViewport",
|
||||
"AppNavigator"
|
||||
],
|
||||
"componentTree": ["SafeAreaProvider", "GameViewport", "AppNavigator"],
|
||||
"navigation": {
|
||||
"type": "zustand-store with animated fade transitions",
|
||||
"routes": "src/navigation/routes.ts",
|
||||
@@ -237,13 +232,7 @@
|
||||
"androidDefault",
|
||||
"phoneLarge"
|
||||
],
|
||||
"volumeKeys": [
|
||||
"master",
|
||||
"music",
|
||||
"ambience",
|
||||
"speech",
|
||||
"sfx"
|
||||
]
|
||||
"volumeKeys": ["master", "music", "ambience", "speech", "sfx"]
|
||||
},
|
||||
"screens": [
|
||||
"src/screens/HomeScreen.tsx",
|
||||
@@ -313,11 +302,7 @@
|
||||
"hook": "src/hooks/useBackgroundMusic.ts",
|
||||
"webHook": "src/hooks/useBackgroundMusic.web.ts",
|
||||
"defaultTrack": "src/assets/music/throne-of-the-bone-king.mp3",
|
||||
"volumeControls": [
|
||||
"master",
|
||||
"music",
|
||||
"mute"
|
||||
]
|
||||
"volumeControls": ["master", "music", "mute"]
|
||||
},
|
||||
"toolkit": {
|
||||
"screen": "src/screens/ToolkitScreen.tsx",
|
||||
@@ -358,10 +343,7 @@
|
||||
"webEntry": "src/web/main.tsx",
|
||||
"electronMain": "electron/main.ts",
|
||||
"electronPreload": "electron/preload.ts",
|
||||
"buildOutputs": [
|
||||
"dist/renderer/",
|
||||
"dist/electron/"
|
||||
]
|
||||
"buildOutputs": ["dist/renderer/", "dist/electron/"]
|
||||
},
|
||||
"taskRoutes": {
|
||||
"ui-screen": {
|
||||
@@ -416,11 +398,7 @@
|
||||
"Electron main/preload compile to dist/electron/.",
|
||||
"Vite emits renderer assets to dist/renderer/."
|
||||
],
|
||||
"verify": [
|
||||
"npm run lint",
|
||||
"npm run web:build",
|
||||
"npm run electron:build"
|
||||
],
|
||||
"verify": ["npm run lint", "npm run web:build", "npm run electron:build"],
|
||||
"update": [
|
||||
"manifest.llm.json when targets, build outputs, or dev commands change"
|
||||
]
|
||||
@@ -465,9 +443,7 @@
|
||||
"Keep Changelog, Credits, and Legal JSON under src/data/toolkit/.",
|
||||
"Update src/data/toolkit/changelog.json before committing or pushing meaningful deliverables unless explicitly skipped."
|
||||
],
|
||||
"verify": [
|
||||
"npm run test -- --runInBand"
|
||||
]
|
||||
"verify": ["npm run test -- --runInBand"]
|
||||
},
|
||||
"realtime": {
|
||||
"description": "Socket.IO server/client wiring for Toolkit connection status and content sync.",
|
||||
@@ -516,9 +492,7 @@
|
||||
"<target source>",
|
||||
"<related __tests__ file>"
|
||||
],
|
||||
"verify": [
|
||||
"npm run test -- --runInBand"
|
||||
]
|
||||
"verify": ["npm run test -- --runInBand"]
|
||||
},
|
||||
"docs": {
|
||||
"description": "README, AGENTS, llm.txt, manifest, docs, or wiki maintenance.",
|
||||
@@ -627,14 +601,13 @@
|
||||
"No JSON Schema validator exists yet for manifest.llm.json.",
|
||||
"Toolkit content panels use socket-backed structured authoring forms for current JSON fields.",
|
||||
"Toolkit audio and image resources can upload local files through the Toolkit socket into src/assets uploads folders.",
|
||||
"The deployed Toolkit socket is expected to run from the shared repo workspace so deployed browser authoring updates development JSON and uploaded assets.",
|
||||
"Toolkit JSON schemas are first-pass wiki-derived interfaces and should remain flexible while authoring forms stabilize.",
|
||||
"Native android/ and ios/ folders are not currently present in this checkout.",
|
||||
"No database exists yet; editable content is intentionally JSON-backed until schemas stabilize."
|
||||
],
|
||||
"verificationBaseline": {
|
||||
"logicOrStoreChanges": [
|
||||
"npm run test -- --runInBand"
|
||||
],
|
||||
"logicOrStoreChanges": ["npm run test -- --runInBand"],
|
||||
"uiChanges": [
|
||||
"npm run lint",
|
||||
"npm run test -- --runInBand",
|
||||
@@ -648,12 +621,10 @@
|
||||
"socketChanges": [
|
||||
"npm run test -- --runInBand",
|
||||
"npm run dev:toolkit",
|
||||
"curl http://127.0.0.1:5174/health"
|
||||
],
|
||||
"mobileChanges": [
|
||||
"npm run lint",
|
||||
"npm run test -- --runInBand"
|
||||
"curl http://127.0.0.1:5174/health",
|
||||
"curl http://bone-kingdom.tabitha.tealthrone:5174/health"
|
||||
],
|
||||
"mobileChanges": ["npm run lint", "npm run test -- --runInBand"],
|
||||
"docsChanges": [
|
||||
"node -e \"JSON.parse(require('fs').readFileSync('manifest.llm.json','utf8')); console.log('manifest ok')\"",
|
||||
"npm run lint"
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
"dev:all": "concurrently -k -n app,toolkit -c blue,magenta \"npm run dev\" \"npm run dev:toolkit\"",
|
||||
"dev:clear": "node socket/clear.mjs",
|
||||
"dev:toolkit": "node socket/server.mjs",
|
||||
"deploy:frontend-health": "node scripts/deploy/write-frontend-health.mjs",
|
||||
"deploy:toolkit": "sh scripts/deploy/restart-toolkit-socket.sh",
|
||||
"electron:build": "npm run web:build && npm run electron:compile",
|
||||
"electron:compile": "tsc -p tsconfig.electron.json",
|
||||
"electron:dev": "npm run electron:compile && cross-env NODE_ENV=development VITE_DEV_SERVER_URL=http://127.0.0.1:5173 concurrently -k \"vite --host 127.0.0.1\" \"wait-on http://127.0.0.1:5173 && electron .\"",
|
||||
|
||||
Executable
+61
@@ -0,0 +1,61 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
repo_path="${1:-$(pwd)}"
|
||||
socket_host="${TOOLKIT_SOCKET_HOST:-0.0.0.0}"
|
||||
socket_port="${TOOLKIT_SOCKET_PORT:-5174}"
|
||||
socket_cors_origin="${TOOLKIT_SOCKET_CORS_ORIGIN:-http://127.0.0.1:5173,http://localhost:5173,http://bone-kingdom.tabitha.tealthrone}"
|
||||
pid_file="$repo_path/.toolkit-socket.pid"
|
||||
log_directory="$repo_path/logs"
|
||||
log_file="$log_directory/toolkit-socket.log"
|
||||
|
||||
cd "$repo_path"
|
||||
|
||||
if [ ! -f package.json ] || [ ! -f socket/server.mjs ]; then
|
||||
echo "[deploy:toolkit] $repo_path is not a Bone Kingdom repo checkout."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$log_directory" \
|
||||
"$repo_path/src/assets/images/uploads" \
|
||||
"$repo_path/src/assets/music/uploads"
|
||||
|
||||
if [ ! -d "$repo_path/node_modules/socket.io" ]; then
|
||||
echo "[deploy:toolkit] node_modules missing socket.io; installing workspace dependencies."
|
||||
npm install --no-audit --no-fund
|
||||
fi
|
||||
|
||||
if [ -f "$pid_file" ]; then
|
||||
existing_pid="$(cat "$pid_file")"
|
||||
|
||||
if [ -n "$existing_pid" ] && kill -0 "$existing_pid" 2>/dev/null; then
|
||||
echo "[deploy:toolkit] stopping existing socket process $existing_pid"
|
||||
kill -TERM "$existing_pid"
|
||||
|
||||
sleep 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "[deploy:toolkit] starting socket on $socket_host:$socket_port"
|
||||
|
||||
TOOLKIT_SOCKET_HOST="$socket_host" \
|
||||
TOOLKIT_SOCKET_PORT="$socket_port" \
|
||||
TOOLKIT_SOCKET_CORS_ORIGIN="$socket_cors_origin" \
|
||||
nohup node socket/server.mjs >"$log_file" 2>&1 &
|
||||
|
||||
socket_pid="$!"
|
||||
printf '%s\n' "$socket_pid" > "$pid_file"
|
||||
|
||||
sleep 1
|
||||
|
||||
if ! kill -0 "$socket_pid" 2>/dev/null; then
|
||||
echo "[deploy:toolkit] socket failed to start; recent log output follows."
|
||||
tail -n 80 "$log_file" || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsS "http://127.0.0.1:$socket_port/health" >/dev/null
|
||||
fi
|
||||
|
||||
echo "[deploy:toolkit] socket running as $socket_pid"
|
||||
@@ -0,0 +1,23 @@
|
||||
import { mkdir, writeFile } from 'node:fs/promises';
|
||||
import { createRequire } from 'node:module';
|
||||
import path from 'node:path';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const packageJson = require('../../package.json');
|
||||
|
||||
const outputPath = process.argv[2] ?? 'dist/renderer/health';
|
||||
const commit =
|
||||
process.env.CI_COMMIT_SHA ??
|
||||
process.env.VITE_LAST_SUCCESSFUL_COMMIT ??
|
||||
'unknown';
|
||||
|
||||
const payload = {
|
||||
ok: true,
|
||||
service: 'blb-frontend',
|
||||
version: packageJson.version,
|
||||
commit,
|
||||
generatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
await mkdir(path.dirname(outputPath), { recursive: true });
|
||||
await writeFile(outputPath, `${JSON.stringify(payload)}\n`, 'utf8');
|
||||
@@ -0,0 +1,41 @@
|
||||
# Toolkit Socket
|
||||
|
||||
The Toolkit socket is a local/shared Socket.IO authoring server for browser-only Toolkit workflows.
|
||||
|
||||
## Local Development
|
||||
|
||||
```bash
|
||||
npm run dev:toolkit
|
||||
curl http://127.0.0.1:5174/health
|
||||
```
|
||||
|
||||
Default local settings:
|
||||
|
||||
- host: `127.0.0.1`
|
||||
- port: `5174`
|
||||
- CORS origin: `http://127.0.0.1:5173`
|
||||
|
||||
## Shared Deployment
|
||||
|
||||
The deployed browser app at `http://bone-kingdom.tabitha.tealthrone/` connects to:
|
||||
|
||||
```text
|
||||
http://bone-kingdom.tabitha.tealthrone:5174
|
||||
```
|
||||
|
||||
Woodpecker restarts this socket from the shared repo workspace with:
|
||||
|
||||
```bash
|
||||
TOOLKIT_SOCKET_HOST=0.0.0.0 \
|
||||
TOOLKIT_SOCKET_PORT=5174 \
|
||||
TOOLKIT_SOCKET_CORS_ORIGIN='http://127.0.0.1:5173,http://localhost:5173,http://bone-kingdom.tabitha.tealthrone' \
|
||||
sh scripts/deploy/restart-toolkit-socket.sh
|
||||
```
|
||||
|
||||
Running from the repo workspace is intentional. Toolkit edits made from the deployed browser update the same source files used during development:
|
||||
|
||||
- `src/data/toolkit/*.json`
|
||||
- `src/assets/images/uploads/`
|
||||
- `src/assets/music/uploads/`
|
||||
|
||||
The deploy script syncs socket code and scripts, but uses `--ignore-existing` for Toolkit JSON and uploaded assets so live content edits are not overwritten by routine deploys.
|
||||
|
||||
+49
-21
@@ -3,6 +3,7 @@ import 'dotenv/config';
|
||||
import { watch } from 'node:fs';
|
||||
import { mkdir, writeFile } from 'node:fs/promises';
|
||||
import { createServer } from 'node:http';
|
||||
import { createRequire } from 'node:module';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
@@ -11,6 +12,8 @@ import { Server } from 'socket.io';
|
||||
import contentStore from './contentStore.cjs';
|
||||
|
||||
const repositoryRoot = fileURLToPath(new URL('..', import.meta.url));
|
||||
const require = createRequire(import.meta.url);
|
||||
const packageJson = require('../package.json');
|
||||
const defaultWebOrigin = 'http://127.0.0.1:5173';
|
||||
const host = process.env.TOOLKIT_SOCKET_HOST ?? '127.0.0.1';
|
||||
const port = Number.parseInt(process.env.TOOLKIT_SOCKET_PORT ?? '5174', 10);
|
||||
@@ -25,29 +28,46 @@ if (Number.isNaN(port)) {
|
||||
throw new Error('TOOLKIT_SOCKET_PORT must be a valid number.');
|
||||
}
|
||||
|
||||
const socketHealthStartedAt = Date.now();
|
||||
const socketHealthStartedAtIso = new Date(socketHealthStartedAt).toISOString();
|
||||
|
||||
function createSocketHealthPayload() {
|
||||
return {
|
||||
ok: true,
|
||||
service: 'blb-toolkit-socket',
|
||||
version: packageJson.version,
|
||||
startedAt: socketHealthStartedAtIso,
|
||||
timestamp: new Date().toISOString(),
|
||||
uptimeSeconds: Math.max(
|
||||
0,
|
||||
Math.round((Date.now() - socketHealthStartedAt) / 1000),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function writeJsonResponse(response, statusCode, body) {
|
||||
response.writeHead(statusCode, {
|
||||
'Access-Control-Allow-Headers': 'Content-Type',
|
||||
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Cache-Control': 'no-store',
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
response.end(`${JSON.stringify(body)}\n`);
|
||||
}
|
||||
|
||||
const httpServer = createServer((request, response) => {
|
||||
if (request.url === '/health') {
|
||||
response.writeHead(200, {
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
response.end(
|
||||
JSON.stringify({
|
||||
ok: true,
|
||||
service: 'blb-toolkit-socket',
|
||||
}),
|
||||
);
|
||||
const pathname = request.url?.split('?')[0];
|
||||
|
||||
if (pathname === '/health') {
|
||||
writeJsonResponse(response, 200, createSocketHealthPayload());
|
||||
return;
|
||||
}
|
||||
|
||||
response.writeHead(404, {
|
||||
'Content-Type': 'application/json',
|
||||
writeJsonResponse(response, 404, {
|
||||
ok: false,
|
||||
service: 'blb-toolkit-socket',
|
||||
});
|
||||
response.end(
|
||||
JSON.stringify({
|
||||
ok: false,
|
||||
service: 'blb-toolkit-socket',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
const io = new Server(httpServer, {
|
||||
@@ -73,7 +93,9 @@ const assetUploadConfig = {
|
||||
};
|
||||
|
||||
function getErrorMessage(error) {
|
||||
return error instanceof Error ? error.message : 'Unknown toolkit content error.';
|
||||
return error instanceof Error
|
||||
? error.message
|
||||
: 'Unknown toolkit content error.';
|
||||
}
|
||||
|
||||
function createContentError(resource, error) {
|
||||
@@ -237,7 +259,10 @@ io.on('connection', socket => {
|
||||
const resource = payload?.resource;
|
||||
|
||||
try {
|
||||
const changedContent = await createContentRecord(resource, payload?.record);
|
||||
const changedContent = await createContentRecord(
|
||||
resource,
|
||||
payload?.record,
|
||||
);
|
||||
|
||||
respond?.({
|
||||
ok: true,
|
||||
@@ -259,7 +284,10 @@ io.on('connection', socket => {
|
||||
const resource = payload?.resource;
|
||||
|
||||
try {
|
||||
const changedContent = await updateContentRecord(resource, payload?.record);
|
||||
const changedContent = await updateContentRecord(
|
||||
resource,
|
||||
payload?.record,
|
||||
);
|
||||
|
||||
respond?.({
|
||||
ok: true,
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
[
|
||||
{
|
||||
"id": "deployed-toolkit-socket-workspace",
|
||||
"version": "0.0.5-20260624-local",
|
||||
"createdAt": "2026-06-24",
|
||||
"title": "Deployed Toolkit Socket Workspace",
|
||||
"summary": "Prepared deployments to run a shared Toolkit socket against the development workspace data and assets.",
|
||||
"changes": [
|
||||
"Added deploy-aware Toolkit socket URL selection for built browser clients.",
|
||||
"Added Woodpecker deploy steps to restart the Toolkit socket and validate frontend and socket health endpoints.",
|
||||
"Added a shared workspace socket restart script that writes Toolkit JSON and uploaded media into the repo source folders."
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "toolkit-structured-media-authoring",
|
||||
"version": "0.0.2-20260624-local",
|
||||
|
||||
@@ -13,6 +13,17 @@ type NodeProcess = {
|
||||
env?: Partial<Record<string, string>>;
|
||||
};
|
||||
|
||||
type BrowserLocation = {
|
||||
hostname: string;
|
||||
protocol: string;
|
||||
};
|
||||
|
||||
type BrowserGlobal = typeof globalThis & {
|
||||
location?: BrowserLocation;
|
||||
};
|
||||
|
||||
declare const __TOOLKIT_SOCKET_URL__: string | undefined;
|
||||
|
||||
export type ToolkitContentResource = EditableToolkitResource;
|
||||
export type ToolkitAssetKind = 'audio' | 'image';
|
||||
|
||||
@@ -79,9 +90,36 @@ function getMaybeProcess() {
|
||||
return typeof process === 'undefined' ? undefined : (process as NodeProcess);
|
||||
}
|
||||
|
||||
function getConfiguredToolkitSocketUrl() {
|
||||
if (
|
||||
typeof __TOOLKIT_SOCKET_URL__ === 'string' &&
|
||||
__TOOLKIT_SOCKET_URL__.length > 0
|
||||
) {
|
||||
return __TOOLKIT_SOCKET_URL__;
|
||||
}
|
||||
|
||||
return getMaybeProcess()?.env?.TOOLKIT_SOCKET_URL;
|
||||
}
|
||||
|
||||
function getBrowserToolkitSocketUrl() {
|
||||
const location = (globalThis as BrowserGlobal).location;
|
||||
|
||||
if (location == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (location.hostname === '127.0.0.1' || location.hostname === 'localhost') {
|
||||
return defaultToolkitSocketUrl;
|
||||
}
|
||||
|
||||
return `${location.protocol}//${location.hostname}:5174`;
|
||||
}
|
||||
|
||||
export function getToolkitSocketUrl() {
|
||||
return (
|
||||
getMaybeProcess()?.env?.TOOLKIT_SOCKET_URL ?? defaultToolkitSocketUrl
|
||||
getConfiguredToolkitSocketUrl() ??
|
||||
getBrowserToolkitSocketUrl() ??
|
||||
defaultToolkitSocketUrl
|
||||
);
|
||||
}
|
||||
|
||||
@@ -103,7 +141,9 @@ export function getToolkitSocket() {
|
||||
return toolkitSocket;
|
||||
}
|
||||
|
||||
function isToolkitContentPayload(value: unknown): value is ToolkitContentPayload {
|
||||
function isToolkitContentPayload(
|
||||
value: unknown,
|
||||
): value is ToolkitContentPayload {
|
||||
if (value == null || typeof value !== 'object') {
|
||||
return false;
|
||||
}
|
||||
@@ -117,9 +157,7 @@ function isToolkitContentPayload(value: unknown): value is ToolkitContentPayload
|
||||
);
|
||||
}
|
||||
|
||||
function parseToolkitContentResponse(
|
||||
response: unknown,
|
||||
): ToolkitContentPayload {
|
||||
function parseToolkitContentResponse(response: unknown): ToolkitContentPayload {
|
||||
if (response == null || typeof response !== 'object') {
|
||||
throw new Error('Toolkit content response was empty.');
|
||||
}
|
||||
@@ -172,7 +210,10 @@ function parseToolkitAssetUploadResponse(
|
||||
throw new Error(uploadResponse.error ?? 'Toolkit asset upload failed.');
|
||||
}
|
||||
|
||||
if (uploadResponse.ok === true && isToolkitAssetUploadPayload(uploadResponse)) {
|
||||
if (
|
||||
uploadResponse.ok === true &&
|
||||
isToolkitAssetUploadPayload(uploadResponse)
|
||||
) {
|
||||
return uploadResponse;
|
||||
}
|
||||
|
||||
|
||||
+87
-22
@@ -1,7 +1,8 @@
|
||||
import path from 'node:path';
|
||||
import type { ServerResponse } from 'node:http';
|
||||
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { defineConfig } from 'vite';
|
||||
import { defineConfig, loadEnv, type PluginOption } from 'vite';
|
||||
|
||||
import packageJson from './package.json';
|
||||
|
||||
@@ -17,25 +18,89 @@ const platformExtensions = [
|
||||
'.json',
|
||||
];
|
||||
|
||||
export default defineConfig({
|
||||
define: {
|
||||
'import.meta.env.VITE_APP_VERSION': JSON.stringify(packageJson.version),
|
||||
},
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: [{ find: /^react-native$/, replacement: 'react-native-web' }],
|
||||
dedupe: ['react', 'react-dom', 'react-native-web'],
|
||||
extensions: platformExtensions,
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: ['react-native-safe-area-context'],
|
||||
},
|
||||
build: {
|
||||
outDir: path.resolve(__dirname, 'dist/renderer'),
|
||||
emptyOutDir: true,
|
||||
},
|
||||
server: {
|
||||
host: '127.0.0.1',
|
||||
port: 5173,
|
||||
},
|
||||
const frontendHealthStartedAt = Date.now();
|
||||
const frontendHealthStartedAtIso = new Date(
|
||||
frontendHealthStartedAt,
|
||||
).toISOString();
|
||||
|
||||
function createFrontendHealthPayload() {
|
||||
return {
|
||||
ok: true,
|
||||
service: 'blb-frontend',
|
||||
version: packageJson.version,
|
||||
startedAt: frontendHealthStartedAtIso,
|
||||
timestamp: new Date().toISOString(),
|
||||
uptimeSeconds: Math.max(
|
||||
0,
|
||||
Math.round((Date.now() - frontendHealthStartedAt) / 1000),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function writeJsonResponse(
|
||||
response: ServerResponse,
|
||||
statusCode: number,
|
||||
body: object,
|
||||
) {
|
||||
response.writeHead(statusCode, {
|
||||
'Cache-Control': 'no-store',
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
response.end(`${JSON.stringify(body)}\n`);
|
||||
}
|
||||
|
||||
function frontendHealthPlugin(): PluginOption {
|
||||
return {
|
||||
name: 'blb-frontend-health',
|
||||
configurePreviewServer(server) {
|
||||
server.middlewares.use((request, response, next) => {
|
||||
if (request.url?.split('?')[0] !== '/health') {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
writeJsonResponse(response, 200, createFrontendHealthPayload());
|
||||
});
|
||||
},
|
||||
configureServer(server) {
|
||||
server.middlewares.use((request, response, next) => {
|
||||
if (request.url?.split('?')[0] !== '/health') {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
writeJsonResponse(response, 200, createFrontendHealthPayload());
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, __dirname, '');
|
||||
const toolkitSocketUrl =
|
||||
env.VITE_TOOLKIT_SOCKET_URL ?? env.TOOLKIT_SOCKET_URL ?? '';
|
||||
|
||||
return {
|
||||
define: {
|
||||
__TOOLKIT_SOCKET_URL__: JSON.stringify(toolkitSocketUrl),
|
||||
'import.meta.env.VITE_APP_VERSION': JSON.stringify(packageJson.version),
|
||||
},
|
||||
plugins: [frontendHealthPlugin(), react()],
|
||||
resolve: {
|
||||
alias: [{ find: /^react-native$/, replacement: 'react-native-web' }],
|
||||
dedupe: ['react', 'react-dom', 'react-native-web'],
|
||||
extensions: platformExtensions,
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: ['react-native-safe-area-context'],
|
||||
},
|
||||
build: {
|
||||
outDir: path.resolve(__dirname, 'dist/renderer'),
|
||||
emptyOutDir: true,
|
||||
},
|
||||
server: {
|
||||
host: '127.0.0.1',
|
||||
port: 5173,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user