mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
Fix npm package build and installation issues (#360)
Co-authored-by: Alex Mazanov <alexandr.mazanov@gmail.com>
This commit is contained in:
parent
0d3b437887
commit
253d0ae3e7
21 changed files with 1376 additions and 411 deletions
20
.github/workflows/README.md
vendored
20
.github/workflows/README.md
vendored
|
|
@ -17,7 +17,23 @@ Basic CI workflow that runs on every push and PR affecting the web directory.
|
||||||
- Pull requests to `main`
|
- Pull requests to `main`
|
||||||
- Only when files in `web/` directory change
|
- Only when files in `web/` directory change
|
||||||
|
|
||||||
### 2. SEA Build Test (`sea-build-test.yml`)
|
### 2. NPM Package Test (`npm-test.yml`)
|
||||||
|
Dedicated workflow for testing the npm package build and installation.
|
||||||
|
|
||||||
|
**Jobs:**
|
||||||
|
- **Test NPM Package**: Builds the npm package and tests installation in a clean environment
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Builds npm package using the clean build approach
|
||||||
|
- Tests global installation without requiring build tools
|
||||||
|
- Verifies server startup and API functionality
|
||||||
|
- Validates package structure and dependencies
|
||||||
|
|
||||||
|
**Triggers:**
|
||||||
|
- Push to `main` or `npm-build` branches when web files change
|
||||||
|
- Pull requests to `main` when web files change
|
||||||
|
|
||||||
|
### 3. SEA Build Test (`sea-build-test.yml`)
|
||||||
Advanced workflow for testing Single Executable Application (SEA) builds with custom Node.js.
|
Advanced workflow for testing Single Executable Application (SEA) builds with custom Node.js.
|
||||||
|
|
||||||
**Features:**
|
**Features:**
|
||||||
|
|
@ -46,7 +62,7 @@ Advanced workflow for testing Single Executable Application (SEA) builds with cu
|
||||||
- Helps identify any Blacksmith-specific issues
|
- Helps identify any Blacksmith-specific issues
|
||||||
- Runs only on push events
|
- Runs only on push events
|
||||||
|
|
||||||
### 3. Xcode SEA Test (`xcode-sea-test.yml`)
|
### 4. Xcode SEA Test (`xcode-sea-test.yml`)
|
||||||
Tests the macOS Xcode build with custom Node.js to ensure the VibeTunnel.app works correctly with SEA executables.
|
Tests the macOS Xcode build with custom Node.js to ensure the VibeTunnel.app works correctly with SEA executables.
|
||||||
|
|
||||||
**Features:**
|
**Features:**
|
||||||
|
|
|
||||||
21
.github/workflows/mac.yml
vendored
21
.github/workflows/mac.yml
vendored
|
|
@ -154,7 +154,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
echo "Resolving Swift package dependencies..."
|
echo "Resolving Swift package dependencies..."
|
||||||
# Workspace is at root level
|
# Workspace is at root level
|
||||||
xcodebuild -resolvePackageDependencies -workspace VibeTunnel.xcworkspace -parallel || echo "Dependency resolution completed"
|
xcodebuild -resolvePackageDependencies -workspace VibeTunnel.xcworkspace || echo "Dependency resolution completed"
|
||||||
|
|
||||||
# Debug: List available schemes
|
# Debug: List available schemes
|
||||||
echo "=== Available schemes ==="
|
echo "=== Available schemes ==="
|
||||||
|
|
@ -179,25 +179,6 @@ jobs:
|
||||||
DEVELOPMENT_TEAM="" \
|
DEVELOPMENT_TEAM="" \
|
||||||
COMPILER_INDEX_STORE_ENABLE=NO
|
COMPILER_INDEX_STORE_ENABLE=NO
|
||||||
|
|
||||||
- name: Build Release (Native Architecture)
|
|
||||||
timeout-minutes: 15
|
|
||||||
run: |
|
|
||||||
set -o pipefail && \
|
|
||||||
xcodebuild build \
|
|
||||||
-workspace VibeTunnel.xcworkspace \
|
|
||||||
-scheme VibeTunnel-Mac \
|
|
||||||
-configuration Release \
|
|
||||||
-destination "platform=macOS" \
|
|
||||||
-showBuildTimingSummary \
|
|
||||||
CODE_SIGN_IDENTITY="" \
|
|
||||||
CODE_SIGNING_REQUIRED=NO \
|
|
||||||
CODE_SIGNING_ALLOWED=NO \
|
|
||||||
CODE_SIGN_ENTITLEMENTS="" \
|
|
||||||
ENABLE_HARDENED_RUNTIME=NO \
|
|
||||||
PROVISIONING_PROFILE_SPECIFIER="" \
|
|
||||||
DEVELOPMENT_TEAM="" \
|
|
||||||
COMPILER_INDEX_STORE_ENABLE=NO
|
|
||||||
|
|
||||||
# LINT PHASE
|
# LINT PHASE
|
||||||
- name: Run SwiftFormat (check mode)
|
- name: Run SwiftFormat (check mode)
|
||||||
id: swiftformat
|
id: swiftformat
|
||||||
|
|
|
||||||
236
.github/workflows/nightly.yml
vendored
Normal file
236
.github/workflows/nightly.yml
vendored
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
name: Nightly Release Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# Run at 2 AM UTC every day (10 PM EST / 7 PM PST)
|
||||||
|
- cron: '0 2 * * *'
|
||||||
|
workflow_dispatch: # Allow manual triggering
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release-build-test:
|
||||||
|
name: Build and Test Release Configuration
|
||||||
|
runs-on: [self-hosted, macOS, ARM64]
|
||||||
|
timeout-minutes: 60
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Clean workspace
|
||||||
|
run: |
|
||||||
|
# Clean workspace for self-hosted runner
|
||||||
|
rm -rf * || true
|
||||||
|
rm -rf .* || true
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Verify Xcode
|
||||||
|
run: |
|
||||||
|
xcodebuild -version
|
||||||
|
swift --version
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '24'
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 9
|
||||||
|
dest: ~/pnpm-${{ github.run_id }}
|
||||||
|
|
||||||
|
- name: Cache Homebrew packages
|
||||||
|
uses: actions/cache@v4
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/Library/Caches/Homebrew
|
||||||
|
/opt/homebrew/Cellar/swiftlint
|
||||||
|
/opt/homebrew/Cellar/swiftformat
|
||||||
|
/opt/homebrew/Cellar/xcbeautify
|
||||||
|
key: ${{ runner.os }}-brew-${{ hashFiles('.github/workflows/mac.yml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-brew-
|
||||||
|
|
||||||
|
- name: Install tools
|
||||||
|
run: |
|
||||||
|
# Install or update required tools
|
||||||
|
MAX_ATTEMPTS=3
|
||||||
|
WAIT_TIME=5
|
||||||
|
|
||||||
|
for attempt in $(seq 1 $MAX_ATTEMPTS); do
|
||||||
|
echo "Tool installation attempt $attempt of $MAX_ATTEMPTS"
|
||||||
|
|
||||||
|
# Check if another brew process is running
|
||||||
|
if pgrep -x "brew" > /dev/null; then
|
||||||
|
echo "Another brew process detected, waiting ${WAIT_TIME}s..."
|
||||||
|
sleep $WAIT_TIME
|
||||||
|
WAIT_TIME=$((WAIT_TIME * 2)) # Exponential backoff
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update Homebrew and install all tools in one command
|
||||||
|
if brew update && brew install swiftlint swiftformat xcbeautify; then
|
||||||
|
echo "Successfully installed/upgraded all tools"
|
||||||
|
break
|
||||||
|
else
|
||||||
|
if [ $attempt -eq $MAX_ATTEMPTS ]; then
|
||||||
|
echo "Failed to install tools after $MAX_ATTEMPTS attempts"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Command failed, waiting ${WAIT_TIME}s before retry..."
|
||||||
|
sleep $WAIT_TIME
|
||||||
|
WAIT_TIME=$((WAIT_TIME * 2)) # Exponential backoff
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Show versions
|
||||||
|
echo "SwiftLint: $(swiftlint --version || echo 'not found')"
|
||||||
|
echo "SwiftFormat: $(swiftformat --version || echo 'not found')"
|
||||||
|
echo "xcbeautify: $(xcbeautify --version || echo 'not found')"
|
||||||
|
|
||||||
|
- name: Cache pnpm store
|
||||||
|
uses: actions/cache@v4
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
path: ~/.local/share/pnpm/store
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('web/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
|
- name: Install web dependencies
|
||||||
|
run: |
|
||||||
|
cd web
|
||||||
|
# Clean any stale lock files
|
||||||
|
rm -f .pnpm-store.lock .pnpm-debug.log || true
|
||||||
|
# Set pnpm to use fewer workers to avoid crashes on self-hosted runners
|
||||||
|
export NODE_OPTIONS="--max-old-space-size=4096"
|
||||||
|
pnpm config set store-dir ~/.local/share/pnpm/store
|
||||||
|
pnpm config set package-import-method hardlink
|
||||||
|
# Install with retries
|
||||||
|
for i in 1 2 3; do
|
||||||
|
echo "Install attempt $i"
|
||||||
|
if pnpm install --frozen-lockfile; then
|
||||||
|
echo "pnpm install succeeded"
|
||||||
|
# Force rebuild of native modules
|
||||||
|
echo "Rebuilding native modules..."
|
||||||
|
pnpm rebuild || true
|
||||||
|
break
|
||||||
|
else
|
||||||
|
echo "pnpm install failed, cleaning and retrying..."
|
||||||
|
rm -rf node_modules .pnpm-store.lock || true
|
||||||
|
sleep 5
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Build web artifacts
|
||||||
|
run: |
|
||||||
|
echo "Building web artifacts..."
|
||||||
|
cd web
|
||||||
|
export CI=true
|
||||||
|
pnpm run build
|
||||||
|
echo "Web artifacts built successfully"
|
||||||
|
|
||||||
|
- name: Resolve Dependencies
|
||||||
|
run: |
|
||||||
|
echo "Resolving Swift package dependencies..."
|
||||||
|
xcodebuild -resolvePackageDependencies -workspace VibeTunnel.xcworkspace || echo "Dependency resolution completed"
|
||||||
|
|
||||||
|
# BUILD RELEASE CONFIGURATION
|
||||||
|
- name: Build Release (Universal Binary)
|
||||||
|
timeout-minutes: 20
|
||||||
|
run: |
|
||||||
|
echo "Building Release configuration with universal binary..."
|
||||||
|
set -o pipefail && \
|
||||||
|
xcodebuild build \
|
||||||
|
-workspace VibeTunnel.xcworkspace \
|
||||||
|
-scheme VibeTunnel-Mac \
|
||||||
|
-configuration Release \
|
||||||
|
-destination "generic/platform=macOS" \
|
||||||
|
-archivePath build/VibeTunnel.xcarchive \
|
||||||
|
-showBuildTimingSummary \
|
||||||
|
CODE_SIGN_IDENTITY="" \
|
||||||
|
CODE_SIGNING_REQUIRED=NO \
|
||||||
|
CODE_SIGNING_ALLOWED=NO \
|
||||||
|
CODE_SIGN_ENTITLEMENTS="" \
|
||||||
|
ENABLE_HARDENED_RUNTIME=NO \
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER="" \
|
||||||
|
DEVELOPMENT_TEAM="" \
|
||||||
|
ONLY_ACTIVE_ARCH=NO \
|
||||||
|
archive | xcbeautify
|
||||||
|
|
||||||
|
echo "Release build completed successfully"
|
||||||
|
|
||||||
|
# TEST RELEASE BUILD
|
||||||
|
- name: Test Release Configuration
|
||||||
|
timeout-minutes: 20
|
||||||
|
run: |
|
||||||
|
echo "Running tests on Release configuration..."
|
||||||
|
set -o pipefail && \
|
||||||
|
xcodebuild test \
|
||||||
|
-workspace VibeTunnel.xcworkspace \
|
||||||
|
-scheme VibeTunnel-Mac \
|
||||||
|
-configuration Release \
|
||||||
|
-destination "platform=macOS" \
|
||||||
|
-enableCodeCoverage YES \
|
||||||
|
-resultBundlePath TestResults-Release.xcresult \
|
||||||
|
CODE_SIGN_IDENTITY="" \
|
||||||
|
CODE_SIGNING_REQUIRED=NO \
|
||||||
|
CODE_SIGNING_ALLOWED=NO \
|
||||||
|
COMPILER_INDEX_STORE_ENABLE=NO | xcbeautify || {
|
||||||
|
echo "::error::Release configuration tests failed"
|
||||||
|
# Try to get more detailed error information
|
||||||
|
echo "=== Attempting to get test failure details ==="
|
||||||
|
xcrun xcresulttool get --path TestResults-Release.xcresult --format json 2>/dev/null | jq '.issues._values[]? | select(.severity == "error")' 2>/dev/null || true
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
echo "Release tests completed successfully"
|
||||||
|
|
||||||
|
# PERFORMANCE VALIDATION
|
||||||
|
- name: Validate Release Binary
|
||||||
|
run: |
|
||||||
|
echo "=== Validating Release Binary ==="
|
||||||
|
ARCHIVE_PATH="build/VibeTunnel.xcarchive"
|
||||||
|
APP_PATH="$ARCHIVE_PATH/Products/Applications/VibeTunnel.app"
|
||||||
|
|
||||||
|
if [ -d "$APP_PATH" ]; then
|
||||||
|
echo "Found VibeTunnel.app at: $APP_PATH"
|
||||||
|
|
||||||
|
# Check binary architectures
|
||||||
|
echo -e "\n=== Binary Architecture ==="
|
||||||
|
lipo -info "$APP_PATH/Contents/MacOS/VibeTunnel" || echo "Binary not found"
|
||||||
|
|
||||||
|
# Check binary size
|
||||||
|
echo -e "\n=== Binary Size ==="
|
||||||
|
du -sh "$APP_PATH" || echo "Could not determine app size"
|
||||||
|
|
||||||
|
# Check if optimizations were applied (Release should be smaller than Debug)
|
||||||
|
echo -e "\n=== Optimization Check ==="
|
||||||
|
# Look for debug symbols - Release builds should have minimal symbols
|
||||||
|
nm "$APP_PATH/Contents/MacOS/VibeTunnel" 2>/dev/null | grep -c "debug" || echo "No debug symbols found (good for Release)"
|
||||||
|
|
||||||
|
# Verify entitlements
|
||||||
|
echo -e "\n=== Entitlements ==="
|
||||||
|
codesign -d --entitlements - "$APP_PATH" 2>&1 || echo "No code signing (expected in CI)"
|
||||||
|
else
|
||||||
|
echo "::warning::Release archive not found at expected location"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# NOTIFY ON FAILURE
|
||||||
|
- name: Notify on Failure
|
||||||
|
if: failure()
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const issue = await github.rest.issues.create({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
title: `Nightly Release Build Failed - ${new Date().toISOString().split('T')[0]}`,
|
||||||
|
body: `The nightly release build failed. Please check the [workflow run](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}) for details.`,
|
||||||
|
labels: ['ci', 'nightly-build']
|
||||||
|
});
|
||||||
|
console.log(`Created issue #${issue.data.number}`);
|
||||||
10
.github/workflows/node.yml
vendored
10
.github/workflows/node.yml
vendored
|
|
@ -172,6 +172,11 @@ jobs:
|
||||||
working-directory: web
|
working-directory: web
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build node-pty
|
||||||
|
working-directory: web
|
||||||
|
run: |
|
||||||
|
cd node-pty && npm install && npm run build
|
||||||
|
|
||||||
- name: Build frontend and backend
|
- name: Build frontend and backend
|
||||||
working-directory: web
|
working-directory: web
|
||||||
run: pnpm run build:ci
|
run: pnpm run build:ci
|
||||||
|
|
@ -327,6 +332,11 @@ jobs:
|
||||||
working-directory: web
|
working-directory: web
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build node-pty for TypeScript
|
||||||
|
working-directory: web
|
||||||
|
run: |
|
||||||
|
cd node-pty && npm install && npm run build
|
||||||
|
|
||||||
- name: Check TypeScript types
|
- name: Check TypeScript types
|
||||||
working-directory: web
|
working-directory: web
|
||||||
run: pnpm run typecheck
|
run: pnpm run typecheck
|
||||||
|
|
|
||||||
130
.github/workflows/npm-test.yml
vendored
Normal file
130
.github/workflows/npm-test.yml
vendored
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
name: NPM Package Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, npm-build ]
|
||||||
|
paths:
|
||||||
|
- 'web/**'
|
||||||
|
- '.github/workflows/npm-test.yml'
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
paths:
|
||||||
|
- 'web/**'
|
||||||
|
- '.github/workflows/npm-test.yml'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: web
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-npm-package:
|
||||||
|
name: Test NPM Package
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10.12.1
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: 'web/pnpm-lock.yaml'
|
||||||
|
|
||||||
|
- name: Install system dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libpam0g-dev build-essential python3 make g++
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build node-pty
|
||||||
|
run: |
|
||||||
|
cd node-pty && npm install && npm run build
|
||||||
|
|
||||||
|
- name: Build npm package
|
||||||
|
run: pnpm run build:npm -- --current-only
|
||||||
|
|
||||||
|
- name: Test npm package installation
|
||||||
|
run: |
|
||||||
|
# Create a test directory
|
||||||
|
mkdir -p /tmp/npm-test
|
||||||
|
cd /tmp/npm-test
|
||||||
|
|
||||||
|
# Copy the built package
|
||||||
|
cp ${{ github.workspace }}/web/vibetunnel-*.tgz .
|
||||||
|
|
||||||
|
# Install the package globally
|
||||||
|
npm install -g vibetunnel-*.tgz
|
||||||
|
|
||||||
|
# Verify installation
|
||||||
|
echo "=== Verifying installation ==="
|
||||||
|
which vibetunnel || (echo "vibetunnel not found" && exit 1)
|
||||||
|
which vt || echo "vt command not installed (expected on Linux)"
|
||||||
|
|
||||||
|
# Check if node-pty was extracted correctly
|
||||||
|
echo "=== Checking node-pty extraction ==="
|
||||||
|
# With the new build, node-pty is bundled directly in the package
|
||||||
|
ls -la $(npm root -g)/vibetunnel/node-pty/ || echo "Checking node-pty structure..."
|
||||||
|
ls -la $(npm root -g)/vibetunnel/node-pty/build/Release/pty.node || echo "node-pty prebuild will be extracted on postinstall"
|
||||||
|
|
||||||
|
# Check package structure
|
||||||
|
echo "=== Checking package structure ==="
|
||||||
|
ls -la $(npm root -g)/vibetunnel/
|
||||||
|
ls -la $(npm root -g)/vibetunnel/lib/
|
||||||
|
|
||||||
|
# Note: authenticate-pam is installed as a regular dependency now
|
||||||
|
# It's not bundled in the new clean build approach
|
||||||
|
|
||||||
|
# Test server startup
|
||||||
|
echo "=== Testing server startup ==="
|
||||||
|
vibetunnel --port 4020 --no-auth &
|
||||||
|
SERVER_PID=$!
|
||||||
|
|
||||||
|
# Wait for server to start
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
# Check if server is running
|
||||||
|
if ps -p $SERVER_PID > /dev/null; then
|
||||||
|
echo "✅ Server process is running"
|
||||||
|
else
|
||||||
|
echo "❌ Server process died"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test HTTP endpoint
|
||||||
|
if curl -s -f http://localhost:4020 > /dev/null; then
|
||||||
|
echo "✅ HTTP server is responding"
|
||||||
|
else
|
||||||
|
echo "❌ HTTP server not responding"
|
||||||
|
kill $SERVER_PID 2>/dev/null
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test API endpoint
|
||||||
|
RESPONSE=$(curl -s http://localhost:4020/api/sessions)
|
||||||
|
# Check if response is an array (either empty [] or with sessions)
|
||||||
|
if echo "$RESPONSE" | grep -E '^\[.*\]$' > /dev/null; then
|
||||||
|
echo "✅ API is responding correctly"
|
||||||
|
echo "Response: $RESPONSE"
|
||||||
|
else
|
||||||
|
echo "❌ API not responding correctly"
|
||||||
|
echo "Response: $RESPONSE"
|
||||||
|
kill $SERVER_PID 2>/dev/null
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
kill $SERVER_PID
|
||||||
|
echo "✅ All tests passed!"
|
||||||
11
.github/workflows/web-ci.yml
vendored
11
.github/workflows/web-ci.yml
vendored
|
|
@ -51,6 +51,10 @@ jobs:
|
||||||
- name: Run linting
|
- name: Run linting
|
||||||
run: pnpm run lint
|
run: pnpm run lint
|
||||||
|
|
||||||
|
- name: Build node-pty for TypeScript
|
||||||
|
run: |
|
||||||
|
cd node-pty && npm install && npm run build
|
||||||
|
|
||||||
- name: Run type checking
|
- name: Run type checking
|
||||||
run: pnpm run typecheck
|
run: pnpm run typecheck
|
||||||
|
|
||||||
|
|
@ -84,6 +88,10 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build node-pty
|
||||||
|
run: |
|
||||||
|
cd node-pty && npm install && npm run build
|
||||||
|
|
||||||
- name: Build project
|
- name: Build project
|
||||||
run: pnpm run build
|
run: pnpm run build
|
||||||
|
|
||||||
|
|
@ -143,4 +151,5 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: server-coverage-report
|
name: server-coverage-report
|
||||||
path: web/coverage/server/
|
path: web/coverage/server/
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
|
|
|
||||||
1
web/.gitignore
vendored
1
web/.gitignore
vendored
|
|
@ -7,6 +7,7 @@ yarn-error.log*
|
||||||
# Generated files
|
# Generated files
|
||||||
public/
|
public/
|
||||||
dist/
|
dist/
|
||||||
|
dist-npm/
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
*.log
|
*.log
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
// Start the CLI - it handles all command routing including 'fwd'
|
// Start the CLI - it handles all command routing including 'fwd'
|
||||||
require('../dist/cli.js');
|
require('../dist/vibetunnel-cli');
|
||||||
|
|
@ -41,11 +41,12 @@ VibeTunnel requires two native modules:
|
||||||
- **Dependencies**: None (vendored implementation)
|
- **Dependencies**: None (vendored implementation)
|
||||||
|
|
||||||
### 2. authenticate-pam (Authentication)
|
### 2. authenticate-pam (Authentication)
|
||||||
- **Purpose**: PAM (Pluggable Authentication Modules) integration
|
- **Purpose**: PAM (Pluggable Authentication Modules) integration for system authentication
|
||||||
- **Components**:
|
- **Components**:
|
||||||
- `authenticate_pam.node`: Node.js addon for system authentication
|
- `authenticate_pam.node`: Node.js addon for system authentication
|
||||||
- **Platforms**: Linux primarily, macOS for compatibility
|
- **Platforms**: Both macOS and Linux
|
||||||
- **Dependencies**: System PAM libraries
|
- **Dependencies**: System PAM libraries
|
||||||
|
- **Note**: While macOS uses different authentication mechanisms internally (OpenDirectory), VibeTunnel attempts PAM authentication on both platforms as a fallback after SSH key authentication
|
||||||
|
|
||||||
## Prebuild System
|
## Prebuild System
|
||||||
|
|
||||||
|
|
@ -55,7 +56,9 @@ We use `prebuild` and `prebuild-install` to provide precompiled native modules,
|
||||||
### Coverage
|
### Coverage
|
||||||
- **Node.js versions**: 20, 22, 23, 24
|
- **Node.js versions**: 20, 22, 23, 24
|
||||||
- **Platforms**: macOS (x64, arm64), Linux (x64, arm64)
|
- **Platforms**: macOS (x64, arm64), Linux (x64, arm64)
|
||||||
- **Total prebuilds**: 32 binaries (16 per native module)
|
- **Total prebuilds**: 24 binaries
|
||||||
|
- node-pty: 16 binaries (macOS and Linux, all architectures)
|
||||||
|
- authenticate-pam: 8 binaries (Linux only - macOS builds may fail due to PAM differences)
|
||||||
|
|
||||||
### Prebuild Files
|
### Prebuild Files
|
||||||
```
|
```
|
||||||
|
|
@ -64,17 +67,23 @@ prebuilds/
|
||||||
├── node-pty-v1.0.0-node-v115-darwin-x64.tar.gz
|
├── node-pty-v1.0.0-node-v115-darwin-x64.tar.gz
|
||||||
├── node-pty-v1.0.0-node-v115-linux-arm64.tar.gz
|
├── node-pty-v1.0.0-node-v115-linux-arm64.tar.gz
|
||||||
├── node-pty-v1.0.0-node-v115-linux-x64.tar.gz
|
├── node-pty-v1.0.0-node-v115-linux-x64.tar.gz
|
||||||
├── authenticate-pam-v1.0.5-node-v115-darwin-arm64.tar.gz
|
|
||||||
├── authenticate-pam-v1.0.5-node-v115-darwin-x64.tar.gz
|
|
||||||
├── authenticate-pam-v1.0.5-node-v115-linux-arm64.tar.gz
|
├── authenticate-pam-v1.0.5-node-v115-linux-arm64.tar.gz
|
||||||
├── authenticate-pam-v1.0.5-node-v115-linux-x64.tar.gz
|
├── authenticate-pam-v1.0.5-node-v115-linux-x64.tar.gz
|
||||||
└── ... (similar for node versions 22, 23, 24)
|
└── ... (similar for node versions 22, 23, 24, Linux only)
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Node version numbers map to internal versions (v115=Node 20, v127=Node 22, v131=Node 23, v134=Node 24)
|
Note: Node version numbers map to internal versions (v115=Node 20, v127=Node 22, v131=Node 23, v134=Node 24)
|
||||||
|
|
||||||
## Build Process
|
## Build Process
|
||||||
|
|
||||||
|
### Clean Build Approach
|
||||||
|
The npm build process uses a clean distribution directory approach that follows npm best practices:
|
||||||
|
|
||||||
|
1. **Creates dist-npm/ directory** - Separate from source files
|
||||||
|
2. **Generates clean package.json** - Only production fields, no dev dependencies
|
||||||
|
3. **Bundles dependencies** - node-pty is bundled directly, no symlinks needed
|
||||||
|
4. **Preserves source integrity** - Never modifies source package.json
|
||||||
|
|
||||||
### Unified Build (Multi-Platform by Default)
|
### Unified Build (Multi-Platform by Default)
|
||||||
```bash
|
```bash
|
||||||
npm run build:npm
|
npm run build:npm
|
||||||
|
|
@ -83,6 +92,7 @@ npm run build:npm
|
||||||
- Builds native modules for all supported platforms (macOS x64/arm64, Linux x64/arm64)
|
- Builds native modules for all supported platforms (macOS x64/arm64, Linux x64/arm64)
|
||||||
- Creates comprehensive prebuilds for zero-dependency installation
|
- Creates comprehensive prebuilds for zero-dependency installation
|
||||||
- Generates npm README optimized for package distribution
|
- Generates npm README optimized for package distribution
|
||||||
|
- Creates clean dist-npm/ directory for packaging
|
||||||
|
|
||||||
### Build Options
|
### Build Options
|
||||||
The unified build script supports flexible targeting:
|
The unified build script supports flexible targeting:
|
||||||
|
|
@ -113,44 +123,45 @@ The build will fail with helpful error messages if Docker is not available.
|
||||||
|
|
||||||
### For End Users
|
### For End Users
|
||||||
1. **Install package**: `npm install -g vibetunnel`
|
1. **Install package**: `npm install -g vibetunnel`
|
||||||
2. **Prebuild-install runs**: Attempts to download prebuilt binaries
|
2. **Postinstall script runs**: Extracts appropriate prebuilt binaries
|
||||||
3. **Fallback compilation**: If prebuilds fail, compiles from source
|
3. **No compilation needed**: Prebuilds included for all supported platforms
|
||||||
4. **Result**: Working VibeTunnel installation
|
4. **Result**: Working VibeTunnel installation without build tools
|
||||||
|
|
||||||
|
### Key Improvements
|
||||||
|
- **No symlinks**: node-pty is bundled directly, avoiding postinstall symlink issues
|
||||||
|
- **Clean package structure**: Only production files in the npm package
|
||||||
|
- **Reliable installation**: Works in restricted environments (Docker, CI)
|
||||||
|
|
||||||
### Installation Scripts
|
### Installation Scripts
|
||||||
The package uses a multi-stage installation approach:
|
The package uses a simplified postinstall approach:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "prebuild-install || node scripts/postinstall-npm.js"
|
"postinstall": "node scripts/postinstall.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Stage 1: prebuild-install
|
#### Postinstall Process
|
||||||
- Downloads appropriate prebuilt binary for current platform/Node version
|
- **Prebuild extraction**: Extracts the appropriate prebuild for the current platform
|
||||||
- Installs to standard locations
|
- **No downloads**: All prebuilds are included in the package
|
||||||
- **Success**: Installation complete, no compilation needed
|
- **No compilation**: Everything is pre-built, no build tools required
|
||||||
- **Failure**: Proceeds to Stage 2
|
- **Platform detection**: Automatically selects correct binary based on OS and architecture
|
||||||
|
|
||||||
#### Stage 2: postinstall-npm.js
|
|
||||||
- **node-pty**: Essential module, installation fails if build fails
|
|
||||||
- **authenticate-pam**: Optional module, warns if build fails
|
|
||||||
- Provides helpful error messages about required build tools
|
|
||||||
|
|
||||||
## Platform-Specific Details
|
## Platform-Specific Details
|
||||||
|
|
||||||
### macOS
|
### macOS
|
||||||
- **spawn-helper**: Additional C binary needed for proper PTY operations
|
- **spawn-helper**: Additional C binary needed for proper PTY operations (now prebuilt as universal binary)
|
||||||
- **Built during install**: spawn-helper compiles via node-gyp when needed
|
- **Authentication**: Attempts PAM authentication but may fall back to environment variables or SSH keys
|
||||||
- **Architecture**: Supports both Intel (x64) and Apple Silicon (arm64)
|
- **Architecture**: Supports both Intel (x64) and Apple Silicon (arm64)
|
||||||
- **Build tools**: Requires Xcode Command Line Tools for source compilation
|
- **Build tools**: Not required with prebuilds; Xcode Command Line Tools only needed for source compilation fallback
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
- **PAM libraries**: Requires `libpam0g-dev` for authenticate-pam compilation
|
- **PAM authentication**: Full support via authenticate-pam module
|
||||||
|
- **PAM libraries**: Requires `libpam0g-dev` for authenticate-pam compilation from source
|
||||||
- **spawn-helper**: Not used on Linux (macOS-only)
|
- **spawn-helper**: Not used on Linux (macOS-only)
|
||||||
- **Build tools**: Requires `build-essential` package for source compilation
|
- **Build tools**: Not required with prebuilds; `build-essential` only needed for source compilation fallback
|
||||||
|
|
||||||
### Docker Build Environment
|
### Docker Build Environment
|
||||||
Linux prebuilds are created using Docker with:
|
Linux prebuilds are created using Docker with:
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,12 @@
|
||||||
"dist/",
|
"dist/",
|
||||||
"public/",
|
"public/",
|
||||||
"bin/",
|
"bin/",
|
||||||
|
"scripts/ensure-native-modules.js",
|
||||||
|
"scripts/postinstall-npm.js",
|
||||||
"node-pty/lib/",
|
"node-pty/lib/",
|
||||||
"node-pty/package.json",
|
"node-pty/package.json",
|
||||||
|
"node-pty/binding.gyp",
|
||||||
|
"node-pty/src/",
|
||||||
"prebuilds/",
|
"prebuilds/",
|
||||||
"README.md"
|
"README.md"
|
||||||
],
|
],
|
||||||
|
|
@ -40,7 +44,7 @@
|
||||||
"build:ci": "node scripts/build-ci.js",
|
"build:ci": "node scripts/build-ci.js",
|
||||||
"build:npm": "node scripts/build-npm.js",
|
"build:npm": "node scripts/build-npm.js",
|
||||||
"prepublishOnly": "npm run build:npm",
|
"prepublishOnly": "npm run build:npm",
|
||||||
"postinstall": "node scripts/ensure-native-modules.js",
|
"postinstall": "node scripts/postinstall-npm.js",
|
||||||
"prebuild": "echo 'Skipping prebuild - handled by build-npm.js'",
|
"prebuild": "echo 'Skipping prebuild - handled by build-npm.js'",
|
||||||
"prebuild:upload": "echo 'Skipping prebuild:upload - not used'",
|
"prebuild:upload": "echo 'Skipping prebuild:upload - not used'",
|
||||||
"lint": "concurrently -n biome,tsc-server,tsc-client,tsc-sw \"biome check src\" \"tsc --noEmit --project tsconfig.server.json\" \"tsc --noEmit --project tsconfig.client.json\" \"tsc --noEmit --project tsconfig.sw.json\"",
|
"lint": "concurrently -n biome,tsc-server,tsc-client,tsc-sw \"biome check src\" \"tsc --noEmit --project tsconfig.server.json\" \"tsc --noEmit --project tsconfig.client.json\" \"tsc --noEmit --project tsconfig.sw.json\"",
|
||||||
|
|
|
||||||
78
web/package.npm.json
Normal file
78
web/package.npm.json
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
{
|
||||||
|
"name": "vibetunnel",
|
||||||
|
"version": "1.0.0-beta.10",
|
||||||
|
"description": "Terminal sharing server with web interface - supports macOS, Linux, and headless environments",
|
||||||
|
"main": "lib/cli.js",
|
||||||
|
"bin": {
|
||||||
|
"vibetunnel": "./bin/vibetunnel",
|
||||||
|
"vt": "./bin/vt"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"lib/",
|
||||||
|
"public/",
|
||||||
|
"bin/",
|
||||||
|
"scripts/",
|
||||||
|
"node-pty/",
|
||||||
|
"node_modules/authenticate-pam/",
|
||||||
|
"prebuilds/",
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"os": [
|
||||||
|
"darwin",
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/amantus-ai/vibetunnel.git",
|
||||||
|
"directory": "web"
|
||||||
|
},
|
||||||
|
"homepage": "https://vibetunnel.sh",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/amantus-ai/vibetunnel/issues"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"postinstall": "node scripts/postinstall.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/commands": "^6.8.1",
|
||||||
|
"@codemirror/lang-css": "^6.3.1",
|
||||||
|
"@codemirror/lang-html": "^6.4.9",
|
||||||
|
"@codemirror/lang-javascript": "^6.2.4",
|
||||||
|
"@codemirror/lang-json": "^6.0.2",
|
||||||
|
"@codemirror/lang-markdown": "^6.3.3",
|
||||||
|
"@codemirror/lang-python": "^6.2.1",
|
||||||
|
"@codemirror/state": "^6.5.2",
|
||||||
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
|
"@codemirror/view": "^6.38.0",
|
||||||
|
"@xterm/headless": "^5.5.0",
|
||||||
|
"authenticate-pam": "^1.0.5",
|
||||||
|
"bonjour-service": "^1.3.0",
|
||||||
|
"chalk": "^5.4.1",
|
||||||
|
"compression": "^1.8.0",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"helmet": "^8.1.0",
|
||||||
|
"http-proxy-middleware": "^3.0.5",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"lit": "^3.3.1",
|
||||||
|
"mime-types": "^3.0.1",
|
||||||
|
"monaco-editor": "^0.52.2",
|
||||||
|
"multer": "^2.0.1",
|
||||||
|
"node-addon-api": "^7.1.0",
|
||||||
|
"node-pty": "file:node-pty",
|
||||||
|
"postject": "1.0.0-alpha.6",
|
||||||
|
"signal-exit": "^4.1.0",
|
||||||
|
"web-push": "^3.6.7",
|
||||||
|
"ws": "^8.18.3"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"terminal",
|
||||||
|
"multiplexer",
|
||||||
|
"websocket",
|
||||||
|
"asciinema"
|
||||||
|
],
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
611
web/scripts/build-npm.js
Executable file → Normal file
611
web/scripts/build-npm.js
Executable file → Normal file
|
|
@ -1,7 +1,8 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unified npm build script for VibeTunnel
|
* Clean npm build script for VibeTunnel
|
||||||
|
* Uses a separate dist-npm directory with its own package.json
|
||||||
* Builds for all platforms by default with complete prebuild support
|
* Builds for all platforms by default with complete prebuild support
|
||||||
*
|
*
|
||||||
* Options:
|
* Options:
|
||||||
|
|
@ -11,7 +12,7 @@
|
||||||
* --arch <arch> Build for specific architecture (x64, arm64)
|
* --arch <arch> Build for specific architecture (x64, arm64)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { execSync, spawn } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
|
@ -21,13 +22,18 @@ const ALL_PLATFORMS = {
|
||||||
linux: ['x64', 'arm64']
|
linux: ['x64', 'arm64']
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DIST_DIR = path.join(__dirname, '..', 'dist-npm');
|
||||||
|
const ROOT_DIR = path.join(__dirname, '..');
|
||||||
|
|
||||||
// Map Node.js versions to ABI versions
|
// Map Node.js versions to ABI versions
|
||||||
|
// ABI versions from: https://nodejs.org/api/n-api.html#node-api-version-matrix
|
||||||
|
// These map to the internal V8 ABI versions used by prebuild
|
||||||
function getNodeAbi(nodeVersion) {
|
function getNodeAbi(nodeVersion) {
|
||||||
const abiMap = {
|
const abiMap = {
|
||||||
'20': '115',
|
'20': '115', // Node.js 20.x uses ABI 115
|
||||||
'22': '127',
|
'22': '127', // Node.js 22.x uses ABI 127
|
||||||
'23': '131',
|
'23': '131', // Node.js 23.x uses ABI 131
|
||||||
'24': '134'
|
'24': '134' // Node.js 24.x uses ABI 134
|
||||||
};
|
};
|
||||||
return abiMap[nodeVersion];
|
return abiMap[nodeVersion];
|
||||||
}
|
}
|
||||||
|
|
@ -41,6 +47,20 @@ const platformFilter = args.find(arg => arg.startsWith('--platform'))?.split('='
|
||||||
const archFilter = args.find(arg => arg.startsWith('--arch'))?.split('=')[1] ||
|
const archFilter = args.find(arg => arg.startsWith('--arch'))?.split('=')[1] ||
|
||||||
(args.includes('--arch') ? args[args.indexOf('--arch') + 1] : null);
|
(args.includes('--arch') ? args[args.indexOf('--arch') + 1] : null);
|
||||||
|
|
||||||
|
// Validate platform and architecture arguments
|
||||||
|
const VALID_PLATFORMS = ['darwin', 'linux'];
|
||||||
|
const VALID_ARCHS = ['x64', 'arm64'];
|
||||||
|
|
||||||
|
if (platformFilter && !VALID_PLATFORMS.includes(platformFilter)) {
|
||||||
|
console.error(`❌ Invalid platform: ${platformFilter}. Valid options: ${VALID_PLATFORMS.join(', ')}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (archFilter && !VALID_ARCHS.includes(archFilter)) {
|
||||||
|
console.error(`❌ Invalid arch: ${archFilter}. Valid options: ${VALID_ARCHS.join(', ')}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
let PLATFORMS = ALL_PLATFORMS;
|
let PLATFORMS = ALL_PLATFORMS;
|
||||||
|
|
||||||
if (currentOnly) {
|
if (currentOnly) {
|
||||||
|
|
@ -61,7 +81,7 @@ if (currentOnly) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🚀 Building VibeTunnel for npm distribution...\n');
|
console.log('🚀 Building VibeTunnel for npm distribution (clean approach)...\n');
|
||||||
|
|
||||||
if (currentOnly) {
|
if (currentOnly) {
|
||||||
console.log(`📦 Legacy mode: Building for ${process.platform}/${process.arch} only\n`);
|
console.log(`📦 Legacy mode: Building for ${process.platform}/${process.arch} only\n`);
|
||||||
|
|
@ -202,15 +222,25 @@ function buildMacOS() {
|
||||||
for (const arch of PLATFORMS.darwin || []) {
|
for (const arch of PLATFORMS.darwin || []) {
|
||||||
console.log(` → authenticate-pam for Node.js ${nodeVersion} ${arch}`);
|
console.log(` → authenticate-pam for Node.js ${nodeVersion} ${arch}`);
|
||||||
try {
|
try {
|
||||||
execSync(`npx prebuild --runtime node --target ${nodeVersion}.0.0 --arch ${arch} --tag-prefix authenticate-pam-v`, {
|
// Use inherit stdio to see any errors during build
|
||||||
|
const result = execSync(`npx prebuild --runtime node --target ${nodeVersion}.0.0 --arch ${arch} --tag-prefix authenticate-pam-v`, {
|
||||||
cwd: authenticatePamDir,
|
cwd: authenticatePamDir,
|
||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
env: { ...process.env, npm_config_target_platform: 'darwin', npm_config_target_arch: arch }
|
env: { ...process.env, npm_config_target_platform: 'darwin', npm_config_target_arch: arch }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check if prebuild was actually created
|
||||||
|
const prebuildFile = path.join(authenticatePamDir, 'prebuilds', `authenticate-pam-v1.0.5-node-v${getNodeAbi(nodeVersion)}-darwin-${arch}.tar.gz`);
|
||||||
|
if (fs.existsSync(prebuildFile)) {
|
||||||
|
console.log(` ✅ Created ${path.basename(prebuildFile)}`);
|
||||||
|
} else {
|
||||||
|
console.warn(` ⚠️ Prebuild file not created for Node.js ${nodeVersion} ${arch}`);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(` ❌ Failed to build authenticate-pam for Node.js ${nodeVersion} ${arch}`);
|
// Don't exit on macOS authenticate-pam build failures - it might work during npm install
|
||||||
console.error(` Error: ${error.message}`);
|
console.warn(` ⚠️ authenticate-pam build failed for macOS (this may be normal)`);
|
||||||
process.exit(1);
|
console.warn(` Error: ${error.message}`);
|
||||||
|
// Continue with other builds instead of exiting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -345,38 +375,122 @@ function mergePrebuilds() {
|
||||||
console.log(`✅ Merged prebuilds: ${nodePtyCount} node-pty + ${pamCount} authenticate-pam = ${allPrebuilds.length} total\n`);
|
console.log(`✅ Merged prebuilds: ${nodePtyCount} node-pty + ${pamCount} authenticate-pam = ${allPrebuilds.length} total\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copy authenticate-pam module for Linux support (OUR LINUX FIX)
|
||||||
|
function copyAuthenticatePam() {
|
||||||
|
console.log('📦 Copying authenticate-pam module for Linux support...\n');
|
||||||
|
|
||||||
|
const srcDir = path.join(ROOT_DIR, 'node_modules', '.pnpm', 'authenticate-pam@1.0.5', 'node_modules', 'authenticate-pam');
|
||||||
|
const destDir = path.join(DIST_DIR, 'node_modules', 'authenticate-pam');
|
||||||
|
|
||||||
|
if (!fs.existsSync(srcDir)) {
|
||||||
|
console.warn('⚠️ authenticate-pam source not found, Linux PAM auth may not work');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create destination directory structure
|
||||||
|
fs.mkdirSync(path.dirname(destDir), { recursive: true });
|
||||||
|
|
||||||
|
// Copy entire module
|
||||||
|
fs.cpSync(srcDir, destDir, { recursive: true });
|
||||||
|
console.log('✅ authenticate-pam module copied to dist-npm for Linux PAM auth\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced validation (OUR IMPROVEMENT)
|
||||||
|
function validatePackageHybrid() {
|
||||||
|
console.log('🔍 Validating hybrid package completeness...\n');
|
||||||
|
|
||||||
|
const errors = [];
|
||||||
|
const warnings = [];
|
||||||
|
|
||||||
|
// Check critical files in dist-npm
|
||||||
|
const criticalFiles = [
|
||||||
|
'lib/vibetunnel-cli',
|
||||||
|
'lib/cli.js',
|
||||||
|
'bin/vibetunnel',
|
||||||
|
'bin/vt',
|
||||||
|
'scripts/postinstall.js',
|
||||||
|
'public/index.html',
|
||||||
|
'node-pty/package.json',
|
||||||
|
'node-pty/binding.gyp',
|
||||||
|
'package.json'
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const file of criticalFiles) {
|
||||||
|
const fullPath = path.join(DIST_DIR, file);
|
||||||
|
if (!fs.existsSync(fullPath)) {
|
||||||
|
errors.push(`Missing critical file: ${file}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check prebuilds (only required when not in current-only mode)
|
||||||
|
const prebuildsDir = path.join(DIST_DIR, 'prebuilds');
|
||||||
|
if (!currentOnly) {
|
||||||
|
if (!fs.existsSync(prebuildsDir)) {
|
||||||
|
errors.push('Missing prebuilds directory in dist-npm');
|
||||||
|
} else {
|
||||||
|
const prebuilds = fs.readdirSync(prebuildsDir).filter(f => f.endsWith('.tar.gz'));
|
||||||
|
if (prebuilds.length === 0) {
|
||||||
|
warnings.push('No prebuilds found in dist-npm prebuilds directory');
|
||||||
|
} else {
|
||||||
|
console.log(` Found ${prebuilds.length} prebuilds in dist-npm`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(' ⚠️ Prebuilds skipped in current-only mode');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check authenticate-pam (Linux support)
|
||||||
|
const authPamDir = path.join(DIST_DIR, 'node_modules', 'authenticate-pam');
|
||||||
|
if (!fs.existsSync(authPamDir)) {
|
||||||
|
warnings.push('authenticate-pam module not included (Linux PAM auth will not work)');
|
||||||
|
} else {
|
||||||
|
console.log(' ✅ authenticate-pam module included for Linux support');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate package.json
|
||||||
|
const packageJsonPath = path.join(DIST_DIR, 'package.json');
|
||||||
|
if (fs.existsSync(packageJsonPath)) {
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||||
|
|
||||||
|
// Check postinstall script
|
||||||
|
if (!packageJson.scripts || !packageJson.scripts.postinstall) {
|
||||||
|
errors.push('Missing postinstall script in package.json');
|
||||||
|
} else {
|
||||||
|
console.log(' ✅ Postinstall script configured');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report results
|
||||||
|
if (errors.length > 0) {
|
||||||
|
console.error('❌ Package validation failed:');
|
||||||
|
errors.forEach(err => console.error(` - ${err}`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (warnings.length > 0) {
|
||||||
|
console.warn('⚠️ Package warnings:');
|
||||||
|
warnings.forEach(warn => console.warn(` - ${warn}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ Hybrid package validation passed\n');
|
||||||
|
}
|
||||||
|
|
||||||
// Main build process
|
// Main build process
|
||||||
async function main() {
|
async function main() {
|
||||||
// Step 0: Temporarily modify package.json for npm packaging
|
// Step 0: Clean previous build
|
||||||
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
console.log('0️⃣ Cleaning previous build...');
|
||||||
const originalPackageJson = fs.readFileSync(packageJsonPath, 'utf8');
|
if (fs.existsSync(DIST_DIR)) {
|
||||||
const packageJson = JSON.parse(originalPackageJson);
|
fs.rmSync(DIST_DIR, { recursive: true });
|
||||||
|
}
|
||||||
|
fs.mkdirSync(DIST_DIR, { recursive: true });
|
||||||
|
|
||||||
// Store original postinstall
|
// Step 1: Standard build process
|
||||||
const originalPostinstall = packageJson.scripts.postinstall;
|
console.log('\n1️⃣ Running standard build process...\n');
|
||||||
|
|
||||||
// Set install script for npm package
|
|
||||||
packageJson.scripts.install = 'prebuild-install || node scripts/postinstall-npm.js';
|
|
||||||
delete packageJson.scripts.postinstall;
|
|
||||||
|
|
||||||
// Add prebuild dependencies for npm package only
|
|
||||||
packageJson.dependencies['prebuild-install'] = '^7.1.3';
|
|
||||||
|
|
||||||
// Write modified package.json
|
|
||||||
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
||||||
|
|
||||||
// Restore original package.json on exit
|
|
||||||
const restorePackageJson = () => {
|
|
||||||
fs.writeFileSync(packageJsonPath, originalPackageJson);
|
|
||||||
};
|
|
||||||
process.on('exit', restorePackageJson);
|
|
||||||
process.on('SIGINT', () => { restorePackageJson(); process.exit(1); });
|
|
||||||
process.on('SIGTERM', () => { restorePackageJson(); process.exit(1); });
|
|
||||||
|
|
||||||
// Step 1: Standard build process (includes spawn-helper)
|
|
||||||
console.log('1️⃣ Running standard build process...\n');
|
|
||||||
try {
|
try {
|
||||||
execSync('node scripts/build.js', { stdio: 'inherit' });
|
execSync('npm run build', {
|
||||||
|
cwd: ROOT_DIR,
|
||||||
|
stdio: 'inherit'
|
||||||
|
});
|
||||||
console.log('✅ Standard build completed\n');
|
console.log('✅ Standard build completed\n');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Standard build failed:', error.message);
|
console.error('❌ Standard build failed:', error.message);
|
||||||
|
|
@ -406,25 +520,199 @@ async function main() {
|
||||||
mergePrebuilds();
|
mergePrebuilds();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3: Ensure node-pty is built for current platform
|
// Step 3: Copy necessary files to dist-npm
|
||||||
console.log('3️⃣ Ensuring node-pty is built for current platform...\n');
|
console.log('3️⃣ Copying files to dist-npm...\n');
|
||||||
const nodePtyBuild = path.join(__dirname, '..', 'node-pty', 'build', 'Release', 'pty.node');
|
|
||||||
if (!fs.existsSync(nodePtyBuild)) {
|
const filesToCopy = [
|
||||||
console.log(' Building node-pty for current platform...');
|
// Compiled CLI
|
||||||
const nodePtyDir = path.join(__dirname, '..', 'node-pty');
|
{ src: 'dist/vibetunnel-cli', dest: 'lib/cli.js' },
|
||||||
try {
|
{ src: 'dist/tsconfig.server.tsbuildinfo', dest: 'lib/tsconfig.server.tsbuildinfo' },
|
||||||
execSync('npm run install', { cwd: nodePtyDir, stdio: 'inherit' });
|
|
||||||
console.log('✅ node-pty built successfully');
|
// Bin scripts
|
||||||
} catch (error) {
|
{ src: 'bin', dest: 'bin' },
|
||||||
console.error('❌ Failed to build node-pty:', error.message);
|
|
||||||
process.exit(1);
|
// Public assets
|
||||||
|
{ src: 'public', dest: 'public' },
|
||||||
|
|
||||||
|
// Node-pty module (bundled)
|
||||||
|
{ src: 'node-pty/lib', dest: 'node-pty/lib' },
|
||||||
|
{ src: 'node-pty/src', dest: 'node-pty/src' },
|
||||||
|
{ src: 'node-pty/binding.gyp', dest: 'node-pty/binding.gyp' },
|
||||||
|
{ src: 'node-pty/package.json', dest: 'node-pty/package.json' },
|
||||||
|
{ src: 'node-pty/README.md', dest: 'node-pty/README.md' },
|
||||||
|
|
||||||
|
// Prebuilds
|
||||||
|
{ src: 'prebuilds', dest: 'prebuilds' },
|
||||||
|
|
||||||
|
// Scripts
|
||||||
|
{ src: 'scripts/postinstall-npm.js', dest: 'scripts/postinstall.js' },
|
||||||
|
{ src: 'scripts/node-pty-plugin.js', dest: 'scripts/node-pty-plugin.js' }
|
||||||
|
];
|
||||||
|
|
||||||
|
function copyRecursive(src, dest) {
|
||||||
|
const srcPath = path.join(ROOT_DIR, src);
|
||||||
|
const destPath = path.join(DIST_DIR, dest);
|
||||||
|
|
||||||
|
if (!fs.existsSync(srcPath)) {
|
||||||
|
console.warn(` ⚠️ Source not found: ${src}`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
console.log('✅ node-pty already built');
|
const destDir = path.dirname(destPath);
|
||||||
|
if (!fs.existsSync(destDir)) {
|
||||||
|
fs.mkdirSync(destDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const stats = fs.statSync(srcPath);
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
fs.cpSync(srcPath, destPath, { recursive: true });
|
||||||
|
} else {
|
||||||
|
fs.copyFileSync(srcPath, destPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(` ✓ ${src} → ${dest}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Create package-specific README
|
filesToCopy.forEach(({ src, dest }) => {
|
||||||
console.log('\n4️⃣ Creating npm package README...\n');
|
copyRecursive(src, dest);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Step 4: Copy authenticate-pam module for Linux support (OUR ENHANCEMENT)
|
||||||
|
copyAuthenticatePam();
|
||||||
|
|
||||||
|
// Step 5: Use package.npm.json if available, otherwise create clean package.json
|
||||||
|
console.log('\n4️⃣ Creating package.json for npm...\n');
|
||||||
|
|
||||||
|
const npmPackageJsonPath = path.join(ROOT_DIR, 'package.npm.json');
|
||||||
|
let npmPackageJson;
|
||||||
|
|
||||||
|
if (fs.existsSync(npmPackageJsonPath)) {
|
||||||
|
// Use our enhanced package.npm.json
|
||||||
|
console.log('Using package.npm.json configuration...');
|
||||||
|
npmPackageJson = JSON.parse(fs.readFileSync(npmPackageJsonPath, 'utf8'));
|
||||||
|
|
||||||
|
// Remove prebuild-install dependency (our approach is better)
|
||||||
|
if (npmPackageJson.dependencies && npmPackageJson.dependencies['prebuild-install']) {
|
||||||
|
delete npmPackageJson.dependencies['prebuild-install'];
|
||||||
|
console.log('✅ Removed problematic prebuild-install dependency');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback to creating clean package.json from source
|
||||||
|
console.log('Creating clean package.json from source...');
|
||||||
|
const sourcePackageJson = JSON.parse(
|
||||||
|
fs.readFileSync(path.join(ROOT_DIR, 'package.json'), 'utf8')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Extract only necessary fields for npm package
|
||||||
|
npmPackageJson = {
|
||||||
|
name: sourcePackageJson.name,
|
||||||
|
version: sourcePackageJson.version,
|
||||||
|
description: sourcePackageJson.description,
|
||||||
|
keywords: sourcePackageJson.keywords,
|
||||||
|
author: sourcePackageJson.author,
|
||||||
|
license: sourcePackageJson.license,
|
||||||
|
homepage: sourcePackageJson.homepage,
|
||||||
|
repository: sourcePackageJson.repository,
|
||||||
|
bugs: sourcePackageJson.bugs,
|
||||||
|
|
||||||
|
// Main entry point
|
||||||
|
main: 'lib/cli.js',
|
||||||
|
|
||||||
|
// Bin scripts
|
||||||
|
bin: {
|
||||||
|
vibetunnel: './bin/vibetunnel',
|
||||||
|
vt: './bin/vt'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Only runtime dependencies
|
||||||
|
dependencies: Object.fromEntries(
|
||||||
|
Object.entries(sourcePackageJson.dependencies)
|
||||||
|
.filter(([key]) => !key.includes('node-pty')) // Exclude node-pty, it's bundled
|
||||||
|
),
|
||||||
|
|
||||||
|
// Minimal scripts
|
||||||
|
scripts: {
|
||||||
|
postinstall: 'node scripts/postinstall.js'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Node.js requirements
|
||||||
|
engines: sourcePackageJson.engines,
|
||||||
|
os: sourcePackageJson.os,
|
||||||
|
|
||||||
|
// Files to include (everything in dist-npm)
|
||||||
|
files: [
|
||||||
|
'lib/',
|
||||||
|
'bin/',
|
||||||
|
'public/',
|
||||||
|
'node-pty/',
|
||||||
|
'prebuilds/',
|
||||||
|
'scripts/',
|
||||||
|
'README.md'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(DIST_DIR, 'package.json'),
|
||||||
|
JSON.stringify(npmPackageJson, null, 2) + '\n'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 6: Fix the CLI structure and bin scripts
|
||||||
|
console.log('\n6️⃣ Fixing CLI structure and bin scripts...\n');
|
||||||
|
|
||||||
|
// The dist/vibetunnel-cli was copied to lib/cli.js
|
||||||
|
// We need to rename it and create a wrapper
|
||||||
|
const cliPath = path.join(DIST_DIR, 'lib', 'cli.js');
|
||||||
|
const cliBundlePath = path.join(DIST_DIR, 'lib', 'vibetunnel-cli');
|
||||||
|
|
||||||
|
// Rename the bundle
|
||||||
|
fs.renameSync(cliPath, cliBundlePath);
|
||||||
|
|
||||||
|
// Create a simple wrapper that requires the bundle
|
||||||
|
const cliWrapperContent = `#!/usr/bin/env node
|
||||||
|
require('./vibetunnel-cli');
|
||||||
|
`;
|
||||||
|
|
||||||
|
fs.writeFileSync(cliPath, cliWrapperContent, { mode: 0o755 });
|
||||||
|
|
||||||
|
// Fix bin scripts to point to correct path
|
||||||
|
const binVibetunnelPath = path.join(DIST_DIR, 'bin', 'vibetunnel');
|
||||||
|
const binVibetunnelContent = `#!/usr/bin/env node
|
||||||
|
|
||||||
|
// Start the CLI - it handles all command routing including 'fwd'
|
||||||
|
const { spawn } = require('child_process');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const cliPath = path.join(__dirname, '..', 'lib', 'vibetunnel-cli');
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
const child = spawn('node', [cliPath, ...args], {
|
||||||
|
stdio: 'inherit',
|
||||||
|
env: process.env
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('exit', (code, signal) => {
|
||||||
|
if (signal) {
|
||||||
|
// Process was killed by signal, exit with 128 + signal number convention
|
||||||
|
// Common signals: SIGTERM=15, SIGINT=2, SIGKILL=9
|
||||||
|
const signalExitCode = signal === 'SIGTERM' ? 143 :
|
||||||
|
signal === 'SIGINT' ? 130 :
|
||||||
|
signal === 'SIGKILL' ? 137 : 128;
|
||||||
|
process.exit(signalExitCode);
|
||||||
|
} else {
|
||||||
|
// Normal exit, use the exit code (or 0 if null)
|
||||||
|
process.exit(code ?? 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
fs.writeFileSync(binVibetunnelPath, binVibetunnelContent, { mode: 0o755 });
|
||||||
|
console.log(' ✓ Fixed bin/vibetunnel path');
|
||||||
|
|
||||||
|
// vt script doesn't need fixing - it dynamically finds the binary
|
||||||
|
|
||||||
|
// Step 7: Create README
|
||||||
|
console.log('\n7️⃣ Creating npm README...\n');
|
||||||
|
|
||||||
const readmeContent = `# VibeTunnel CLI
|
const readmeContent = `# VibeTunnel CLI
|
||||||
|
|
||||||
Full-featured terminal sharing server with web interface for macOS and Linux. Windows not yet supported.
|
Full-featured terminal sharing server with web interface for macOS and Linux. Windows not yet supported.
|
||||||
|
|
@ -435,87 +723,85 @@ Full-featured terminal sharing server with web interface for macOS and Linux. Wi
|
||||||
npm install -g vibetunnel
|
npm install -g vibetunnel
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
## Requirements
|
## Quick Start
|
||||||
|
|
||||||
- Node.js >= 20.0.0
|
|
||||||
- macOS or Linux (Windows not yet supported)
|
|
||||||
- Build tools for native modules (Xcode on macOS, build-essential on Linux)
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Start the server
|
|
||||||
|
|
||||||
\`\`\`bash
|
\`\`\`bash
|
||||||
# Start with default settings (port 4020)
|
# Start VibeTunnel server
|
||||||
vibetunnel
|
vibetunnel
|
||||||
|
|
||||||
# Start with custom port
|
# Or use short alias
|
||||||
vibetunnel --port 8080
|
vt
|
||||||
|
|
||||||
# Start without authentication
|
# Custom port and settings
|
||||||
vibetunnel --no-auth
|
vibetunnel --port 4020 --auth
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
Then open http://localhost:4020 in your browser to access the web interface.
|
# Forward mode (connect to remote VibeTunnel)
|
||||||
|
vibetunnel fwd username@hostname
|
||||||
### Use the vt command wrapper
|
|
||||||
|
|
||||||
The \`vt\` command allows you to run commands with TTY forwarding:
|
|
||||||
|
|
||||||
\`\`\`bash
|
|
||||||
# Monitor AI agents with automatic activity tracking
|
|
||||||
vt claude
|
|
||||||
vt claude --dangerously-skip-permissions
|
|
||||||
|
|
||||||
# Run commands with output visible in VibeTunnel
|
|
||||||
vt npm test
|
|
||||||
vt python script.py
|
|
||||||
vt top
|
|
||||||
|
|
||||||
# Launch interactive shell
|
|
||||||
vt --shell
|
|
||||||
vt -i
|
|
||||||
|
|
||||||
# Update session title (inside a session)
|
|
||||||
vt title "My Project"
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
### Forward commands to a session
|
|
||||||
|
|
||||||
\`\`\`bash
|
|
||||||
# Basic usage
|
|
||||||
vibetunnel fwd <session-id> <command> [args...]
|
|
||||||
|
|
||||||
# Examples
|
|
||||||
vibetunnel fwd --session-id abc123 ls -la
|
|
||||||
vibetunnel fwd --session-id abc123 npm test
|
|
||||||
vibetunnel fwd --session-id abc123 python script.py
|
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Web-based terminal interface** - Access terminals from any browser
|
- **Terminal Sharing**: Share your terminal through a web browser
|
||||||
- **Multiple concurrent sessions** - Run multiple terminals simultaneously
|
- **Web Interface**: Access terminals from any device with a browser
|
||||||
- **Real-time synchronization** - See output in real-time
|
- **Session Management**: Create, manage, and switch between multiple terminal sessions
|
||||||
- **TTY forwarding** - Full terminal emulation support
|
- **Authentication**: Built-in authentication system
|
||||||
- **Session management** - Create, list, and manage sessions
|
- **Cross-Platform**: Works on macOS and Linux
|
||||||
- **Cross-platform** - Works on macOS and Linux
|
|
||||||
- **No dependencies** - Just Node.js required
|
|
||||||
|
|
||||||
## Package Contents
|
## Requirements
|
||||||
|
|
||||||
This npm package includes:
|
- **Node.js**: Version 20 or higher
|
||||||
- Full VibeTunnel server with web UI
|
- **Operating System**: macOS or Linux (Windows not yet supported)
|
||||||
- Command-line tools (vibetunnel, vt)
|
- **Build Tools**: For source compilation fallback (make, gcc, python3)
|
||||||
- Native PTY support for terminal emulation
|
|
||||||
- Web interface with xterm.js
|
|
||||||
- Session management and forwarding
|
|
||||||
|
|
||||||
## Platform Support
|
## Platform Support
|
||||||
|
|
||||||
- macOS (Intel and Apple Silicon)
|
### macOS
|
||||||
- Linux (x64 and ARM64)
|
- Intel (x64) and Apple Silicon (arm64)
|
||||||
- Windows: Not yet supported ([#252](https://github.com/amantus-ai/vibetunnel/issues/252))
|
- Requires Xcode Command Line Tools for source compilation
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
- x64 and ARM64 architectures
|
||||||
|
- PAM authentication support
|
||||||
|
- Automatic fallback to source compilation when prebuilds unavailable
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
VibeTunnel can be configured via command line arguments:
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
vibetunnel --help
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Installation Issues
|
||||||
|
|
||||||
|
If you encounter issues during installation:
|
||||||
|
|
||||||
|
1. **Missing Build Tools**: Install build essentials
|
||||||
|
\`\`\`bash
|
||||||
|
# Ubuntu/Debian
|
||||||
|
sudo apt-get install build-essential python3-dev
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
xcode-select --install
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
2. **Permission Issues**: Use sudo for global installation
|
||||||
|
\`\`\`bash
|
||||||
|
sudo npm install -g vibetunnel
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
3. **Node Version**: Ensure Node.js 20+ is installed
|
||||||
|
\`\`\`bash
|
||||||
|
node --version
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Runtime Issues
|
||||||
|
|
||||||
|
- **Server Won't Start**: Check if port is already in use
|
||||||
|
- **Authentication Failed**: Verify system authentication setup
|
||||||
|
- **Terminal Not Responsive**: Check browser console for WebSocket errors
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
|
@ -525,20 +811,21 @@ See the main repository for complete documentation: https://github.com/amantus-a
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const readmePath = path.join(__dirname, '..', 'README.md');
|
fs.writeFileSync(
|
||||||
fs.writeFileSync(readmePath, readmeContent);
|
path.join(DIST_DIR, 'README.md'),
|
||||||
console.log('✅ npm README created');
|
readmeContent
|
||||||
|
);
|
||||||
// Step 5: Clean up test files (keep screencap.js - it's needed)
|
|
||||||
console.log('\n5️⃣ Cleaning up test files...\n');
|
// Step 8: Clean up test files in dist-npm
|
||||||
|
console.log('\n8️⃣ Cleaning up test files...\n');
|
||||||
const testFiles = [
|
const testFiles = [
|
||||||
'public/bundle/test.js',
|
'public/bundle/test.js',
|
||||||
'public/test' // Remove entire test directory
|
'public/test' // Remove entire test directory
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const file of testFiles) {
|
for (const file of testFiles) {
|
||||||
const filePath = path.join(__dirname, '..', file);
|
const filePath = path.join(DIST_DIR, file);
|
||||||
if (fs.existsSync(filePath)) {
|
if (fs.existsSync(filePath)) {
|
||||||
if (fs.statSync(filePath).isDirectory()) {
|
if (fs.statSync(filePath).isDirectory()) {
|
||||||
fs.rmSync(filePath, { recursive: true, force: true });
|
fs.rmSync(filePath, { recursive: true, force: true });
|
||||||
|
|
@ -549,50 +836,40 @@ MIT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 9: Validate package with our comprehensive checks
|
||||||
|
validatePackageHybrid();
|
||||||
|
|
||||||
// Step 6: Show final package info
|
// Step 10: Create npm package
|
||||||
console.log('\n6️⃣ Package summary...\n');
|
console.log('\n9️⃣ Creating npm package...\n');
|
||||||
|
try {
|
||||||
// Calculate total size
|
execSync('npm pack', {
|
||||||
function getDirectorySize(dirPath) {
|
cwd: DIST_DIR,
|
||||||
let totalSize = 0;
|
stdio: 'inherit'
|
||||||
const items = fs.readdirSync(dirPath);
|
});
|
||||||
|
|
||||||
for (const item of items) {
|
// Move the package to root directory
|
||||||
const itemPath = path.join(dirPath, item);
|
const packageFiles = fs.readdirSync(DIST_DIR)
|
||||||
const stats = fs.statSync(itemPath);
|
.filter(f => f.endsWith('.tgz'));
|
||||||
|
|
||||||
if (stats.isFile()) {
|
if (packageFiles.length > 0) {
|
||||||
totalSize += stats.size;
|
const packageFile = packageFiles[0];
|
||||||
} else if (stats.isDirectory()) {
|
fs.renameSync(
|
||||||
totalSize += getDirectorySize(itemPath);
|
path.join(DIST_DIR, packageFile),
|
||||||
}
|
path.join(ROOT_DIR, packageFile)
|
||||||
|
);
|
||||||
|
console.log(`\n✅ Package created: ${packageFile}`);
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
return totalSize;
|
console.error('❌ npm pack failed:', error.message);
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const packageRoot = path.join(__dirname, '..');
|
console.log('\n🎉 Hybrid npm build completed successfully!');
|
||||||
const totalSize = getDirectorySize(packageRoot);
|
|
||||||
const sizeMB = (totalSize / 1024 / 1024).toFixed(1);
|
|
||||||
|
|
||||||
console.log(`📦 Package size: ${sizeMB} MB`);
|
|
||||||
|
|
||||||
if (!currentOnly) {
|
|
||||||
const prebuildsDir = path.join(__dirname, '..', 'prebuilds');
|
|
||||||
if (fs.existsSync(prebuildsDir)) {
|
|
||||||
const prebuildFiles = fs.readdirSync(prebuildsDir).filter(f => f.endsWith('.tar.gz'));
|
|
||||||
console.log(`🔧 Prebuilds: ${prebuildFiles.length} binaries included`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n🎉 npm package build completed successfully!');
|
|
||||||
console.log('\nNext steps:');
|
console.log('\nNext steps:');
|
||||||
console.log(' - Test locally: npm pack');
|
console.log(' - Test locally: npm pack && npm install -g vibetunnel-*.tgz');
|
||||||
|
console.log(' - Test Linux compatibility: Check authenticate-pam and fallback compilation');
|
||||||
console.log(' - Publish: npm publish');
|
console.log(' - Publish: npm publish');
|
||||||
|
|
||||||
// Restore original package.json
|
|
||||||
restorePackageJson();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch(error => {
|
main().catch(error => {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const esbuild = require('esbuild');
|
const esbuild = require('esbuild');
|
||||||
const { prodOptions } = require('./esbuild-config.js');
|
const { prodOptions } = require('./esbuild-config.js');
|
||||||
|
const { nodePtyPlugin } = require('./node-pty-plugin.js');
|
||||||
|
|
||||||
async function build() {
|
async function build() {
|
||||||
console.log('Starting build process...');
|
console.log('Starting build process...');
|
||||||
|
|
@ -76,9 +77,22 @@ async function build() {
|
||||||
target: 'node18',
|
target: 'node18',
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
outfile: 'dist/vibetunnel-cli',
|
outfile: 'dist/vibetunnel-cli',
|
||||||
|
plugins: [nodePtyPlugin],
|
||||||
external: [
|
external: [
|
||||||
'node-pty',
|
// 'node-pty', // Removed - handled by plugin
|
||||||
'authenticate-pam',
|
'authenticate-pam',
|
||||||
|
'compression',
|
||||||
|
'helmet',
|
||||||
|
'express',
|
||||||
|
'ws',
|
||||||
|
'jsonwebtoken',
|
||||||
|
'web-push',
|
||||||
|
'bonjour-service',
|
||||||
|
'signal-exit',
|
||||||
|
'http-proxy-middleware',
|
||||||
|
'multer',
|
||||||
|
'mime-types',
|
||||||
|
'@xterm/headless',
|
||||||
],
|
],
|
||||||
minify: true,
|
minify: true,
|
||||||
sourcemap: false,
|
sourcemap: false,
|
||||||
|
|
|
||||||
73
web/scripts/node-pty-plugin.js
Normal file
73
web/scripts/node-pty-plugin.js
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
/**
|
||||||
|
* ESBuild plugin to handle node-pty resolution for npm packages
|
||||||
|
*/
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const nodePtyPlugin = {
|
||||||
|
name: 'node-pty-resolver',
|
||||||
|
setup(build) {
|
||||||
|
// Resolve node-pty imports to our bundled version
|
||||||
|
build.onResolve({ filter: /^node-pty$/ }, args => {
|
||||||
|
// In development, use the normal node_modules resolution
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For npm builds, resolve to our bundled node-pty
|
||||||
|
return {
|
||||||
|
path: 'node-pty',
|
||||||
|
namespace: 'node-pty-stub'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Provide stub that dynamically loads the bundled node-pty
|
||||||
|
build.onLoad({ filter: /^node-pty$/, namespace: 'node-pty-stub' }, () => {
|
||||||
|
return {
|
||||||
|
contents: `
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Try multiple possible locations for node-pty
|
||||||
|
const possiblePaths = [
|
||||||
|
// When installed via npm
|
||||||
|
path.join(__dirname, '../node-pty'),
|
||||||
|
path.join(__dirname, '../../node-pty'),
|
||||||
|
// During development
|
||||||
|
path.join(__dirname, '../node_modules/node-pty'),
|
||||||
|
// Fallback to regular require
|
||||||
|
'node-pty'
|
||||||
|
];
|
||||||
|
|
||||||
|
let nodePty;
|
||||||
|
let loadError;
|
||||||
|
|
||||||
|
for (const tryPath of possiblePaths) {
|
||||||
|
try {
|
||||||
|
if (tryPath === 'node-pty') {
|
||||||
|
// Try regular require as last resort
|
||||||
|
nodePty = require(tryPath);
|
||||||
|
} else if (fs.existsSync(tryPath)) {
|
||||||
|
// Check if the path exists before trying to load
|
||||||
|
nodePty = require(tryPath);
|
||||||
|
}
|
||||||
|
if (nodePty) break;
|
||||||
|
} catch (err) {
|
||||||
|
loadError = err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nodePty) {
|
||||||
|
throw new Error(\`Failed to load node-pty from any location. Last error: \${loadError?.message}\`);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nodePty;
|
||||||
|
`,
|
||||||
|
loader: 'js'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { nodePtyPlugin };
|
||||||
|
|
@ -2,12 +2,13 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Postinstall script for npm package
|
* Postinstall script for npm package
|
||||||
* Fallback build script when prebuild-install fails
|
* Handles prebuild extraction and fallback compilation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { execSync } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
console.log('Setting up native modules for VibeTunnel...');
|
console.log('Setting up native modules for VibeTunnel...');
|
||||||
|
|
||||||
|
|
@ -20,125 +21,264 @@ if (isDevelopment) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try prebuild-install first for each module
|
// Create node_modules directory if it doesn't exist
|
||||||
const tryPrebuildInstall = (name, dir) => {
|
const nodeModulesDir = path.join(__dirname, '..', 'node_modules');
|
||||||
console.log(`Trying prebuild-install for ${name}...`);
|
if (!fs.existsSync(nodeModulesDir)) {
|
||||||
|
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create symlink for node-pty so it can be required normally
|
||||||
|
const nodePtySource = path.join(__dirname, '..', 'node-pty');
|
||||||
|
const nodePtyTarget = path.join(nodeModulesDir, 'node-pty');
|
||||||
|
if (!fs.existsSync(nodePtyTarget) && fs.existsSync(nodePtySource)) {
|
||||||
try {
|
try {
|
||||||
execSync('prebuild-install', {
|
fs.symlinkSync(nodePtySource, nodePtyTarget, 'dir');
|
||||||
cwd: dir,
|
console.log('✓ Created node-pty symlink in node_modules');
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Warning: Could not create node-pty symlink:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Node ABI version
|
||||||
|
const nodeABI = process.versions.modules;
|
||||||
|
|
||||||
|
// Get platform and architecture
|
||||||
|
const platform = process.platform;
|
||||||
|
const arch = os.arch();
|
||||||
|
|
||||||
|
// Convert architecture names
|
||||||
|
const archMap = {
|
||||||
|
'arm64': 'arm64',
|
||||||
|
'aarch64': 'arm64',
|
||||||
|
'x64': 'x64',
|
||||||
|
'x86_64': 'x64'
|
||||||
|
};
|
||||||
|
const normalizedArch = archMap[arch] || arch;
|
||||||
|
|
||||||
|
console.log(`Platform: ${platform}-${normalizedArch}, Node ABI: ${nodeABI}`);
|
||||||
|
|
||||||
|
// Function to try prebuild-install first
|
||||||
|
const tryPrebuildInstall = (moduleName, moduleDir) => {
|
||||||
|
try {
|
||||||
|
// Check if prebuild-install is available
|
||||||
|
const prebuildInstallPath = require.resolve('prebuild-install/bin.js');
|
||||||
|
console.log(` Attempting to use prebuild-install for ${moduleName}...`);
|
||||||
|
|
||||||
|
execSync(`node "${prebuildInstallPath}"`, {
|
||||||
|
cwd: moduleDir,
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
env: { ...process.env, npm_config_cache: path.join(require('os').homedir(), '.npm') }
|
env: { ...process.env, npm_config_build_from_source: 'false' }
|
||||||
});
|
});
|
||||||
console.log(`✓ ${name} prebuilt binary installed`);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(` No prebuilt binary available for ${name}, will compile from source`);
|
console.log(` prebuild-install failed for ${moduleName}, will try manual extraction`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle both native modules with prebuild-install fallback
|
// Function to manually extract prebuild
|
||||||
|
const extractPrebuild = (name, version, targetDir) => {
|
||||||
|
const prebuildFile = path.join(__dirname, '..', 'prebuilds',
|
||||||
|
`${name}-v${version}-node-v${nodeABI}-${platform}-${normalizedArch}.tar.gz`);
|
||||||
|
|
||||||
|
if (!fs.existsSync(prebuildFile)) {
|
||||||
|
console.log(` No prebuild found for ${name} on this platform`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the parent directory
|
||||||
|
const buildParentDir = path.join(targetDir);
|
||||||
|
fs.mkdirSync(buildParentDir, { recursive: true });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Extract directly into the module directory - the tar already contains build/Release structure
|
||||||
|
execSync(`tar -xzf "${prebuildFile}" -C "${buildParentDir}"`, { stdio: 'inherit' });
|
||||||
|
console.log(`✓ ${name} prebuilt binary extracted`);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(` Failed to extract ${name} prebuild:`, error.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to compile from source
|
||||||
|
const compileFromSource = (moduleName, moduleDir) => {
|
||||||
|
console.log(` Building ${moduleName} from source...`);
|
||||||
|
try {
|
||||||
|
// First check if node-gyp is available
|
||||||
|
try {
|
||||||
|
execSync('node-gyp --version', { stdio: 'pipe' });
|
||||||
|
} catch (e) {
|
||||||
|
console.log(' Installing node-gyp...');
|
||||||
|
execSync('npm install -g node-gyp', { stdio: 'inherit' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// For node-pty, ensure node-addon-api is available
|
||||||
|
if (moduleName === 'node-pty') {
|
||||||
|
const nodeAddonApiPath = path.join(moduleDir, 'node_modules', 'node-addon-api');
|
||||||
|
if (!fs.existsSync(nodeAddonApiPath)) {
|
||||||
|
console.log(` Setting up node-addon-api for ${moduleName}...`);
|
||||||
|
|
||||||
|
// Create node_modules directory
|
||||||
|
const nodeModulesDir = path.join(moduleDir, 'node_modules');
|
||||||
|
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
||||||
|
|
||||||
|
// Try multiple locations for node-addon-api
|
||||||
|
const possiblePaths = [
|
||||||
|
path.join(__dirname, '..', 'node_modules', 'node-addon-api'),
|
||||||
|
path.join(__dirname, '..', '..', 'node_modules', 'node-addon-api'),
|
||||||
|
path.join(__dirname, '..', '..', '..', 'node_modules', 'node-addon-api'),
|
||||||
|
'/usr/local/lib/node_modules/vibetunnel/node_modules/node-addon-api',
|
||||||
|
'/usr/lib/node_modules/vibetunnel/node_modules/node-addon-api'
|
||||||
|
];
|
||||||
|
|
||||||
|
let found = false;
|
||||||
|
for (const sourcePath of possiblePaths) {
|
||||||
|
if (fs.existsSync(sourcePath)) {
|
||||||
|
console.log(` Found node-addon-api at: ${sourcePath}`);
|
||||||
|
console.log(` Copying to ${nodeAddonApiPath}...`);
|
||||||
|
fs.cpSync(sourcePath, nodeAddonApiPath, { recursive: true });
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
// As a fallback, install it
|
||||||
|
console.log(` Installing node-addon-api package...`);
|
||||||
|
try {
|
||||||
|
execSync('npm install node-addon-api@^7.1.0 --no-save --no-package-lock', {
|
||||||
|
cwd: moduleDir,
|
||||||
|
stdio: 'inherit'
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(' Failed to install node-addon-api:', e.message);
|
||||||
|
console.error(' Trying to continue anyway...');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
execSync('node-gyp rebuild', {
|
||||||
|
cwd: moduleDir,
|
||||||
|
stdio: 'inherit'
|
||||||
|
});
|
||||||
|
console.log(`✓ ${moduleName} built successfully`);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(` Failed to build ${moduleName}:`, error.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle both native modules
|
||||||
const modules = [
|
const modules = [
|
||||||
{
|
{
|
||||||
name: 'node-pty',
|
name: 'node-pty',
|
||||||
|
version: '1.0.0',
|
||||||
dir: path.join(__dirname, '..', 'node-pty'),
|
dir: path.join(__dirname, '..', 'node-pty'),
|
||||||
build: path.join(__dirname, '..', 'node-pty', 'build', 'Release', 'pty.node'),
|
build: path.join(__dirname, '..', 'node-pty', 'build', 'Release', 'pty.node'),
|
||||||
essential: true
|
essential: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'authenticate-pam',
|
name: 'authenticate-pam',
|
||||||
|
version: '1.0.5',
|
||||||
dir: path.join(__dirname, '..', 'node_modules', 'authenticate-pam'),
|
dir: path.join(__dirname, '..', 'node_modules', 'authenticate-pam'),
|
||||||
build: path.join(__dirname, '..', 'node_modules', 'authenticate-pam', 'build', 'Release', 'authenticate_pam.node'),
|
build: path.join(__dirname, '..', 'node_modules', 'authenticate-pam', 'build', 'Release', 'authenticate_pam.node'),
|
||||||
essential: false
|
essential: true, // PAM is essential for server environments
|
||||||
|
platforms: ['linux', 'darwin'] // Needed on Linux and macOS
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
let hasErrors = false;
|
let hasErrors = false;
|
||||||
|
|
||||||
for (const module of modules) {
|
for (const module of modules) {
|
||||||
if (!fs.existsSync(module.build)) {
|
console.log(`\nProcessing ${module.name}...`);
|
||||||
// First try prebuild-install
|
|
||||||
const prebuildSuccess = tryPrebuildInstall(module.name, module.dir);
|
// Skip platform-specific modules if not on that platform
|
||||||
|
if (module.platforms && !module.platforms.includes(platform)) {
|
||||||
if (!prebuildSuccess) {
|
console.log(` Skipping ${module.name} (not needed on ${platform})`);
|
||||||
// Fall back to compilation
|
continue;
|
||||||
console.log(`Building ${module.name} from source...`);
|
}
|
||||||
try {
|
|
||||||
execSync('node-gyp rebuild', {
|
// Check if module directory exists
|
||||||
cwd: module.dir,
|
if (!fs.existsSync(module.dir)) {
|
||||||
stdio: 'inherit'
|
console.warn(` Warning: ${module.name} directory not found at ${module.dir}`);
|
||||||
});
|
if (module.essential) {
|
||||||
console.log(`✓ ${module.name} built successfully`);
|
hasErrors = true;
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to build ${module.name}:`, error.message);
|
|
||||||
if (module.essential) {
|
|
||||||
console.error(`${module.name} is required for VibeTunnel to function.`);
|
|
||||||
console.error('You may need to install build tools for your platform:');
|
|
||||||
console.error('- macOS: Install Xcode Command Line Tools');
|
|
||||||
console.error('- Linux: Install build-essential package');
|
|
||||||
hasErrors = true;
|
|
||||||
} else {
|
|
||||||
console.warn(`Warning: ${module.name} build failed. Some features may be limited.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if already built
|
||||||
|
if (fs.existsSync(module.build)) {
|
||||||
console.log(`✓ ${module.name} already available`);
|
console.log(`✓ ${module.name} already available`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try installation methods in order
|
||||||
|
let success = false;
|
||||||
|
|
||||||
|
// Method 1: Try prebuild-install (preferred)
|
||||||
|
success = tryPrebuildInstall(module.name, module.dir);
|
||||||
|
|
||||||
|
// Method 2: Manual prebuild extraction
|
||||||
|
if (!success) {
|
||||||
|
success = extractPrebuild(module.name, module.version, module.dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method 3: Compile from source
|
||||||
|
if (!success && fs.existsSync(path.join(module.dir, 'binding.gyp'))) {
|
||||||
|
success = compileFromSource(module.name, module.dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check final result
|
||||||
|
if (!success) {
|
||||||
|
// Special handling for authenticate-pam on macOS
|
||||||
|
if (module.name === 'authenticate-pam' && process.platform === 'darwin') {
|
||||||
|
console.warn(`⚠️ Warning: ${module.name} installation failed on macOS.`);
|
||||||
|
console.warn(' This is expected - macOS will fall back to environment variable or SSH key authentication.');
|
||||||
|
console.warn(' To enable PAM authentication, install Xcode Command Line Tools and rebuild.');
|
||||||
|
} else if (module.essential) {
|
||||||
|
console.error(`\n❌ ${module.name} is required for VibeTunnel to function.`);
|
||||||
|
console.error('You may need to install build tools for your platform:');
|
||||||
|
console.error('- macOS: Install Xcode Command Line Tools');
|
||||||
|
console.error('- Linux: Install build-essential and libpam0g-dev packages');
|
||||||
|
hasErrors = true;
|
||||||
|
} else {
|
||||||
|
console.warn(`⚠️ Warning: ${module.name} installation failed. Some features may be limited.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install vt symlink/wrapper
|
||||||
|
if (!hasErrors && !isDevelopment) {
|
||||||
|
console.log('\nSetting up vt command...');
|
||||||
|
|
||||||
|
const vtSource = path.join(__dirname, '..', 'bin', 'vt');
|
||||||
|
|
||||||
|
// Check if vt script exists
|
||||||
|
if (!fs.existsSync(vtSource)) {
|
||||||
|
console.warn('⚠️ vt command script not found in package');
|
||||||
|
console.log(' Use "vibetunnel" command instead');
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// Make vt script executable
|
||||||
|
fs.chmodSync(vtSource, '755');
|
||||||
|
console.log('✓ vt command configured');
|
||||||
|
console.log(' Note: The vt command is available through npm/npx');
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('⚠️ Could not configure vt command:', error.message);
|
||||||
|
console.log(' Use "vibetunnel" command instead');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasErrors) {
|
if (hasErrors) {
|
||||||
|
console.error('\n❌ Setup failed with errors');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
} else {
|
||||||
|
console.log('\n✅ VibeTunnel is ready to use');
|
||||||
// Conditionally install vt symlink
|
console.log('Run "vibetunnel --help" for usage information');
|
||||||
if (!isDevelopment) {
|
}
|
||||||
try {
|
|
||||||
// Find npm's global bin directory
|
|
||||||
const npmBinDir = execSync('npm bin -g', { encoding: 'utf8' }).trim();
|
|
||||||
const vtTarget = path.join(npmBinDir, 'vt');
|
|
||||||
const vtSource = path.join(__dirname, '..', 'bin', 'vt');
|
|
||||||
|
|
||||||
// Check if vt already exists
|
|
||||||
if (fs.existsSync(vtTarget)) {
|
|
||||||
// Check if it's already our symlink
|
|
||||||
try {
|
|
||||||
const stats = fs.lstatSync(vtTarget);
|
|
||||||
if (stats.isSymbolicLink()) {
|
|
||||||
const linkTarget = fs.readlinkSync(vtTarget);
|
|
||||||
if (linkTarget.includes('vibetunnel')) {
|
|
||||||
console.log('✓ vt command already installed (VibeTunnel)');
|
|
||||||
} else {
|
|
||||||
console.log('⚠️ vt command already exists (different tool)');
|
|
||||||
console.log(' Use "vibetunnel" command or "npx vt" instead');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('⚠️ vt command already exists (not a symlink)');
|
|
||||||
console.log(' Use "vibetunnel" command instead');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Ignore errors checking the existing file
|
|
||||||
console.log('⚠️ vt command already exists');
|
|
||||||
console.log(' Use "vibetunnel" command instead');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Create the symlink
|
|
||||||
try {
|
|
||||||
fs.symlinkSync(vtSource, vtTarget);
|
|
||||||
// Make it executable
|
|
||||||
fs.chmodSync(vtTarget, '755');
|
|
||||||
console.log('✓ vt command installed successfully');
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('⚠️ Could not install vt command:', error.message);
|
|
||||||
console.log(' Use "vibetunnel" command instead');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// If we can't determine npm bin dir or create symlink, just warn
|
|
||||||
console.warn('⚠️ Could not install vt command:', error.message);
|
|
||||||
console.log(' Use "vibetunnel" command instead');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('✓ VibeTunnel is ready to use');
|
|
||||||
console.log('Run "vibetunnel --help" for usage information');
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "Testing Docker build for Linux x64..."
|
|
||||||
|
|
||||||
# Create the build script
|
|
||||||
cat > docker-build-test.sh << 'EOF'
|
|
||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "Installing build dependencies..."
|
|
||||||
apt-get update && apt-get install -y python3 make g++ git
|
|
||||||
|
|
||||||
echo "Setting up project..."
|
|
||||||
cd /workspace
|
|
||||||
|
|
||||||
# Fix npm permissions issue in Docker
|
|
||||||
mkdir -p ~/.npm
|
|
||||||
chown -R $(id -u):$(id -g) ~/.npm
|
|
||||||
|
|
||||||
# Install pnpm using corepack (more reliable)
|
|
||||||
corepack enable
|
|
||||||
corepack prepare pnpm@latest --activate
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
cd /workspace
|
|
||||||
pnpm install --ignore-scripts --no-frozen-lockfile
|
|
||||||
|
|
||||||
# Go to node-pty directory
|
|
||||||
cd node-pty
|
|
||||||
|
|
||||||
# Install prebuild locally in node-pty
|
|
||||||
pnpm add -D prebuild
|
|
||||||
|
|
||||||
# Build for Node.js 20
|
|
||||||
echo "Building for Node.js 20..."
|
|
||||||
./node_modules/.bin/prebuild --runtime node --target 20.0.0
|
|
||||||
|
|
||||||
# List results
|
|
||||||
echo "Build complete. Prebuilds:"
|
|
||||||
ls -la prebuilds/
|
|
||||||
EOF
|
|
||||||
|
|
||||||
chmod +x docker-build-test.sh
|
|
||||||
|
|
||||||
# Run the test
|
|
||||||
docker run --rm \
|
|
||||||
-v "$(pwd)":/workspace \
|
|
||||||
-w /workspace \
|
|
||||||
--platform linux/amd64 \
|
|
||||||
node:22-bookworm \
|
|
||||||
/workspace/docker-build-test.sh
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
rm docker-build-test.sh
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Script to run Playwright tests with parallel configuration
|
|
||||||
|
|
||||||
echo "Running Playwright tests with parallel configuration..."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Run all tests (parallel and serial)
|
|
||||||
if [ "$1" == "all" ]; then
|
|
||||||
echo "Running all tests (parallel + serial)..."
|
|
||||||
pnpm exec playwright test
|
|
||||||
elif [ "$1" == "parallel" ]; then
|
|
||||||
echo "Running only parallel tests..."
|
|
||||||
pnpm exec playwright test --project=chromium-parallel
|
|
||||||
elif [ "$1" == "serial" ]; then
|
|
||||||
echo "Running only serial tests..."
|
|
||||||
pnpm exec playwright test --project=chromium-serial
|
|
||||||
elif [ "$1" == "debug" ]; then
|
|
||||||
echo "Running tests in debug mode..."
|
|
||||||
pnpm exec playwright test --debug
|
|
||||||
elif [ "$1" == "ui" ]; then
|
|
||||||
echo "Running tests with UI mode..."
|
|
||||||
pnpm exec playwright test --ui
|
|
||||||
else
|
|
||||||
echo "Usage: ./scripts/test-parallel.sh [all|parallel|serial|debug|ui]"
|
|
||||||
echo ""
|
|
||||||
echo "Options:"
|
|
||||||
echo " all - Run all tests (parallel and serial)"
|
|
||||||
echo " parallel - Run only parallel tests"
|
|
||||||
echo " serial - Run only serial tests"
|
|
||||||
echo " debug - Run tests in debug mode"
|
|
||||||
echo " ui - Run tests with Playwright UI"
|
|
||||||
echo ""
|
|
||||||
echo "If no option is provided, this help message is shown."
|
|
||||||
fi
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Clean up existing sessions
|
|
||||||
echo "Cleaning up existing sessions..."
|
|
||||||
rm -rf ~/.vibetunnel/control/*
|
|
||||||
|
|
||||||
# Run Playwright tests
|
|
||||||
echo "Running Playwright tests..."
|
|
||||||
pnpm playwright test "$@"
|
|
||||||
|
|
@ -54,8 +54,16 @@ process.on('unhandledRejection', (reason, promise) => {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Only execute if this is the main module (or in SEA where require.main is undefined)
|
// Only execute if this is the main module (or in SEA/bundled context where require.main is undefined)
|
||||||
if (!module.parent && (require.main === module || require.main === undefined)) {
|
// In bundled builds, both module.parent and require.main are undefined
|
||||||
|
// In npm package context, check if we're the actual CLI entry point
|
||||||
|
const isMainModule =
|
||||||
|
!module.parent &&
|
||||||
|
(require.main === module ||
|
||||||
|
require.main === undefined ||
|
||||||
|
(require.main?.filename?.endsWith('/vibetunnel-cli') ?? false));
|
||||||
|
|
||||||
|
if (isMainModule) {
|
||||||
if (process.argv[2] === 'version') {
|
if (process.argv[2] === 'version') {
|
||||||
console.log(`VibeTunnel Server v${VERSION}`);
|
console.log(`VibeTunnel Server v${VERSION}`);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,13 @@ describe('SessionView', () => {
|
||||||
// Setup fetch mock
|
// Setup fetch mock
|
||||||
fetchMock = setupFetchMock();
|
fetchMock = setupFetchMock();
|
||||||
|
|
||||||
|
// Mock the server status endpoint that's called on component connect
|
||||||
|
fetchMock.mockResponse('/api/server/status', {
|
||||||
|
macAppConnected: false,
|
||||||
|
cloudflareEnabled: false,
|
||||||
|
isDevelopmentServer: false,
|
||||||
|
});
|
||||||
|
|
||||||
// Create component
|
// Create component
|
||||||
element = await fixture<SessionView>(html` <session-view></session-view> `);
|
element = await fixture<SessionView>(html` <session-view></session-view> `);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -559,7 +559,76 @@ export async function createApp(): Promise<AppInstance> {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Serve static files with .html extension handling and caching headers
|
// Serve static files with .html extension handling and caching headers
|
||||||
const publicPath = path.join(process.cwd(), 'public');
|
// In production/bundled mode, use the package directory; in development, use cwd
|
||||||
|
const getPublicPath = () => {
|
||||||
|
// More precise npm package detection:
|
||||||
|
// 1. Check if we're explicitly in an npm package structure
|
||||||
|
// 2. The file should be in node_modules/vibetunnel/lib/
|
||||||
|
// 3. Or check for our specific package markers
|
||||||
|
const isNpmPackage = (() => {
|
||||||
|
// Most reliable: check if we're in node_modules/vibetunnel structure
|
||||||
|
if (__filename.includes(path.join('node_modules', 'vibetunnel', 'lib'))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Windows path variant
|
||||||
|
if (__filename.includes('node_modules\\vibetunnel\\lib')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secondary check: if we're in a lib directory, verify it's actually an npm package
|
||||||
|
// by checking for the existence of package.json in the parent directory
|
||||||
|
if (path.basename(__dirname) === 'lib') {
|
||||||
|
const parentDir = path.dirname(__dirname);
|
||||||
|
const packageJsonPath = path.join(parentDir, 'package.json');
|
||||||
|
try {
|
||||||
|
const packageJson = require(packageJsonPath);
|
||||||
|
// Verify this is actually our package
|
||||||
|
return packageJson.name === 'vibetunnel';
|
||||||
|
} catch {
|
||||||
|
// Not a valid npm package structure
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (process.env.VIBETUNNEL_BUNDLED === 'true' || process.env.BUILD_DATE || isNpmPackage) {
|
||||||
|
// In bundled/production/npm mode, find package root
|
||||||
|
// When bundled, __dirname is /path/to/package/dist, so go up one level
|
||||||
|
// When globally installed, we need to find the package root
|
||||||
|
let packageRoot = __dirname;
|
||||||
|
|
||||||
|
// If we're in the dist directory, go up one level
|
||||||
|
if (path.basename(packageRoot) === 'dist') {
|
||||||
|
packageRoot = path.dirname(packageRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For npm package context, if we're in lib directory, go up one level
|
||||||
|
if (path.basename(packageRoot) === 'lib') {
|
||||||
|
packageRoot = path.dirname(packageRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for package.json to confirm we're in the right place
|
||||||
|
const publicPath = path.join(packageRoot, 'public');
|
||||||
|
const indexPath = path.join(publicPath, 'index.html');
|
||||||
|
|
||||||
|
// If index.html exists, we found the right path
|
||||||
|
if (require('fs').existsSync(indexPath)) {
|
||||||
|
return publicPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: try going up from the bundled CLI location
|
||||||
|
// The bundled CLI might be in node_modules/vibetunnel/dist/
|
||||||
|
return path.join(__dirname, '..', 'public');
|
||||||
|
} else {
|
||||||
|
// In development mode, use current working directory
|
||||||
|
return path.join(process.cwd(), 'public');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const publicPath = getPublicPath();
|
||||||
const isDevelopment = !process.env.BUILD_DATE || process.env.NODE_ENV === 'development';
|
const isDevelopment = !process.env.BUILD_DATE || process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue