Unify loader assets across shells

This commit is contained in:
Shantur Rathore
2025-11-22 21:20:29 +00:00
parent 92420d9e02
commit e9f3c4ee52
11 changed files with 437 additions and 451 deletions

View File

@@ -1,45 +1,89 @@
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");
const fs = require("fs")
const path = require("path")
const { execSync } = require("child_process")
const root = path.resolve(__dirname, "..");
const workspaceRoot = path.resolve(root, "..", "..");
const serverRoot = path.resolve(root, "..", "server");
const dest = path.resolve(root, "src-tauri", "resources", "server");
const root = path.resolve(__dirname, "..")
const workspaceRoot = path.resolve(root, "..", "..")
const serverRoot = path.resolve(root, "..", "server")
const uiRoot = path.resolve(root, "..", "ui")
const uiDist = path.resolve(uiRoot, "src", "renderer", "dist")
const serverDest = path.resolve(root, "src-tauri", "resources", "server")
const uiLoadingDest = path.resolve(root, "src-tauri", "resources", "ui-loading")
const sources = ["dist", "public", "node_modules", "package.json"];
const sources = ["dist", "public", "node_modules", "package.json"]
function ensureServerBuild() {
const distPath = path.join(serverRoot, "dist");
const publicPath = path.join(serverRoot, "public");
const distPath = path.join(serverRoot, "dist")
const publicPath = path.join(serverRoot, "public")
if (fs.existsSync(distPath) && fs.existsSync(publicPath)) {
return;
return
}
console.log("[prebuild] server build missing; running workspace build...");
console.log("[prebuild] server build missing; running workspace build...")
execSync("npm --workspace @neuralnomads/codenomad run build", {
cwd: workspaceRoot,
stdio: "inherit",
});
})
if (!fs.existsSync(distPath) || !fs.existsSync(publicPath)) {
throw new Error("[prebuild] server artifacts still missing after build");
throw new Error("[prebuild] server artifacts still missing after build")
}
}
ensureServerBuild();
fs.rmSync(dest, { recursive: true, force: true });
fs.mkdirSync(dest, { recursive: true });
for (const name of sources) {
const from = path.join(serverRoot, name);
const to = path.join(dest, name);
if (!fs.existsSync(from)) {
console.warn(`[prebuild] skipped missing ${from}`);
continue;
function ensureUiBuild() {
const loadingHtml = path.join(uiDist, "loading.html")
if (fs.existsSync(loadingHtml)) {
return
}
console.log("[prebuild] ui build missing; running workspace build...")
execSync("npm --workspace @codenomad/ui run build", {
cwd: workspaceRoot,
stdio: "inherit",
})
if (!fs.existsSync(loadingHtml)) {
throw new Error("[prebuild] ui loading assets missing after build")
}
fs.cpSync(from, to, { recursive: true });
console.log(`[prebuild] copied ${from} -> ${to}`);
}
function copyServerArtifacts() {
fs.rmSync(serverDest, { recursive: true, force: true })
fs.mkdirSync(serverDest, { recursive: true })
for (const name of sources) {
const from = path.join(serverRoot, name)
const to = path.join(serverDest, name)
if (!fs.existsSync(from)) {
console.warn(`[prebuild] skipped missing ${from}`)
continue
}
fs.cpSync(from, to, { recursive: true })
console.log(`[prebuild] copied ${from} -> ${to}`)
}
}
function copyUiLoadingAssets() {
const loadingSource = path.join(uiDist, "loading.html")
const assetsSource = path.join(uiDist, "assets")
if (!fs.existsSync(loadingSource)) {
throw new Error("[prebuild] cannot find built loading.html")
}
fs.rmSync(uiLoadingDest, { recursive: true, force: true })
fs.mkdirSync(uiLoadingDest, { recursive: true })
fs.copyFileSync(loadingSource, path.join(uiLoadingDest, "loading.html"))
if (fs.existsSync(assetsSource)) {
fs.cpSync(assetsSource, path.join(uiLoadingDest, "assets"), { recursive: true })
}
console.log(`[prebuild] prepared UI loading assets from ${uiDist}`)
}
ensureServerBuild()
ensureUiBuild()
copyServerArtifacts()
copyUiLoadingAssets()

View File

@@ -4,9 +4,9 @@
"version": "0.1.0",
"identifier": "ai.opencode.client",
"build": {
"beforeDevCommand": "",
"beforeDevCommand": "npm run prebuild",
"beforeBuildCommand": "npm run bundle:server",
"frontendDist": "../src"
"frontendDist": "resources/ui-loading"
},
"app": {
"withGlobalTauri": true,
@@ -14,7 +14,7 @@
{
"label": "main",
"title": "CodeNomad",
"url": "index.html",
"url": "loading.html",
"width": 1400,
"height": 900,
"minWidth": 800,
@@ -34,9 +34,8 @@
"bundle": {
"active": true,
"resources": [
"../src/index.html",
"../src/icon.png",
"resources/server"
"resources/server",
"resources/ui-loading"
],
"icon": ["icon.icns", "icon.ico", "icon.png"],
"targets": ["app", "appimage", "deb", "rpm", "nsis"]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -1,197 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CodeNomad</title>
<style>
:root {
color-scheme: dark;
}
body {
margin: 0;
min-height: 100vh;
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background-color: #1a1a1a;
color: #cfd4dc;
display: flex;
align-items: center;
justify-content: center;
padding: 32px;
text-align: center;
}
button {
border: none;
background: none;
font: inherit;
color: inherit;
}
.wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
max-width: 520px;
}
.logo {
width: 180px;
height: auto;
filter: drop-shadow(0 15px 40px rgba(0, 0, 0, 0.35));
}
.title {
font-size: 2.7rem;
font-weight: 600;
margin: 0;
color: #f4f6fb;
}
.loading-card {
margin-top: 12px;
width: 100%;
max-width: 420px;
padding: 22px;
border-radius: 18px;
background: #151a23;
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.45);
}
.loading-row {
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
font-size: 0.95rem;
color: #cfd4dc;
}
.spinner {
width: 18px;
height: 18px;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.18);
border-top-color: #6ce3ff;
animation: spin 0.9s linear infinite;
}
.phrase-controls {
margin-top: 12px;
font-size: 0.9rem;
color: #8f96a9;
display: flex;
justify-content: center;
gap: 8px;
}
.phrase-controls button {
color: #8fb5ff;
cursor: pointer;
}
.error {
margin-top: 12px;
color: #ff9ea9;
font-size: 0.95rem;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<div class="wrapper" role="status" aria-live="polite">
<img src="./icon.png" alt="CodeNomad" class="logo" />
<div>
<h1 class="title">CodeNomad</h1>
</div>
<div class="loading-card">
<div class="loading-row">
<div class="spinner" aria-hidden="true"></div>
<span id="loading-phrase">Warming up the AI neurons…</span>
</div>
<div class="phrase-controls">
<button id="phrase-toggle" type="button">Show another</button>
</div>
<div class="error" id="error"></div>
</div>
</div>
<script>
const phrases = [
"Warming up the AI neurons…",
"Convincing the AI to stop daydreaming…",
"Polishing the AIs code goggles…",
"Asking the AI to stop reorganizing your files…",
"Feeding the AI additional coffee…",
"Teaching the AI not to delete node_modules (again)…",
"Telling the AI to act natural before you arrive…",
"Asking the AI to please stop rewriting history…",
"Letting the AI stretch before its coding sprint…",
"Persuading the AI to give you keyboard control…",
]
const phraseEl = document.getElementById("loading-phrase")
const button = document.getElementById("phrase-toggle")
const errorEl = document.getElementById("error")
function pickPhrase() {
const next = phrases[Math.floor(Math.random() * phrases.length)]
phraseEl.textContent = next
}
function setError(message) {
errorEl.textContent = message || ""
}
function navigateTo(url) {
if (!url) return
window.location.replace(url)
}
async function bootstrap() {
pickPhrase()
button?.addEventListener("click", pickPhrase)
if (!window.__TAURI__ || !window.__TAURI__.event || !window.__TAURI__.invoke) {
return
}
const { listen } = window.__TAURI__.event
const invoke = window.__TAURI__.invoke
listen("cli:ready", (event) => {
const payload = event?.payload || {}
if (payload.url) {
navigateTo(payload.url)
}
})
listen("cli:error", (event) => {
const payload = event?.payload || {}
if (payload.message) {
setError(payload.message)
}
})
listen("cli:status", (event) => {
const payload = event?.payload || {}
if (payload.state !== "ready") {
setError("")
}
})
try {
const status = await invoke("cli_get_status")
if (status?.state === "ready" && status.url) {
navigateTo(status.url)
}
if (status?.state === "error" && status.error) {
setError(status.error)
}
} catch (error) {
setError(String(error))
}
}
bootstrap()
</script>
</body>
</html>