Skip to content

Commit b1e150f

Browse files
committed
Add build-windows.sh script for Windows platform setup and enhance pyright-bridge.ts for path normalization
- Introduced build-windows.sh to automate the build process for Windows, including bundling TypeScript, downloading Node.js, and creating a zip archive. - Updated pyright-bridge.ts to normalize paths for BOT_ROOT and JESSE_ROOT, ensuring compatibility across platforms. - Adjusted pyrightconfig.json to maintain consistent path references for improved project structure.
1 parent b7a877b commit b1e150f

File tree

3 files changed

+300
-14
lines changed

3 files changed

+300
-14
lines changed

build-windows.sh

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
#!/bin/bash
2+
3+
# Build script for Windows platform using Unix shell (WSL/Git Bash)
4+
# Creates win32-x64.zip with bundled Node.js runtime
5+
6+
set -e
7+
8+
PLATFORM="win32-x64"
9+
NODE_VERSION="v20.11.0"
10+
NODE_ARCH="win-x64" # Node.js uses "win-x64" in download URLs
11+
12+
echo "╔════════════════════════════════════════════════════════╗"
13+
echo "║ Building Pyright LSP Bridge for Windows ║"
14+
echo "╚════════════════════════════════════════════════════════╝"
15+
echo ""
16+
17+
# Clean previous builds
18+
echo "🧹 Cleaning previous builds..."
19+
rm -rf output
20+
mkdir -p output/${PLATFORM}
21+
22+
# Step 1: Bundle TypeScript into one JavaScript file with esbuild
23+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
24+
echo "📦 Bundling TypeScript with esbuild..."
25+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
26+
27+
npx esbuild index.ts \
28+
--bundle \
29+
--platform=node \
30+
--target=node18 \
31+
--format=esm \
32+
--outfile=output/${PLATFORM}/bundle.js \
33+
--external:ws \
34+
--external:vscode-ws-jsonrpc \
35+
--external:vscode-jsonrpc \
36+
--external:dotenv
37+
38+
if [ ! -f "output/${PLATFORM}/bundle.js" ]; then
39+
echo "❌ esbuild bundling failed!"
40+
exit 1
41+
fi
42+
echo "✓ Bundled: output/${PLATFORM}/bundle.js"
43+
echo ""
44+
45+
# Step 2: Download and extract Node.js runtime for Windows
46+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
47+
echo "📥 Downloading Node.js ${NODE_VERSION} for ${NODE_ARCH}..."
48+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
49+
50+
NODE_PKG="node-${NODE_VERSION}-${NODE_ARCH}"
51+
NODE_URL="https://nodejs.org/dist/${NODE_VERSION}/${NODE_PKG}.zip"
52+
NODE_FILE="/tmp/${NODE_PKG}.zip"
53+
54+
# Check if cached file exists and is valid
55+
if [ -f "${NODE_FILE}" ]; then
56+
echo "Found cached Node.js, verifying..."
57+
if unzip -t "${NODE_FILE}" >/dev/null 2>&1; then
58+
echo "✓ Using cached Node.js"
59+
else
60+
echo "⚠️ Cached file is corrupted, re-downloading..."
61+
rm -f "${NODE_FILE}"
62+
fi
63+
fi
64+
65+
# Download if not exists or was corrupted
66+
if [ ! -f "${NODE_FILE}" ]; then
67+
echo "Downloading from ${NODE_URL}..."
68+
69+
# Try curl first
70+
if command -v curl &> /dev/null; then
71+
curl -f -L --progress-bar "${NODE_URL}" -o "${NODE_FILE}" || {
72+
echo "❌ curl download failed"
73+
rm -f "${NODE_FILE}"
74+
75+
# Try wget as fallback
76+
if command -v wget &> /dev/null; then
77+
echo "Trying wget..."
78+
wget -q --show-progress "${NODE_URL}" -O "${NODE_FILE}" || {
79+
echo "❌ wget download also failed"
80+
rm -f "${NODE_FILE}"
81+
echo "Please download manually from: ${NODE_URL}"
82+
exit 1
83+
}
84+
else
85+
echo "Please download manually from: ${NODE_URL}"
86+
echo "Save it to: ${NODE_FILE}"
87+
exit 1
88+
fi
89+
}
90+
elif command -v wget &> /dev/null; then
91+
wget -q --show-progress "${NODE_URL}" -O "${NODE_FILE}" || {
92+
echo "❌ Failed to download Node.js"
93+
rm -f "${NODE_FILE}"
94+
echo "Please download manually from: ${NODE_URL}"
95+
exit 1
96+
}
97+
else
98+
echo "❌ Neither curl nor wget found"
99+
echo "Please install curl or wget, or download manually from: ${NODE_URL}"
100+
exit 1
101+
fi
102+
103+
# Check file size (should be around 28-30 MB)
104+
FILE_SIZE=$(stat -f%z "${NODE_FILE}" 2>/dev/null || stat -c%s "${NODE_FILE}" 2>/dev/null || echo "0")
105+
if [ "$FILE_SIZE" -lt 1000000 ]; then
106+
echo "❌ Downloaded file is too small (${FILE_SIZE} bytes)"
107+
echo "Expected around 28-30 MB"
108+
echo "The download might have failed or returned an error page"
109+
echo ""
110+
echo "Please try:"
111+
echo "1. Check your internet connection"
112+
echo "2. Download manually: ${NODE_URL}"
113+
echo "3. Save to: ${NODE_FILE}"
114+
rm -f "${NODE_FILE}"
115+
exit 1
116+
fi
117+
118+
# Verify the downloaded file
119+
echo "Verifying download..."
120+
if ! unzip -t "${NODE_FILE}" >/dev/null 2>&1; then
121+
echo "❌ Downloaded file is corrupted or not a valid zip"
122+
rm -f "${NODE_FILE}"
123+
exit 1
124+
fi
125+
echo "✓ Download verified"
126+
fi
127+
128+
echo "📂 Extracting Node.js..."
129+
unzip -q "${NODE_FILE}" -d /tmp/ || {
130+
echo "❌ Failed to extract Node.js"
131+
echo "Removing corrupted file, please run again"
132+
rm -f "${NODE_FILE}"
133+
exit 1
134+
}
135+
136+
mv "/tmp/${NODE_PKG}" "output/${PLATFORM}/node"
137+
echo "✓ Node.js extracted to output/${PLATFORM}/node"
138+
139+
# Step 3: Strip unnecessary files from Node.js to reduce size
140+
echo "🧹 Stripping unnecessary files from Node.js..."
141+
cd "output/${PLATFORM}/node"
142+
143+
# Remove npm, npx, corepack (we don't need package managers at runtime)
144+
rm -rf node_modules/npm 2>/dev/null || true
145+
rm -rf node_modules/corepack 2>/dev/null || true
146+
rm -f npm npm.cmd npx npx.cmd corepack corepack.cmd 2>/dev/null || true
147+
148+
# Remove docs and other non-essential files
149+
rm -f *.md LICENSE 2>/dev/null || true
150+
151+
cd ../../..
152+
echo "✓ Stripped unnecessary files from Node.js"
153+
echo ""
154+
155+
# Step 4: Install production dependencies
156+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
157+
echo "📥 Installing production node_modules..."
158+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
159+
160+
cp package.json output/${PLATFORM}/
161+
cd output/${PLATFORM}
162+
npm install --production --no-optional
163+
rm package-lock.json
164+
echo '{"type":"module"}' > package.json
165+
echo "✓ Installed production dependencies"
166+
echo ""
167+
168+
# Step 5: Prune unnecessary files from node_modules
169+
echo "🧹 Pruning unnecessary files from node_modules..."
170+
find node_modules -type d -name "test" -exec rm -rf {} + 2>/dev/null || true
171+
find node_modules -type d -name "tests" -exec rm -rf {} + 2>/dev/null || true
172+
find node_modules -type d -name "docs" -exec rm -rf {} + 2>/dev/null || true
173+
find node_modules -type d -name "examples" -exec rm -rf {} + 2>/dev/null || true
174+
find node_modules -type d -name "example" -exec rm -rf {} + 2>/dev/null || true
175+
find node_modules -type d -name ".github" -exec rm -rf {} + 2>/dev/null || true
176+
find node_modules -type d -name "coverage" -exec rm -rf {} + 2>/dev/null || true
177+
find node_modules -type d -name "benchmark" -exec rm -rf {} + 2>/dev/null || true
178+
find node_modules -type f -name "*.md" -delete 2>/dev/null || true
179+
find node_modules -type f -name "*.ts" ! -name "*.d.ts" -delete 2>/dev/null || true
180+
find node_modules -type f -name "*.map" -delete 2>/dev/null || true
181+
find node_modules -type f -name "LICENSE*" -delete 2>/dev/null || true
182+
find node_modules -type f -name "CHANGELOG*" -delete 2>/dev/null || true
183+
find node_modules -type f -name ".npmignore" -delete 2>/dev/null || true
184+
find node_modules -type f -name ".eslintrc*" -delete 2>/dev/null || true
185+
find node_modules -type f -name ".prettierrc*" -delete 2>/dev/null || true
186+
find node_modules -type f -name "tsconfig.json" -delete 2>/dev/null || true
187+
echo "✓ Pruned unnecessary files"
188+
189+
cd ../..
190+
echo ""
191+
192+
# Step 6: Copy config template
193+
echo "📋 Copying pyrightconfig.json..."
194+
cp pyrightconfig.json output/${PLATFORM}/
195+
echo "✓ Copied config template"
196+
echo ""
197+
198+
# Step 7: Create Windows start script (.bat)
199+
echo "📝 Creating start.bat for Windows..."
200+
cat > "output/${PLATFORM}/start.bat" << 'EOF'
201+
@echo off
202+
REM Pyright LSP WebSocket Bridge
203+
REM Usage: start.bat --port <PORT> --bot-root <BOT_ROOT> --jesse-root <JESSE_ROOT>
204+
205+
set DIR=%~dp0
206+
"%DIR%node\node.exe" "%DIR%bundle.js" %*
207+
EOF
208+
echo "✓ Created start.bat"
209+
echo ""
210+
211+
# Step 8: Create compressed zip archive
212+
echo "📦 Creating zip archive..."
213+
cd output
214+
215+
# Get sizes before compression
216+
DIR_SIZE=$(du -sh "${PLATFORM}" | cut -f1)
217+
218+
# Create zip using standard zip command
219+
if command -v zip &> /dev/null; then
220+
zip -rq "${PLATFORM}.zip" "${PLATFORM}/"
221+
echo "✓ Created ${PLATFORM}.zip"
222+
ARCHIVE_SIZE=$(du -sh "${PLATFORM}.zip" | cut -f1)
223+
else
224+
# Fallback to tar if zip is not available
225+
tar -czf "${PLATFORM}.tar.gz" "${PLATFORM}/"
226+
echo "✓ Created ${PLATFORM}.tar.gz (zip not available)"
227+
ARCHIVE_SIZE=$(du -sh "${PLATFORM}.tar.gz" | cut -f1)
228+
fi
229+
230+
# Remove extracted folder
231+
rm -rf "${PLATFORM}"
232+
echo "✓ Removed extracted folder"
233+
cd ..
234+
echo ""
235+
236+
# Show summary
237+
echo "╔════════════════════════════════════════════════════════╗"
238+
echo "║ ✅ BUILD COMPLETE ║"
239+
echo "╚════════════════════════════════════════════════════════╝"
240+
echo ""
241+
242+
if [ -f "output/${PLATFORM}.zip" ]; then
243+
echo "📦 Compressed archive: output/${PLATFORM}.zip"
244+
elif [ -f "output/${PLATFORM}.tar.gz" ]; then
245+
echo "📦 Compressed archive: output/${PLATFORM}.tar.gz"
246+
fi
247+
248+
echo " Original size: ${DIR_SIZE}"
249+
echo " Compressed: ${ARCHIVE_SIZE}"
250+
echo ""
251+
echo "📁 Archive contains:"
252+
echo " - bundle.js (all your bridge code bundled)"
253+
echo " - node/ (Node.js ${NODE_VERSION} runtime for Windows, optimized)"
254+
echo " - node_modules/ (Pyright + pruned dependencies)"
255+
echo " - pyrightconfig.json (config template)"
256+
echo " - start.bat (Windows startup script)"
257+
echo ""
258+
echo "🚀 To deploy on Windows:"
259+
echo " 1. Extract the zip file"
260+
echo " 2. Open Command Prompt or PowerShell"
261+
echo " 3. Run: cd ${PLATFORM} && start.bat --port 9011 --bot-root C:\\path\\to\\bot --jesse-root C:\\path\\to\\jesse"
262+
echo ""
263+

pyright-bridge.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { toSocket, WebSocketMessageReader, WebSocketMessageWriter } from 'vscode
44
import { StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node.js'
55
import { readFileSync, writeFileSync, existsSync } from 'fs'
66
import path, { dirname, join } from 'path'
7-
import { fileURLToPath } from 'url'
7+
import { fileURLToPath, pathToFileURL } from 'url'
88

99
import 'dotenv/config'
1010

@@ -50,13 +50,20 @@ function deployPyrightConfig() {
5050
return
5151
}
5252

53-
// Read template and replace variables
54-
let config = readFileSync(templatePath, 'utf-8')
55-
config = config.replace(/\$\{BOT_ROOT\}/g, BOT_ROOT)
56-
config = config.replace(/\$\{JESSE_ROOT\}/g, JESSE_ROOT || '')
53+
// Normalize paths to use forward slashes (works on all platforms)
54+
const normalizedBotRoot = BOT_ROOT.replace(/\\/g, '/')
55+
const normalizedJesseRoot = (JESSE_ROOT || '').replace(/\\/g, '/')
5756

58-
// Write to workspace
59-
writeFileSync(targetPath, config)
57+
// Read template and replace variables with normalized paths
58+
let configContent = readFileSync(templatePath, 'utf-8')
59+
configContent = configContent.replace(/\$\{BOT_ROOT\}/g, normalizedBotRoot)
60+
configContent = configContent.replace(/\$\{JESSE_ROOT\}/g, normalizedJesseRoot)
61+
62+
// Parse and re-serialize to ensure valid, formatted JSON
63+
const config = JSON.parse(configContent)
64+
65+
// Write normalized, valid JSON to workspace
66+
writeFileSync(targetPath, JSON.stringify(config, null, 2))
6067
console.log(`Deployed pyrightconfig.json to ${targetPath}`)
6168
}
6269

@@ -108,10 +115,10 @@ export function startPyrightBridge() {
108115
console.log('🔧 Auto-injecting project configuration')
109116

110117
msg.params = msg.params || {}
111-
msg.params.rootUri = `file://${BOT_ROOT}`
118+
msg.params.rootUri = pathToFileURL(BOT_ROOT).toString()
112119
msg.params.workspaceFolders = [
113120
{
114-
uri: `file://${BOT_ROOT}`,
121+
uri: pathToFileURL(BOT_ROOT).toString(),
115122
name: 'jesse-ai'
116123
}
117124
]
@@ -123,11 +130,27 @@ export function startPyrightBridge() {
123130
if (msg.params?.textDocument?.uri) {
124131
const uri = msg.params.textDocument.uri
125132

126-
// If not already absolute, make it absolute
127-
if (!uri.startsWith('file://')) {
128-
msg.params.textDocument.uri = `file://${path.join(BOT_ROOT, uri)}`
133+
// Parse the file URI to get the path
134+
if (uri.startsWith('file://')) {
135+
try {
136+
const filePath = fileURLToPath(uri)
137+
138+
// If the path is relative, make it absolute
139+
if (!path.isAbsolute(filePath)) {
140+
const absolutePath = path.join(BOT_ROOT, filePath)
141+
msg.params.textDocument.uri = pathToFileURL(absolutePath).toString()
142+
}
143+
} catch (err) {
144+
// If parsing fails, assume it's a relative path without proper file:// scheme
145+
const absolutePath = path.join(BOT_ROOT, uri.replace('file:///', '').replace('file://', ''))
146+
msg.params.textDocument.uri = pathToFileURL(absolutePath).toString()
129147
}
148+
} else {
149+
// No file:// prefix, treat as relative path
150+
const absolutePath = path.join(BOT_ROOT, uri)
151+
msg.params.textDocument.uri = pathToFileURL(absolutePath).toString()
130152
}
153+
}
131154

132155
writer.write(msg)
133156
})

pyrightconfig.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
"reportMissingImports": true,
33
"reportMissingTypeStubs": false,
44
"extraPaths": [
5-
"${JESSE_ROOT}",
5+
"${JESSE_ROOT}"
66
],
77
"executionEnvironments": [
88
{
99
"root": "${BOT_ROOT}",
1010
"extraPaths": [
11-
"${JESSE_ROOT}",
11+
"${JESSE_ROOT}"
1212
]
1313
}
1414
]

0 commit comments

Comments
 (0)