From c70330bcfd7b74e93328252cc90ff92c3a9bdf4a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 26 Jun 2025 23:10:05 +0200 Subject: [PATCH] Migrate to Microsoft node-pty v1.1.0-beta34 (#87) --- .github/workflows/README.md | 148 ++++ .github/workflows/claude-code-review.yml | 36 +- .github/workflows/sea-build-test.yml | 234 ++++++ .github/workflows/web-ci.yml | 131 +++ docs/custom-node.md | 13 + .../DockIconManagerTests.swift | 19 +- web/build-custom-node.js | 181 +++-- web/build-native.js | 756 +++++++----------- web/fwd-test.ts | 4 +- web/package.json | 6 +- web/pnpm-lock.yaml | 216 +---- web/scripts/build.js | 2 +- web/scripts/ensure-native-modules.js | 66 ++ web/src/client/components/sidebar-header.ts | 79 -- web/src/server/pty/pty-manager.ts | 4 +- web/src/server/pty/types.ts | 2 +- web/src/test/setup.ts | 2 +- 17 files changed, 1033 insertions(+), 866 deletions(-) create mode 100644 .github/workflows/README.md create mode 100644 .github/workflows/sea-build-test.yml create mode 100644 .github/workflows/web-ci.yml create mode 100755 web/scripts/ensure-native-modules.js diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 00000000..b2f1ec55 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,148 @@ +# VibeTunnel CI/CD Workflows + +This directory contains GitHub Actions workflows for continuous integration and testing. + +## Workflows + +### 1. Web CI (`web-ci.yml`) +Basic CI workflow that runs on every push and PR affecting the web directory. + +**Jobs:** +- **Lint and Type Check**: Runs biome linting and TypeScript type checking +- **Build**: Builds the project and uploads artifacts +- **Test**: Runs the test suite + +**Triggers:** +- Push to `main` or `ms-pty` branches +- Pull requests to `main` +- Only when files in `web/` directory change + +### 2. SEA Build Test (`sea-build-test.yml`) +Advanced workflow for testing Single Executable Application (SEA) builds with custom Node.js. + +**Features:** +- Builds custom Node.js from source with optimizations +- Uses Blacksmith runners for significantly faster builds +- Caches custom Node.js builds for faster subsequent runs +- Tests SEA builds with both system and custom Node.js +- Supports manual triggers with custom Node.js versions + +**Jobs:** +1. **build-custom-node**: + - Runs on `blacksmith-32vcpu-ubuntu-2404-arm` for fast compilation + - Builds minimal Node.js without npm, intl, inspector, etc. + - Uses Blacksmith cache for persistence + - Outputs the custom Node.js path for downstream jobs + +2. **test-sea-build**: + - Runs on `blacksmith-8vcpu-ubuntu-2404-arm` + - Matrix build testing both system and custom Node.js + - Builds SEA executable with node-pty patches + - Performs smoke tests on the generated executable + - Uploads artifacts for inspection + +3. **test-github-runners**: + - Uses standard `ubuntu-latest` runners for comparison + - Helps identify any Blacksmith-specific issues + - Runs only on push events + +### 3. 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. + +**Features:** +- Builds custom Node.js on macOS using self-hosted runners +- Tests integration of SEA executable into macOS app bundle +- Verifies the app launches and contains the correct binaries +- Supports manual triggers with custom Node.js versions + +**Jobs:** +1. **build-custom-node-mac**: + - Runs on self-hosted macOS runner + - Builds custom Node.js for macOS + - Uses GitHub Actions cache (appropriate for self-hosted) + - Outputs node path and size information + +2. **test-xcode-build**: + - Builds SEA executable with custom Node.js + - Copies SEA and native modules to app resources + - Builds VibeTunnel.app using Xcode + - Verifies SEA executable is correctly bundled + - Tests basic app functionality + - Uploads built app as artifact + +## Runner Strategy + +### Blacksmith Runners (Linux) +- **Custom Node.js Build**: `blacksmith-32vcpu-ubuntu-2404-arm` (high CPU for compilation) +- **Other CI Jobs**: `blacksmith-8vcpu-ubuntu-2404-arm` (standard workloads) +- Benefits: Significantly faster builds, better caching, ARM64 architecture + +### Self-Hosted Runners (macOS) +- Used for Xcode builds and macOS-specific testing +- Access to Xcode and macOS-specific tools +- Can test code signing and notarization + +### GitHub Runners (Comparison) +- `ubuntu-latest` used in test job for baseline comparison +- Helps identify Blacksmith-specific issues + +## Caching Strategy + +### Blacksmith Cache +**IMPORTANT**: When using Blacksmith runners, you MUST use `useblacksmith/cache@v1` +- Used for all jobs running on Blacksmith runners +- Provides faster cache operations +- Better persistence than GitHub Actions cache +- Cache key: `custom-node-linux-x64-v{version}-{hash}` + +### GitHub Actions Cache +**Only used for self-hosted runners and standard GitHub runners** +- Self-hosted macOS runners use `actions/cache@v4` +- Standard GitHub runners use `actions/cache@v4` +- Cache key format same as Blacksmith + +### Cache Performance +- Initial custom Node.js build: ~10-15 minutes on 32vCPU +- Cached builds: ~1 minute +- Blacksmith cache restoration: 2-3x faster than GitHub Actions cache + +## Manual Triggers + +The SEA build workflow supports manual triggers via GitHub UI: +```yaml +workflow_dispatch: + inputs: + node_version: + description: 'Node.js version to build' + default: '24.2.0' +``` + +## Local Testing + +To test the SEA build locally: +```bash +# Build custom Node.js +cd web +node build-custom-node.js + +# Build SEA with custom Node.js +node build-native.js --custom-node=".node-builds/node-v24.2.0-minimal/out/Release/node" +``` + +## Optimization Details + +The custom Node.js build removes: +- International support (`--without-intl`) +- npm and corepack (`--without-npm --without-corepack`) +- Inspector/debugging (`--without-inspector`) +- Code cache and snapshots +- Uses `-Os` optimization for size + +This reduces the Node.js binary from ~120MB to ~50-60MB. + +## Future Improvements + +- [ ] Add Windows and macOS to the build matrix +- [ ] Implement release workflow for automated releases +- [ ] Add performance benchmarks +- [ ] Integrate with release signing process \ No newline at end of file diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 956b1a6e..165a43cf 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -148,38 +148,4 @@ jobs: HEAD_BRANCH: ${{ github.event.pull_request.head.ref }} CHANGED_FILES: ${{ github.event.pull_request.changed_files }} ADDITIONS: ${{ github.event.pull_request.additions }} - DELETIONS: ${{ github.event.pull_request.deletions }} - - # Optional: Post a summary comment if Claude's review is very long - - name: Create summary if needed - if: steps.check-review.outputs.skip != 'true' && always() - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - // Wait a bit for Claude's comment to appear - await new Promise(resolve => setTimeout(resolve, 5000)); - - // Find Claude's latest comment - const comments = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - per_page: 10, - sort: 'created', - direction: 'desc' - }); - - const claudeComment = comments.data.find(c => c.user.login === 'claude[bot]'); - - if (claudeComment && claudeComment.body.length > 10000) { - // If the review is very long, add a summary at the top - const summary = `## šŸ“Š Review Summary\n\n**Review length**: ${claudeComment.body.length} characters\n**Commit**: ${context.payload.pull_request.head.sha.substring(0, 7)}\n\n> šŸ’” Tip: Use the table of contents below to navigate this review.\n\n---\n\n`; - - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: claudeComment.id, - body: summary + claudeComment.body - }); - } \ No newline at end of file + DELETIONS: ${{ github.event.pull_request.deletions }} \ No newline at end of file diff --git a/.github/workflows/sea-build-test.yml b/.github/workflows/sea-build-test.yml new file mode 100644 index 00000000..da5a5fa0 --- /dev/null +++ b/.github/workflows/sea-build-test.yml @@ -0,0 +1,234 @@ +name: SEA Build Test + +on: + push: + branches: [ main ] + paths: + - 'web/**' + - '.github/workflows/sea-build-test.yml' + pull_request: + branches: [ main ] + paths: + - 'web/**' + - '.github/workflows/sea-build-test.yml' + workflow_dispatch: + inputs: + node_version: + description: 'Node.js version to build' + required: false + default: '24.2.0' + type: string + +env: + NODE_VERSION: ${{ github.event.inputs.node_version || '24.2.0' }} + CUSTOM_NODE_CACHE_KEY: custom-node-linux-x64 + +jobs: + build-custom-node: + name: Build Custom Node.js + # DISABLED: Custom Node.js compilation temporarily disabled + if: false + runs-on: blacksmith-32vcpu-ubuntu-2404-arm + outputs: + cache-hit: ${{ steps.cache-custom-node.outputs.cache-hit }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup build dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + python3 \ + ninja-build \ + ccache \ + libpam0g-dev + + - name: Cache custom Node.js build (Blacksmith) + id: cache-custom-node + uses: useblacksmith/cache@v1 + with: + path: | + web/.node-builds/node-v${{ env.NODE_VERSION }}-minimal + key: ${{ env.CUSTOM_NODE_CACHE_KEY }}-v${{ env.NODE_VERSION }}-${{ hashFiles('web/build-custom-node.js') }} + restore-keys: | + ${{ env.CUSTOM_NODE_CACHE_KEY }}-v${{ env.NODE_VERSION }}- + + - name: Build custom Node.js + if: steps.cache-custom-node.outputs.cache-hit != 'true' + working-directory: web + run: | + node build-custom-node.js --version=${{ env.NODE_VERSION }} + + test-sea-build: + name: Test SEA Build + # DISABLED: Removed dependency on build-custom-node since it's disabled + # needs: build-custom-node + runs-on: blacksmith-8vcpu-ubuntu-2404-arm + strategy: + matrix: + # DISABLED: Only testing with system Node.js, custom disabled + node-type: [system] # was: [system, custom] + 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 (system) + if: matrix.node-type == 'system' + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + cache-dependency-path: 'web/pnpm-lock.yaml' + + - name: Restore custom Node.js from cache (Blacksmith) + if: matrix.node-type == 'custom' + id: restore-custom-node + uses: useblacksmith/cache@v1 + with: + path: | + web/.node-builds/node-v${{ env.NODE_VERSION }}-minimal + key: ${{ env.CUSTOM_NODE_CACHE_KEY }}-v${{ env.NODE_VERSION }}-${{ hashFiles('web/build-custom-node.js') }} + restore-keys: | + ${{ env.CUSTOM_NODE_CACHE_KEY }}-v${{ env.NODE_VERSION }}- + + - name: Build custom Node.js if not cached + # DISABLED: Custom Node.js compilation temporarily disabled + if: false && matrix.node-type == 'custom' && steps.restore-custom-node.outputs.cache-hit != 'true' + working-directory: web + run: | + echo "Custom Node.js not found in cache, building..." + node build-custom-node.js --version=${{ env.NODE_VERSION }} + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libpam0g-dev + + - name: Install dependencies + working-directory: web + run: | + pnpm install --frozen-lockfile + + - name: Build SEA executable (system Node.js) + if: matrix.node-type == 'system' + working-directory: web + run: | + echo "Building SEA with system Node.js..." + node --version + node build-native.js + + - name: Build SEA executable (custom Node.js) + # DISABLED: Custom Node.js test temporarily disabled + if: false && matrix.node-type == 'custom' + working-directory: web + run: | + # Use auto-discovery since we know the custom Node.js is in .node-builds + echo "Building SEA with custom Node.js (auto-discovery)..." + node build-native.js --custom-node + + - name: Test SEA executable + working-directory: web + run: | + echo "Testing SEA executable..." + ./native/vibetunnel --version || true + + # Basic smoke test - check if it starts + timeout 5s ./native/vibetunnel --help || true + + # Check binary size + ls -lh native/ + size_mb=$(du -m native/vibetunnel | cut -f1) + echo "SEA executable size: ${size_mb} MB" + + # Ensure native modules are present + test -f native/pty.node || (echo "ERROR: pty.node not found" && exit 1) + test -f native/authenticate_pam.node || (echo "ERROR: authenticate_pam.node not found" && exit 1) + # spawn-helper is only needed on macOS + if [[ "$RUNNER_OS" == "macOS" ]]; then + test -f native/spawn-helper || (echo "ERROR: spawn-helper not found" && exit 1) + fi + + - name: Upload SEA artifacts + uses: actions/upload-artifact@v4 + with: + name: sea-build-${{ matrix.node-type }}-linux + path: | + web/native/ + retention-days: 7 + + # Test on standard GitHub runners for comparison + test-github-runners: + name: Test on GitHub Runners + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup build dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + python3 \ + ninja-build \ + ccache \ + libpam0g-dev + + - 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: Cache custom Node.js build + uses: actions/cache@v4 + with: + path: | + web/.node-builds/node-v${{ env.NODE_VERSION }}-minimal + key: blacksmith-${{ env.CUSTOM_NODE_CACHE_KEY }}-v${{ env.NODE_VERSION }}-${{ hashFiles('web/build-custom-node.js') }} + restore-keys: | + blacksmith-${{ env.CUSTOM_NODE_CACHE_KEY }}-v${{ env.NODE_VERSION }}- + + - name: Build and test everything + working-directory: web + run: | + # Install dependencies + pnpm install --frozen-lockfile + + # Build custom Node.js if not cached + # DISABLED: Custom Node.js compilation temporarily disabled + # if [ ! -f ".node-builds/node-v${NODE_VERSION}-minimal/out/Release/node" ]; then + # node build-custom-node.js --version=${NODE_VERSION} + # fi + + # Test both builds + echo "=== Testing with system Node.js ===" + node build-native.js + ./native/vibetunnel --version || true + + # DISABLED: Custom Node.js test temporarily disabled + # echo "=== Testing with custom Node.js ===" + # CUSTOM_NODE=".node-builds/node-v${NODE_VERSION}-minimal/out/Release/node" + # node build-native.js --custom-node="${CUSTOM_NODE}" + # ./native/vibetunnel --version || true + + - name: Compare sizes + working-directory: web + run: | + echo "Binary sizes comparison:" + ls -lh native/vibetunnel + echo "System Node.js based: $(du -h native/vibetunnel | cut -f1)" \ No newline at end of file diff --git a/.github/workflows/web-ci.yml b/.github/workflows/web-ci.yml new file mode 100644 index 00000000..0fe01877 --- /dev/null +++ b/.github/workflows/web-ci.yml @@ -0,0 +1,131 @@ +name: Web CI + +on: + push: + branches: [ main, ms-pty ] + paths: + - 'web/**' + - '.github/workflows/web-ci.yml' + pull_request: + branches: [ main ] + paths: + - 'web/**' + - '.github/workflows/web-ci.yml' + +defaults: + run: + working-directory: web + +jobs: + lint-and-type-check: + name: Lint and Type Check + runs-on: blacksmith-8vcpu-ubuntu-2404-arm + 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 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run linting + run: pnpm run lint + + - name: Run type checking + run: pnpm run typecheck + + - name: Check formatting + run: pnpm run format:check + + build: + name: Build + runs-on: blacksmith-8vcpu-ubuntu-2404-arm + 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 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build project + run: pnpm run build + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: web-build + path: | + web/dist/ + web/public/ + retention-days: 7 + + test: + name: Test + runs-on: blacksmith-8vcpu-ubuntu-2404-arm + 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 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run tests + run: pnpm run test:ci + + - name: Upload coverage + uses: actions/upload-artifact@v4 + if: always() + with: + name: coverage-report + path: web/coverage/ + retention-days: 7 \ No newline at end of file diff --git a/docs/custom-node.md b/docs/custom-node.md index 990a09f5..84e30168 100644 --- a/docs/custom-node.md +++ b/docs/custom-node.md @@ -31,6 +31,19 @@ By building a custom Node.js without these features, we achieve a significantly - Uses the custom Node.js to create a smaller executable - Build output shows version and size comparison +## Prerequisites + +### Required Build Tools +For optimal build performance, the following tools are required: +- **Ninja**: Build system for faster compilation (significantly faster than Make) +- **ccache**: Compiler cache to speed up rebuilds + +#### Installation +- **macOS**: `brew install ninja ccache` +- **Linux**: `apt-get install ninja-build ccache` (or equivalent for your distribution) + +The build script will automatically use these tools if available, falling back to Make if Ninja is not found. + ## Build Automation ### Release Builds diff --git a/mac/VibeTunnelTests/DockIconManagerTests.swift b/mac/VibeTunnelTests/DockIconManagerTests.swift index 3dfab9e3..3f1ca0c2 100644 --- a/mac/VibeTunnelTests/DockIconManagerTests.swift +++ b/mac/VibeTunnelTests/DockIconManagerTests.swift @@ -13,24 +13,7 @@ struct DockIconManagerTests { let instance2 = DockIconManager.shared #expect(instance1 === instance2) } - - @Test("User preference for dock icon") - func userPreferenceForDockIcon() { - // Store current value to restore later - let currentValue = UserDefaults.standard.bool(forKey: "showInDock") - // Test with dock icon enabled - UserDefaults.standard.set(true, forKey: "showInDock") - #expect(UserDefaults.standard.bool(forKey: "showInDock") == true) - - // Test with dock icon disabled - UserDefaults.standard.set(false, forKey: "showInDock") - #expect(UserDefaults.standard.bool(forKey: "showInDock") == false) - - // Restore original value - UserDefaults.standard.set(currentValue, forKey: "showInDock") - } - @Test("Update dock visibility based on windows") @MainActor func updateDockVisibilityBasedOnWindows() { @@ -108,4 +91,4 @@ struct DockIconManagerTests { // Restore UserDefaults.standard.set(originalPref, forKey: "showInDock") } -} \ No newline at end of file +} diff --git a/web/build-custom-node.js b/web/build-custom-node.js index 13e236bf..db37a0e9 100755 --- a/web/build-custom-node.js +++ b/web/build-custom-node.js @@ -3,12 +3,18 @@ /** * Build a custom Node.js binary with reduced size by excluding features we don't need. * - * See custom-node-build-flags.md for detailed documentation and size optimization results. + * This script automatically adapts to CI and local environments. * - * Quick usage: - * node build-custom-node.js # Builds Node.js 24.2.0 (recommended) - * node build-custom-node.js --latest # Latest version - * node build-custom-node.js --version=24.2.0 # Specific version + * Usage: + * node build-custom-node.js # Builds Node.js 24.2.0 (recommended) + * node build-custom-node.js --latest # Latest version + * node build-custom-node.js --version=24.2.0 # Specific version + * NODE_VERSION=24.2.0 node build-custom-node.js # Via environment variable (CI) + * + * In CI environments: + * - Outputs GitHub Actions variables + * - Uses ccache if available + * - Creates build summary files */ const { execSync } = require('child_process'); @@ -16,6 +22,9 @@ const fs = require('fs'); const path = require('path'); const https = require('https'); +// Detect if running in CI +const isCI = process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true'; + // Parse command line arguments const args = process.argv.slice(2); let targetVersion = null; @@ -29,6 +38,13 @@ for (const arg of args) { } } +// Helper for GitHub Actions output +function setOutput(name, value) { + if (process.env.GITHUB_OUTPUT) { + fs.appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${value}\n`); + } +} + // Helper to download files function downloadFile(url, destPath) { return new Promise((resolve, reject) => { @@ -70,7 +86,7 @@ async function getLatestNodeVersion() { } async function buildCustomNode() { - // Determine version to build + // Determine version to build (CLI args take precedence over env vars) let nodeSourceVersion; if (useLatest) { console.log('Fetching latest Node.js version...'); @@ -78,18 +94,25 @@ async function buildCustomNode() { console.log(`Latest Node.js version: ${nodeSourceVersion}`); } else if (targetVersion) { nodeSourceVersion = targetVersion; + } else if (process.env.NODE_VERSION) { + // Support CI environment variable + nodeSourceVersion = process.env.NODE_VERSION; } else { // Default to Node.js 24.2.0 (recommended version) nodeSourceVersion = '24.2.0'; } - console.log(`Building custom Node.js ${nodeSourceVersion} with all feature removals (-Os)...`); + const platform = process.platform; + const arch = process.arch; + + console.log(`Building custom Node.js ${nodeSourceVersion} for ${platform}-${arch}...`); console.log('This will take 10-20 minutes on first run, but will be cached for future builds.'); const nodeSourceUrl = `https://nodejs.org/dist/v${nodeSourceVersion}/node-v${nodeSourceVersion}.tar.gz`; const majorVersion = nodeSourceVersion.split('.')[0]; - const buildDir = path.join(__dirname, '.node-builds'); + // In CI scripts directory, go up one level to find web root + const buildDir = path.join(__dirname, __dirname.endsWith('scripts') ? '..' : '.', '.node-builds'); const versionDir = path.join(buildDir, `node-v${nodeSourceVersion}-minimal`); const markerFile = path.join(versionDir, '.build-complete'); const customNodePath = path.join(versionDir, 'out', 'Release', 'node'); @@ -99,8 +122,16 @@ async function buildCustomNode() { console.log(`Using cached custom Node.js build from ${customNodePath}`); const stats = fs.statSync(customNodePath); console.log(`Cached custom Node.js size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`); - console.log(`\nTo use this custom Node.js with build-native.js:`); - console.log(`node build-native.js --custom-node="${customNodePath}"`); + + if (isCI) { + // Set outputs for GitHub Actions + setOutput('node-path', customNodePath); + setOutput('node-size', stats.size); + setOutput('cache-hit', 'true'); + } else { + console.log(`\nTo use this custom Node.js with build-native.js:`); + console.log(`node build-native.js --custom-node="${customNodePath}"`); + } return customNodePath; } @@ -109,8 +140,8 @@ async function buildCustomNode() { fs.mkdirSync(buildDir, { recursive: true }); } - // Clean up old version directory if exists - if (fs.existsSync(versionDir)) { + // Clean up incomplete builds (check for marker file) + if (fs.existsSync(versionDir) && !fs.existsSync(markerFile)) { console.log('Cleaning up incomplete build...'); fs.rmSync(versionDir, { recursive: true, force: true }); } @@ -131,12 +162,14 @@ async function buildCustomNode() { // Rename to version-specific directory const extractedDir = path.join(buildDir, `node-v${nodeSourceVersion}`); - fs.renameSync(extractedDir, versionDir); + if (fs.existsSync(extractedDir)) { + fs.renameSync(extractedDir, versionDir); + } // Configure and build process.chdir(versionDir); - console.log('Configuring Node.js build (all feature removals, -Os only)...'); + console.log('Configuring Node.js build...'); const configureArgs = [ '--without-intl', // Remove internationalization support '--without-npm', // Don't include npm @@ -144,42 +177,49 @@ async function buildCustomNode() { '--without-inspector', // Remove debugging/profiling features '--without-node-code-cache', // Disable code cache '--without-node-snapshot', // Don't create/use startup snapshot - '--ninja', // Use ninja if available for faster builds ]; + // Check if ninja is available + try { + execSync('which ninja', { stdio: 'ignore' }); + configureArgs.push('--ninja'); + console.log('Using Ninja for faster builds...'); + } catch { + console.log('Ninja not found, using Make...'); + } + + // Enable ccache if available + try { + execSync('which ccache', { stdio: 'ignore' }); + process.env.CC = 'ccache gcc'; + process.env.CXX = 'ccache g++'; + console.log('Using ccache for faster rebuilds...'); + } catch { + console.log('ccache not found, proceeding without it...'); + } + // Use -Os optimization which is proven to be safe process.env.CFLAGS = '-Os'; process.env.CXXFLAGS = '-Os'; // Clear LDFLAGS to avoid any issues delete process.env.LDFLAGS; - // Check if ninja is available, install if not - try { - execSync('which ninja', { stdio: 'ignore' }); - console.log('Using Ninja for faster builds...'); - } catch { - console.log('Ninja not found, installing via Homebrew...'); - try { - execSync('brew install ninja', { stdio: 'inherit' }); - console.log('Ninja installed successfully'); - } catch (brewError) { - console.log('Failed to install ninja, falling back to Make...'); - // Remove --ninja if not available - configureArgs.pop(); - } - } - execSync(`./configure ${configureArgs.join(' ')}`, { stdio: 'inherit' }); console.log('Building Node.js (this will take a while)...'); const cores = require('os').cpus().length; + const startTime = Date.now(); // Check if we're using ninja or make - const buildSystem = configureArgs.includes('--ninja') ? 'ninja' : 'make'; - if (buildSystem === 'ninja') { - execSync(`ninja -C out/Release -j ${cores}`, { stdio: 'inherit' }); - } else { - execSync(`make -j${cores}`, { stdio: 'inherit' }); + const buildCmd = configureArgs.includes('--ninja') + ? `ninja -C out/Release -j ${cores}` + : `make -j${cores}`; + + execSync(buildCmd, { stdio: 'inherit' }); + + const buildTime = Math.round((Date.now() - startTime) / 1000); + if (isCI) { + console.log(`Build completed in ${buildTime} seconds`); } // Verify the build @@ -187,9 +227,14 @@ async function buildCustomNode() { throw new Error('Node.js build failed - binary not found'); } - // Strip the binary + // Test the binary + const version = execSync(`"${customNodePath}" --version`, { encoding: 'utf8' }).trim(); + console.log(`Built Node.js version: ${version}`); + + // Strip the binary (different command for Linux vs macOS) console.log('Stripping Node.js binary...'); - execSync(`strip -S "${customNodePath}"`, { stdio: 'inherit' }); + const stripCmd = platform === 'darwin' ? 'strip -S' : 'strip -s'; + execSync(`${stripCmd} "${customNodePath}"`, { stdio: 'inherit' }); // Check final size const stats = fs.statSync(customNodePath); @@ -206,31 +251,71 @@ async function buildCustomNode() { } // Mark build as complete - fs.writeFileSync(markerFile, JSON.stringify({ + const buildInfo = { version: nodeSourceVersion, buildDate: new Date().toISOString(), size: stats.size, - configureArgs: configureArgs - }, null, 2)); + platform: platform, + arch: arch, + configureArgs: configureArgs, + buildTime: buildTime + }; + + fs.writeFileSync(markerFile, JSON.stringify(buildInfo, null, 2)); + + // Create a summary file + const summaryPath = path.join(versionDir, 'build-summary.txt'); + const summary = ` +Custom Node.js Build Summary +============================ +Version: ${nodeSourceVersion} +Platform: ${platform}-${arch} +Size: ${(stats.size / 1024 / 1024).toFixed(2)} MB +Build Time: ${buildTime} seconds +Configure Args: ${configureArgs.join(' ')} +Path: ${customNodePath} +`; + fs.writeFileSync(summaryPath, summary); // Change back to original directory process.chdir(originalCwd); + if (isCI) { + // Set outputs for GitHub Actions + setOutput('node-path', customNodePath); + setOutput('node-size', stats.size); + setOutput('node-version', version); + setOutput('build-time', buildTime); + setOutput('cache-hit', 'false'); + } + + // Output for both CI and local use console.log(`\nCustom Node.js location: ${customNodePath}`); - console.log(`\nTo use this custom Node.js with build-native.js:`); + console.log(`To use this custom Node.js with build-native.js:`); console.log(`node build-native.js --custom-node="${customNodePath}"`); return customNodePath; } catch (error) { process.chdir(originalCwd); - console.error('Failed to build custom Node.js:', error.message); + console.error('Failed to build custom Node.js:', error.message || error); + + // Set error output for CI + if (isCI) { + setOutput('build-error', error.message || 'Unknown error'); + } + process.exit(1); } } -// Run the build -buildCustomNode().catch(err => { - console.error('Build failed:', err); - process.exit(1); -}); \ No newline at end of file +// Run the build if called directly +if (require.main === module) { + buildCustomNode().catch(err => { + console.error('Build failed:', err); + process.exit(1); + }); +} + +// Export for use as a module +module.exports = { buildCustomNode }; \ No newline at end of file diff --git a/web/build-native.js b/web/build-native.js index b02a7153..b6e88fe7 100755 --- a/web/build-native.js +++ b/web/build-native.js @@ -13,51 +13,14 @@ * - `pty.node` - Native binding for terminal emulation * - `spawn-helper` - Helper binary for spawning processes (Unix only) * - * ## How it works - * - * 1. **Patches node-pty** to work with SEA's limitations: - * - SEA's require() can only load built-in Node.js modules, not external files - * - We patch node-pty to use `process.dlopen()` instead of `require()` for native modules - * - All file lookups are changed to look next to the executable, not in node_modules - * - * 2. **Bundles TypeScript** using esbuild: - * - Compiles and bundles all TypeScript/JavaScript into a single file - * - Includes inline sourcemaps for better debugging - * - Source map support can be enabled with --sourcemap flag - * - * 3. **Creates SEA blob**: - * - Uses Node.js's experimental SEA config to generate a blob from the bundle - * - The blob contains all the JavaScript code and can be injected into a Node binary - * - * 4. **Injects into Node.js binary**: - * - Copies the Node.js executable and injects the SEA blob using postject - * - Signs the binary on macOS to avoid security warnings - * - * ## Portability - * The resulting executable is fully portable: - * - No absolute paths are embedded - * - Native modules are loaded relative to the executable location - * - Can be moved to any directory or machine with the same OS/architecture - * * ## Usage * ```bash * node build-native.js # Build with system Node.js * node build-native.js --sourcemap # Build with inline sourcemaps - * node build-native.js --custom-node=/path/to/node # Use custom Node.js binary - * - * # Build custom Node.js first: - * node build-custom-node.js # Build minimal Node.js for current version - * node build-custom-node.js --version=24.2.0 # Build specific version + * node build-native.js --custom-node # Auto-discover custom Node.js (uses most recent) + * node build-native.js --custom-node=/path/to/node # Use specific custom Node.js binary + * node build-native.js --custom-node /path/to/node # Alternative syntax * ``` - * - * ## Requirements - * - Node.js 20+ (for SEA support) - * - postject (installed automatically if needed) - * - * ## Known Limitations - * - The SEA warning about require() limitations is expected and harmless - * - Native modules must be distributed alongside the executable - * - Cross-platform builds are not supported (build on the target platform) */ const { execSync } = require('child_process'); @@ -74,31 +37,12 @@ for (let i = 0; i < process.argv.length; i++) { if (arg.startsWith('--custom-node=')) { customNodePath = arg.split('=')[1]; } else if (arg === '--custom-node') { - // Check if next argument is a path if (i + 1 < process.argv.length && !process.argv[i + 1].startsWith('--')) { + // Next argument is the path customNodePath = process.argv[i + 1]; } else { - // No path provided, search for custom Node.js build - console.log('Searching for custom Node.js build...'); - const customBuildsDir = path.join(__dirname, '.node-builds'); - if (fs.existsSync(customBuildsDir)) { - const dirs = fs.readdirSync(customBuildsDir) - .filter(dir => dir.startsWith('node-v') && dir.endsWith('-minimal')) - .map(dir => ({ - name: dir, - path: path.join(customBuildsDir, dir, 'out/Release/node'), - mtime: fs.statSync(path.join(customBuildsDir, dir)).mtime - })) - .filter(item => fs.existsSync(item.path)) - .sort((a, b) => b.mtime - a.mtime); // Sort by modification time, newest first - - if (dirs.length > 0) { - customNodePath = dirs[0].path; - console.log(`Found custom Node.js at: ${customNodePath}`); - } else { - console.log('No custom Node.js builds found in .node-builds/'); - } - } + // No path provided, use auto-discovery + customNodePath = 'auto'; } } } @@ -116,340 +60,6 @@ if (nodeVersion < 20) { process.exit(1); } -function patchNodePty() { - console.log('Preparing node-pty for SEA build...'); - - // Always reinstall to ensure clean state - console.log('Reinstalling node-pty to ensure clean state...'); - execSync('rm -rf node_modules/@homebridge/node-pty-prebuilt-multiarch', { stdio: 'inherit' }); - - // Suppress npm warnings during installation - execSync('NODE_NO_WARNINGS=1 pnpm install @homebridge/node-pty-prebuilt-multiarch --silent', { - stdio: 'inherit', - env: { - ...process.env, - NODE_NO_WARNINGS: '1', - npm_config_loglevel: 'error' - } - }); - - // Also ensure authenticate-pam is installed - console.log('Ensuring authenticate-pam is installed...'); - execSync('pnpm install authenticate-pam --silent 2>/dev/null || pnpm install authenticate-pam --silent', { - stdio: ['inherit', 'inherit', 'pipe'], - shell: true - }); - - // If using custom Node.js, rebuild native modules - if (customNodePath) { - console.log('Custom Node.js detected - rebuilding native modules...'); - - // Get versions - const customVersion = execSync(`"${customNodePath}" --version`, { encoding: 'utf8' }).trim(); - const systemVersion = process.version; - - console.log(`Custom Node.js: ${customVersion}`); - console.log(`System Node.js: ${systemVersion}`); - - // Rebuild node-pty with the custom Node using pnpm rebuild - console.log('Rebuilding @homebridge/node-pty-prebuilt-multiarch with custom Node.js...'); - - try { - // Use system Node to run pnpm, but rebuild for custom Node version - // The key is to use system Node.js to run pnpm (which needs regex support), - // but tell node-gyp to build against the custom Node.js headers - console.log('Using system Node.js to run pnpm for compatibility...'); - - // First rebuild node-pty which is critical - execSync(`pnpm rebuild @homebridge/node-pty-prebuilt-multiarch`, { - stdio: 'inherit', - env: { - ...process.env, - npm_config_runtime: 'node', - npm_config_target: customVersion.substring(1), // Remove 'v' prefix - npm_config_arch: process.arch, - npm_config_target_arch: process.arch, - npm_config_disturl: 'https://nodejs.org/dist', - npm_config_build_from_source: 'true', - CXXFLAGS: '-std=c++20 -stdlib=libc++ -mmacosx-version-min=14.0', - MACOSX_DEPLOYMENT_TARGET: '14.0' - } - }); - console.log('node-pty rebuilt successfully'); - - // Rebuild authenticate-pam (required for authentication) - console.log('Rebuilding authenticate-pam...'); - - // Create a wrapper script to filter warnings - const wrapperScript = `#!/bin/bash -# Filter out specific warnings while preserving errors -pnpm rebuild authenticate-pam "$@" 2>&1 | grep -v "cast from 'typename" | grep -v "converts to incompatible function type" | grep -v "expanded from macro" | grep -v "~~~" | grep -v "In file included from" | grep -v "warnings generated" | grep -v "In instantiation of" | grep -v "requested here" || true -`; - fs.writeFileSync('build-wrapper.sh', wrapperScript, { mode: 0o755 }); - - try { - execSync(`./build-wrapper.sh`, { - stdio: 'inherit', - env: { - ...process.env, - npm_config_runtime: 'node', - npm_config_target: customVersion.substring(1), - npm_config_arch: process.arch, - npm_config_target_arch: process.arch, - npm_config_disturl: 'https://nodejs.org/dist', - npm_config_build_from_source: 'true', - CXXFLAGS: '-std=c++20 -stdlib=libc++ -mmacosx-version-min=14.0 -Wno-cast-function-type -Wno-incompatible-function-pointer-types', - MACOSX_DEPLOYMENT_TARGET: '14.0' - } - }); - console.log('authenticate-pam rebuilt successfully'); - } finally { - // Clean up wrapper script - if (fs.existsSync('build-wrapper.sh')) { - fs.unlinkSync('build-wrapper.sh'); - } - } - - console.log('Native modules rebuilt successfully with custom Node.js'); - } catch (error) { - console.error('Failed to rebuild native module:', error.message); - console.error('Trying alternative rebuild method...'); - - // Alternative: Force reinstall and rebuild - try { - console.log('Forcing reinstall and rebuild...'); - execSync(`rm -rf node_modules/@homebridge/node-pty-prebuilt-multiarch`, { stdio: 'inherit' }); - execSync(`rm -rf node_modules/authenticate-pam`, { stdio: 'inherit' }); - - // First install the packages - execSync(`pnpm install @homebridge/node-pty-prebuilt-multiarch authenticate-pam --force`, { stdio: 'inherit' }); - - // Then rebuild them with custom Node settings - // Create a wrapper script to filter warnings - const rebuildWrapperScript = `#!/bin/bash -# Filter out specific warnings while preserving errors -pnpm rebuild @homebridge/node-pty-prebuilt-multiarch authenticate-pam "$@" 2>&1 | grep -v "cast from 'typename" | grep -v "converts to incompatible function type" | grep -v "expanded from macro" | grep -v "~~~" | grep -v "In file included from" | grep -v "warnings generated" | grep -v "In instantiation of" | grep -v "requested here" || true -`; - fs.writeFileSync('rebuild-wrapper.sh', rebuildWrapperScript, { mode: 0o755 }); - - try { - execSync(`./rebuild-wrapper.sh`, { - stdio: 'inherit', - env: { - ...process.env, - npm_config_runtime: 'node', - npm_config_target: customVersion.substring(1), - npm_config_arch: process.arch, - npm_config_target_arch: process.arch, - npm_config_disturl: 'https://nodejs.org/dist', - CXXFLAGS: '-std=c++20 -stdlib=libc++ -mmacosx-version-min=14.0 -Wno-cast-function-type -Wno-incompatible-function-pointer-types', - MACOSX_DEPLOYMENT_TARGET: '14.0' - } - }); - } finally { - // Clean up wrapper script - if (fs.existsSync('rebuild-wrapper.sh')) { - fs.unlinkSync('rebuild-wrapper.sh'); - } - } - console.log('Native module rebuilt from source successfully'); - } catch (error2) { - console.error('Alternative rebuild also failed:', error2.message); - process.exit(1); - } - } - } - - console.log('Patching node-pty for SEA build...'); - - // Marker to detect if files have been patched - const PATCH_MARKER = '/* VIBETUNNEL_SEA_PATCHED */'; - - // Helper function to check if file is already patched - function isFilePatched(filePath) { - if (!fs.existsSync(filePath)) return false; - const content = fs.readFileSync(filePath, 'utf8'); - return content.includes(PATCH_MARKER); - } - - // Patch prebuild-loader.js to use process.dlopen instead of require - const prebuildLoaderFile = path.join(__dirname, 'node_modules/@homebridge/node-pty-prebuilt-multiarch/lib/prebuild-loader.js'); - const prebuildLoaderContent = `"use strict"; -${PATCH_MARKER} -Object.defineProperty(exports, "__esModule", { value: true }); -var path = require("path"); -var fs = require("fs"); - -// Custom loader for SEA that uses process.dlopen -var pty; - -// Helper function to load native module using dlopen -function loadNativeModule(modulePath) { - const module = { exports: {} }; - process.dlopen(module, modulePath); - return module.exports; -} - -// Determine the path to pty.node -function getPtyPath() { - const execDir = path.dirname(process.execPath); - // Look for pty.node next to the executable first - const ptyPath = path.join(execDir, 'pty.node'); - - if (fs.existsSync(ptyPath)) { - return ptyPath; - } - - // If not found, throw error with helpful message - throw new Error('Could not find pty.node next to executable at: ' + ptyPath); -} - -try { - const ptyPath = getPtyPath(); - - // Set spawn-helper path for Unix systems - if (process.platform !== 'win32') { - const execDir = path.dirname(process.execPath); - const spawnHelperPath = path.join(execDir, 'spawn-helper'); - if (fs.existsSync(spawnHelperPath)) { - process.env.NODE_PTY_SPAWN_HELPER_PATH = spawnHelperPath; - } - } - - pty = loadNativeModule(ptyPath); -} catch (error) { - console.error('Failed to load pty.node:', error); - throw error; -} - -exports.default = pty; -//# sourceMappingURL=prebuild-loader.js.map`; - - if (isFilePatched(prebuildLoaderFile)) { - console.log('prebuild-loader.js is already patched, skipping...'); - } else { - fs.writeFileSync(prebuildLoaderFile, prebuildLoaderContent.trimEnd() + '\n'); - console.log('Patched prebuild-loader.js'); - } - - // Also patch windowsPtyAgent.js if it exists - const windowsPtyAgentFile = path.join(__dirname, 'node_modules/@homebridge/node-pty-prebuilt-multiarch/lib/windowsPtyAgent.js'); - if (fs.existsSync(windowsPtyAgentFile)) { - if (isFilePatched(windowsPtyAgentFile)) { - console.log('windowsPtyAgent.js is already patched, skipping...'); - } else { - let content = fs.readFileSync(windowsPtyAgentFile, 'utf8'); - // Add patch marker at the beginning - content = `${PATCH_MARKER}\n` + content; - // Replace direct require of .node files with our loader - content = content.replace( - /require\(['"]\.\.\/build\/Release\/pty\.node['"]\)/g, - "require('./prebuild-loader').default" - ); - fs.writeFileSync(windowsPtyAgentFile, content.trimEnd() + '\n'); - console.log('Patched windowsPtyAgent.js'); - } - } - - // Patch index.js exports.native line - const indexFile = path.join(__dirname, 'node_modules/@homebridge/node-pty-prebuilt-multiarch/lib/index.js'); - if (fs.existsSync(indexFile)) { - if (isFilePatched(indexFile)) { - console.log('index.js is already patched, skipping...'); - } else { - let content = fs.readFileSync(indexFile, 'utf8'); - // Add patch marker at the beginning - content = `${PATCH_MARKER}\n` + content; - // Replace the exports.native line that directly requires .node - content = content.replace( - /exports\.native = \(process\.platform !== 'win32' \? require\(prebuild_file_path_1\.ptyPath \|\| '\.\.\/build\/Release\/pty\.node'\) : null\);/, - "exports.native = (process.platform !== 'win32' ? require('./prebuild-loader').default : null);" - ); - fs.writeFileSync(indexFile, content.trimEnd() + '\n'); - console.log('Patched index.js'); - } - } - - // Patch unixTerminal.js to fix spawn-helper path resolution - const unixTerminalFile = path.join(__dirname, 'node_modules/@homebridge/node-pty-prebuilt-multiarch/lib/unixTerminal.js'); - if (fs.existsSync(unixTerminalFile)) { - if (isFilePatched(unixTerminalFile)) { - console.log('unixTerminal.js is already patched, skipping...'); - } else { - let content = fs.readFileSync(unixTerminalFile, 'utf8'); - // Add patch marker at the beginning - content = `${PATCH_MARKER}\n` + content; - - // Replace the helperPath resolution logic - const helperPathPatch = `var helperPath; -// For SEA, use spawn-helper from environment or next to executable -if (process.env.NODE_PTY_SPAWN_HELPER_PATH) { - helperPath = process.env.NODE_PTY_SPAWN_HELPER_PATH; -} else { - // In SEA context, look next to the executable - const execDir = path.dirname(process.execPath); - const spawnHelperPath = path.join(execDir, 'spawn-helper'); - if (require('fs').existsSync(spawnHelperPath)) { - helperPath = spawnHelperPath; - } else { - // Fallback to original logic - helperPath = '../build/Release/spawn-helper'; - helperPath = path.resolve(__dirname, helperPath); - helperPath = helperPath.replace('app.asar', 'app.asar.unpacked'); - helperPath = helperPath.replace('node_modules.asar', 'node_modules.asar.unpacked'); - } -}`; - - // Find and replace the helperPath section - content = content.replace( - /var helperPath;[\s\S]*?helperPath = helperPath\.replace\('node_modules\.asar', 'node_modules\.asar\.unpacked'\);/m, - helperPathPatch - ); - - fs.writeFileSync(unixTerminalFile, content.trimEnd() + '\n'); - console.log('Patched unixTerminal.js'); - } - } - - console.log('node-pty patching complete.'); -} - -// Function to clean patches from node-pty -function cleanPatches() { - console.log('Cleaning patches from node-pty...'); - - const filesToClean = [ - 'node_modules/@homebridge/node-pty-prebuilt-multiarch/lib/prebuild-loader.js', - 'node_modules/@homebridge/node-pty-prebuilt-multiarch/lib/windowsPtyAgent.js', - 'node_modules/@homebridge/node-pty-prebuilt-multiarch/lib/index.js', - 'node_modules/@homebridge/node-pty-prebuilt-multiarch/lib/unixTerminal.js' - ]; - - filesToClean.forEach(file => { - const filePath = path.join(__dirname, file); - if (fs.existsSync(filePath)) { - try { - fs.unlinkSync(filePath); - console.log(`Removed patched file: ${file}`); - } catch (err) { - console.error(`Failed to remove ${file}:`, err.message); - } - } - }); - - // Run pnpm install to restore original files - console.log('Running pnpm install to restore original files...'); - try { - execSync('pnpm install @homebridge/node-pty-prebuilt-multiarch --force', { - cwd: __dirname, - stdio: 'inherit' - }); - console.log('Original files restored.'); - } catch (err) { - console.error('Failed to restore original files:', err.message); - } -} - // Cleanup function function cleanup() { if (fs.existsSync('build') && !process.argv.includes('--keep-build')) { @@ -469,34 +79,173 @@ process.on('SIGTERM', () => { process.exit(1); }); +function applyMinimalPatches() { + console.log('Applying minimal SEA patches to node-pty...'); + + // Create sea-loader.js + const seaLoaderPath = path.join(__dirname, 'node_modules/node-pty/lib/sea-loader.js'); + if (!fs.existsSync(seaLoaderPath)) { + const seaLoaderContent = `"use strict"; +/* VIBETUNNEL_SEA_LOADER */ +Object.defineProperty(exports, "__esModule", { value: true }); +var path = require("path"); +var fs = require("fs"); + +// Custom loader for SEA that uses process.dlopen +var pty; + +// Helper function to load native module using dlopen +function loadNativeModule(modulePath) { + const module = { exports: {} }; + process.dlopen(module, modulePath); + return module.exports; +} + +// Determine the path to pty.node +function getPtyPath() { + const execDir = path.dirname(process.execPath); + // Look for pty.node next to the executable first + const ptyPath = path.join(execDir, 'pty.node'); + + if (fs.existsSync(ptyPath)) { + // Add path validation for security + const resolvedPath = path.resolve(ptyPath); + const resolvedExecDir = path.resolve(execDir); + if (!resolvedPath.startsWith(resolvedExecDir)) { + throw new Error('Invalid pty.node path detected'); + } + return ptyPath; + } + + // If not found, throw error with helpful message + throw new Error('Could not find pty.node next to executable at: ' + ptyPath); +} + +try { + const ptyPath = getPtyPath(); + + // Set spawn-helper path for macOS only + // Linux uses forkpty() directly and doesn't need spawn-helper + if (process.platform === 'darwin') { + const execDir = path.dirname(process.execPath); + const spawnHelperPath = path.join(execDir, 'spawn-helper'); + if (fs.existsSync(spawnHelperPath)) { + process.env.NODE_PTY_SPAWN_HELPER_PATH = spawnHelperPath; + } + } + + pty = loadNativeModule(ptyPath); +} catch (error) { + console.error('Failed to load pty.node:', error); + throw error; +} + +exports.default = pty; +`; + fs.writeFileSync(seaLoaderPath, seaLoaderContent); + } + + // Patch index.js + const indexPath = path.join(__dirname, 'node_modules/node-pty/lib/index.js'); + if (fs.existsSync(indexPath)) { + let content = fs.readFileSync(indexPath, 'utf8'); + if (!content.includes('VIBETUNNEL_SEA')) { + content = content.replace( + "exports.native = (process.platform !== 'win32' ? require('../build/Release/pty.node') : null);", + "exports.native = (process.platform !== 'win32' ? (process.env.VIBETUNNEL_SEA ? require('./sea-loader').default : require('../build/Release/pty.node')) : null);" + ); + fs.writeFileSync(indexPath, content); + } + } + + // Patch unixTerminal.js + const unixPath = path.join(__dirname, 'node_modules/node-pty/lib/unixTerminal.js'); + if (fs.existsSync(unixPath)) { + let content = fs.readFileSync(unixPath, 'utf8'); + if (!content.includes('VIBETUNNEL_SEA')) { + // Find and replace the pty loading section + const startMarker = 'var pty;\nvar helperPath;'; + const endMarker = 'var DEFAULT_FILE = \'sh\';'; + const startIdx = content.indexOf(startMarker); + const endIdx = content.indexOf(endMarker); + + if (startIdx !== -1 && endIdx !== -1) { + const newSection = `var pty; +var helperPath; +// For SEA, check environment variables +if (process.env.VIBETUNNEL_SEA) { + pty = require('./sea-loader').default; + // In SEA context, look for spawn-helper on macOS only (Linux doesn't use it) + if (process.platform === 'darwin') { + const execDir = path.dirname(process.execPath); + const spawnHelperPath = path.join(execDir, 'spawn-helper'); + if (require('fs').existsSync(spawnHelperPath)) { + helperPath = spawnHelperPath; + } else if (process.env.NODE_PTY_SPAWN_HELPER_PATH) { + helperPath = process.env.NODE_PTY_SPAWN_HELPER_PATH; + } + } + // On Linux, helperPath remains undefined which is fine +} else { + // Original loading logic + try { + pty = require('../build/Release/pty.node'); + helperPath = '../build/Release/spawn-helper'; + } + catch (outerError) { + try { + pty = require('../build/Debug/pty.node'); + helperPath = '../build/Debug/spawn-helper'; + } + catch (innerError) { + console.error('innerError', innerError); + // Re-throw the exception from the Release require if the Debug require fails as well + throw outerError; + } + } + helperPath = path.resolve(__dirname, helperPath); + helperPath = helperPath.replace('app.asar', 'app.asar.unpacked'); + helperPath = helperPath.replace('node_modules.asar', 'node_modules.asar.unpacked'); +} +`; + content = content.substring(0, startIdx) + newSection + content.substring(endIdx); + fs.writeFileSync(unixPath, content); + } + } + } + + console.log('SEA patches applied successfully'); +} + async function main() { try { - // Set up environment to suppress warnings - process.env.NODE_NO_WARNINGS = '1'; - process.env.npm_config_loglevel = 'error'; + // Apply minimal patches to node-pty + applyMinimalPatches(); - // Handle command line arguments - if (process.argv.includes('--help')) { - console.log('VibeTunnel Native Build Script\n'); - console.log('Usage: node build-native.js [options]\n'); - console.log('Options:'); - console.log(' --help Show this help message'); - console.log(' --clean-patches Remove all patches from node-pty and restore original files'); - console.log(' --force-patch Force re-patching even if files are already patched'); - console.log(' --keep-build Keep the build directory after completion'); - console.log(' --node Use a custom Node.js binary for SEA\n'); - process.exit(0); + // Ensure native modules are built (in case postinstall didn't run) + const nativePtyDir = 'node_modules/node-pty/build/Release'; + const nativeAuthDir = 'node_modules/authenticate-pam/build/Release'; + + if (!fs.existsSync(nativePtyDir)) { + console.log('Building node-pty native module...'); + // Find the actual node-pty path (could be in .pnpm directory) + const nodePtyPath = require.resolve('node-pty/package.json'); + const nodePtyDir = path.dirname(nodePtyPath); + console.log(`Found node-pty at: ${nodePtyDir}`); + + // Build node-pty using node-gyp directly to avoid TypeScript compilation + execSync(`cd "${nodePtyDir}" && npx node-gyp rebuild`, { + stdio: 'inherit', + shell: true + }); } - if (process.argv.includes('--clean-patches')) { - cleanPatches(); - process.exit(0); - } - - const forcePatching = process.argv.includes('--force-patch'); - if (forcePatching) { - console.log('Force patching enabled - will re-patch even if already patched'); - cleanPatches(); + if (!fs.existsSync(nativeAuthDir)) { + console.log('Building authenticate-pam native module...'); + execSync('npm rebuild authenticate-pam', { + stdio: 'inherit', + cwd: __dirname + }); } // Create build directory @@ -512,11 +261,52 @@ async function main() { // 0. Determine which Node.js to use let nodeExe = process.execPath; if (customNodePath) { - // Validate custom node exists - if (!fs.existsSync(customNodePath)) { - console.error(`Error: Custom Node.js not found at ${customNodePath}`); - console.error('Build one using: node build-custom-node.js'); - process.exit(1); + if (customNodePath === 'auto') { + // Auto-discover custom Node.js build + const buildDir = path.join(__dirname, '.node-builds'); + if (fs.existsSync(buildDir)) { + // Find the most recent custom Node.js build + const builds = fs.readdirSync(buildDir) + .filter(name => name.startsWith('node-v') && name.endsWith('-minimal')) + .map(name => { + const nodePath = path.join(buildDir, name, 'out', 'Release', 'node'); + if (fs.existsSync(nodePath)) { + const match = name.match(/node-v(.+)-minimal/); + if (!match || !match[1]) { + console.warn(`Warning: Skipping directory with invalid name format: ${name}`); + return null; + } + return { + path: nodePath, + version: match[1], + mtime: fs.statSync(nodePath).mtime + }; + } + return null; + }) + .filter(Boolean) + .sort((a, b) => b.mtime - a.mtime); + + if (builds.length > 0) { + customNodePath = builds[0].path; + console.log(`Auto-discovered custom Node.js v${builds[0].version} at ${customNodePath}`); + } else { + console.error('Error: No custom Node.js builds found in .node-builds/'); + console.error('Build one using: node build-custom-node.js'); + process.exit(1); + } + } else { + console.error('Error: No .node-builds directory found'); + console.error('Build a custom Node.js using: node build-custom-node.js'); + process.exit(1); + } + } else { + // Validate custom node exists at specified path + if (!fs.existsSync(customNodePath)) { + console.error(`Error: Custom Node.js not found at ${customNodePath}`); + console.error('Build one using: node build-custom-node.js'); + process.exit(1); + } } nodeExe = customNodePath; } @@ -525,26 +315,35 @@ async function main() { const nodeStats = fs.statSync(nodeExe); console.log(`Node.js binary size: ${(nodeStats.size / 1024 / 1024).toFixed(2)} MB`); - // Get version of the Node.js we're using + // 1. Rebuild native modules if using custom Node.js if (customNodePath) { - try { - const customVersion = execSync(`"${nodeExe}" --version`, { encoding: 'utf8' }).trim(); - console.log(`Custom Node.js version: ${customVersion}`); - console.log('This minimal build excludes intl, npm, inspector, and other unused features.'); - } catch (e) { - console.log('Could not determine custom Node.js version'); - } + console.log('\nCustom Node.js detected - rebuilding native modules...'); + const customVersion = execSync(`"${nodeExe}" --version`, { encoding: 'utf8' }).trim(); + console.log(`Custom Node.js version: ${customVersion}`); + + execSync(`pnpm rebuild node-pty authenticate-pam`, { + stdio: 'inherit', + env: { + ...process.env, + npm_config_runtime: 'node', + npm_config_target: customVersion.substring(1), // Remove 'v' prefix + npm_config_arch: process.arch, + npm_config_target_arch: process.arch, + npm_config_disturl: 'https://nodejs.org/dist', + npm_config_build_from_source: 'true', + // Node.js 24 requires C++20 + CXXFLAGS: '-std=c++20', + npm_config_cxxflags: '-std=c++20' + } + }); } - // 1. Patch node-pty - patchNodePty(); - - // 2. Bundle TypeScript with esbuild using custom loader + // 2. Bundle TypeScript with esbuild console.log('\nBundling TypeScript with esbuild...'); // Use deterministic timestamps based on git commit or source - let buildDate; - let buildTimestamp; + let buildDate = new Date().toISOString(); + let buildTimestamp = Date.now(); try { // Try to use the last commit date for reproducible builds @@ -553,20 +352,10 @@ async function main() { buildTimestamp = new Date(gitDate).getTime(); console.log(`Using git commit date for reproducible build: ${buildDate}`); } catch (e) { - // Fallback to SOURCE_DATE_EPOCH if set (for reproducible builds) - if (process.env.SOURCE_DATE_EPOCH) { - buildTimestamp = parseInt(process.env.SOURCE_DATE_EPOCH) * 1000; - buildDate = new Date(buildTimestamp).toISOString(); - console.log(`Using SOURCE_DATE_EPOCH for reproducible build: ${buildDate}`); - } else { - // Only use current time as last resort - buildDate = new Date().toISOString(); - buildTimestamp = Date.now(); - console.warn('Warning: Using current time for build - output will not be reproducible'); - } + // Fallback to current time + console.warn('Warning: Using current time for build - output will not be reproducible'); } - // Use esbuild directly without custom loader since we're patching node-pty let esbuildCmd = `NODE_NO_WARNINGS=1 npx esbuild src/cli.ts \\ --bundle \\ --platform=node \\ @@ -575,15 +364,17 @@ async function main() { --format=cjs \\ --keep-names \\ --external:authenticate-pam \\ + --external:../build/Release/pty.node \\ + --external:./build/Release/pty.node \\ --define:process.env.BUILD_DATE='"${buildDate}"' \\ - --define:process.env.BUILD_TIMESTAMP='"${buildTimestamp}"'`; + --define:process.env.BUILD_TIMESTAMP='"${buildTimestamp}"' \\ + --define:process.env.VIBETUNNEL_SEA='"true"'`; // Also inject git commit hash for version tracking try { const gitCommit = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim(); esbuildCmd += ` \\\n --define:process.env.GIT_COMMIT='"${gitCommit}"'`; } catch (e) { - // Not in a git repo or git not available esbuildCmd += ` \\\n --define:process.env.GIT_COMMIT='"unknown"'`; } @@ -600,7 +391,30 @@ async function main() { } }); - // 2. Create SEA configuration + // 2b. Post-process bundle to ensure VIBETUNNEL_SEA is properly set + console.log('\nPost-processing bundle for SEA compatibility...'); + let bundleContent = fs.readFileSync('build/bundle.js', 'utf8'); + + // Remove shebang line if present (not valid in SEA bundles) + if (bundleContent.startsWith('#!')) { + bundleContent = bundleContent.substring(bundleContent.indexOf('\n') + 1); + } + + // Add VIBETUNNEL_SEA environment variable at the top of the bundle + // This ensures the patched node-pty knows it's running in SEA mode + const seaEnvSetup = `// Set VIBETUNNEL_SEA environment variable for SEA mode +if (typeof process !== 'undefined' && process.versions && process.versions.node) { + process.env.VIBETUNNEL_SEA = 'true'; +} + +`; + + bundleContent = seaEnvSetup + bundleContent; + + fs.writeFileSync('build/bundle.js', bundleContent); + console.log('Bundle post-processing complete'); + + // 3. Create SEA configuration console.log('\nCreating SEA configuration...'); const seaConfig = { main: 'build/bundle.js', @@ -612,11 +426,11 @@ async function main() { fs.writeFileSync('build/sea-config.json', JSON.stringify(seaConfig, null, 2)); - // 3. Generate SEA blob + // 4. Generate SEA blob console.log('Generating SEA blob...'); execSync('node --experimental-sea-config build/sea-config.json', { stdio: 'inherit' }); - // 4. Create executable + // 5. Create executable console.log('\nCreating executable...'); const targetExe = process.platform === 'win32' ? 'native/vibetunnel.exe' : 'native/vibetunnel'; @@ -626,7 +440,7 @@ async function main() { fs.chmodSync(targetExe, 0o755); } - // 5. Inject the blob + // 6. Inject the blob console.log('Injecting SEA blob...'); let postjectCmd = `npx postject ${targetExe} NODE_SEA_BLOB build/sea-prep.blob \\ --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2`; @@ -637,16 +451,14 @@ async function main() { execSync(postjectCmd, { stdio: 'inherit' }); - // 6. Strip the executable first (before signing) + // 7. Strip the executable first (before signing) console.log('Stripping final executable...'); - // Note: This will show a warning about invalidating code signature, which is expected - // since we're modifying a signed Node.js binary. We'll re-sign it in the next step. execSync(`strip -S ${targetExe} 2>&1 | grep -v "warning: changes being made" || true`, { stdio: 'inherit', shell: true }); - // 7. Sign on macOS (after stripping) + // 8. Sign on macOS (after stripping) if (process.platform === 'darwin') { console.log('Signing executable...'); execSync(`codesign --sign - ${targetExe}`, { stdio: 'inherit' }); @@ -657,9 +469,13 @@ async function main() { console.log(`Final executable size: ${(finalStats.size / 1024 / 1024).toFixed(2)} MB`); console.log(`Size reduction: ${((nodeStats.size - finalStats.size) / 1024 / 1024).toFixed(2)} MB`); - // 8. Copy native modules BEFORE restoring (to preserve custom-built versions) - console.log('Copying native modules...'); - const nativeModulesDir = 'node_modules/@homebridge/node-pty-prebuilt-multiarch/build/Release'; + // 9. Copy native modules + console.log('\nCopying native modules...'); + + // Find the actual node-pty build directory (could be in .pnpm directory) + const nodePtyPath = require.resolve('node-pty/package.json'); + const nodePtyBaseDir = path.dirname(nodePtyPath); + const nativeModulesDir = path.join(nodePtyBaseDir, 'build/Release'); // Check if native modules exist if (!fs.existsSync(nativeModulesDir)) { @@ -677,8 +493,10 @@ async function main() { fs.copyFileSync(ptyNodePath, 'native/pty.node'); console.log(' - Copied pty.node'); - // Copy spawn-helper (Unix only) - if (process.platform !== 'win32') { + // Copy spawn-helper (macOS only) + // Note: spawn-helper is only built and required on macOS where it's used for pty_posix_spawn() + // On Linux, node-pty uses forkpty() directly and doesn't need spawn-helper + if (process.platform === 'darwin') { const spawnHelperPath = path.join(nativeModulesDir, 'spawn-helper'); if (!fs.existsSync(spawnHelperPath)) { console.error('Error: spawn-helper not found. Native module build may have failed.'); @@ -699,18 +517,14 @@ async function main() { process.exit(1); } - // 9. Restore original node-pty (AFTER copying the custom-built version) - console.log('\nRestoring original node-pty for development...'); - execSync('rm -rf node_modules/@homebridge/node-pty-prebuilt-multiarch', { stdio: 'inherit' }); - execSync('pnpm install @homebridge/node-pty-prebuilt-multiarch --silent', { stdio: 'inherit' }); - console.log('\nāœ… Build complete!'); console.log(`\nPortable executable created in native/ directory:`); console.log(` - vibetunnel (executable)`); console.log(` - pty.node`); - if (process.platform !== 'win32') { + if (process.platform === 'darwin') { console.log(` - spawn-helper`); } + console.log(` - authenticate_pam.node`); console.log('\nAll files must be kept together in the same directory.'); console.log('This bundle will work on any machine with the same OS/architecture.'); diff --git a/web/fwd-test.ts b/web/fwd-test.ts index 7468f7b7..243b72e0 100755 --- a/web/fwd-test.ts +++ b/web/fwd-test.ts @@ -5,8 +5,8 @@ * Tests PTY spawning, terminal raw mode, stdin/stdout forwarding */ -import * as pty from '@homebridge/node-pty-prebuilt-multiarch'; -import { which } from '@homebridge/node-pty-prebuilt-multiarch/lib/utils'; +import * as pty from 'node-pty'; +import { which } from 'node-pty/lib/utils'; import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; diff --git a/web/package.json b/web/package.json index de5cf4a4..318e8369 100644 --- a/web/package.json +++ b/web/package.json @@ -16,8 +16,9 @@ "lint:fix": "biome check src --write", "lint:biome": "biome check src", "typecheck": "concurrently -n server,client,sw \"tsc --noEmit --project tsconfig.server.json\" \"tsc --noEmit --project tsconfig.client.json\" \"tsc --noEmit --project tsconfig.sw.json\"", + "pretest": "node scripts/ensure-native-modules.js", "test": "vitest", - "test:ci": "vitest run --reporter=verbose", + "test:ci": "npm run pretest && vitest run --reporter=verbose", "test:coverage": "vitest run --coverage", "test:client": "vitest run --mode=client", "test:server": "vitest run --mode=server", @@ -31,7 +32,6 @@ }, "pnpm": { "onlyBuiltDependencies": [ - "@homebridge/node-pty-prebuilt-multiarch", "authenticate-pam", "esbuild", "puppeteer" @@ -48,7 +48,6 @@ "@codemirror/state": "^6.4.1", "@codemirror/theme-one-dark": "^6.1.2", "@codemirror/view": "^6.28.0", - "@homebridge/node-pty-prebuilt-multiarch": "^0.12.0", "@xterm/headless": "^5.5.0", "authenticate-pam": "^1.0.5", "chalk": "^4.1.2", @@ -57,6 +56,7 @@ "lit": "^3.3.0", "mime-types": "^3.0.1", "monaco-editor": "^0.52.2", + "node-pty": "github:microsoft/node-pty#v1.1.0-beta34", "postject": "^1.0.0-alpha.6", "signal-exit": "^4.1.0", "web-push": "^3.6.7", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 74452e37..4171bfc3 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -38,9 +38,6 @@ importers: '@codemirror/view': specifier: ^6.28.0 version: 6.37.2 - '@homebridge/node-pty-prebuilt-multiarch': - specifier: ^0.12.0 - version: 0.12.0 '@xterm/headless': specifier: ^5.5.0 version: 5.5.0 @@ -65,6 +62,9 @@ importers: monaco-editor: specifier: ^0.52.2 version: 0.52.2 + node-pty: + specifier: github:microsoft/node-pty#v1.1.0-beta34 + version: https://codeload.github.com/microsoft/node-pty/tar.gz/d738123f1faf7287513b0df8b9e327be54702e94 postject: specifier: ^1.0.0-alpha.6 version: 1.0.0-alpha.6 @@ -468,9 +468,6 @@ packages: '@hapi/bourne@3.0.0': resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==} - '@homebridge/node-pty-prebuilt-multiarch@0.12.0': - resolution: {integrity: sha512-hJCGcfOnMeRh2KUdWPlVN/1egnfqI4yxgpDhqHSkF2DLn5fiJNdjEHHlcM1K2w9+QBmRE2D/wfmM4zUOb8aMyQ==} - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1141,9 +1138,6 @@ packages: bare-events: optional: true - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - basic-ftp@5.0.5: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} @@ -1152,9 +1146,6 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - bn.js@4.12.2: resolution: {integrity: sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==} @@ -1180,9 +1171,6 @@ packages: buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -1246,9 +1234,6 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} - chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - chromium-bidi@5.1.0: resolution: {integrity: sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==} peerDependencies: @@ -1396,10 +1381,6 @@ packages: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} - decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -1407,10 +1388,6 @@ packages: deep-equal@1.0.1: resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==} - deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - default-gateway@6.0.3: resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==} engines: {node: '>= 10'} @@ -1450,10 +1427,6 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - detect-libc@2.0.4: - resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} - engines: {node: '>=8'} - devtools-protocol@0.0.1452169: resolution: {integrity: sha512-FOFDVMGrAUNp0dDKsAU1TorWJUx2JOU1k9xdgBKKJF3IBh/Uhl2yswG5r3TEAOrCiGY2QRp1e6LVDQrCsTKO4g==} @@ -1579,10 +1552,6 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} - expand-template@2.0.3: - resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} - engines: {node: '>=6'} - expect-type@1.2.1: resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} engines: {node: '>=12.0.0'} @@ -1669,9 +1638,6 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} - fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1707,9 +1673,6 @@ packages: resolution: {integrity: sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==} engines: {node: '>= 14'} - github-from-package@0.0.0: - resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1789,9 +1752,6 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1810,9 +1770,6 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - internal-ip@6.2.0: resolution: {integrity: sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg==} engines: {node: '>=10'} @@ -2111,10 +2068,6 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} @@ -2132,9 +2085,6 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} @@ -2167,9 +2117,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - napi-build-utils@2.0.0: - resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} - negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -2178,10 +2125,6 @@ packages: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} - node-abi@3.75.0: - resolution: {integrity: sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==} - engines: {node: '>=10'} - node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} @@ -2194,6 +2137,10 @@ packages: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-pty@https://codeload.github.com/microsoft/node-pty/tar.gz/d738123f1faf7287513b0df8b9e327be54702e94: + resolution: {tarball: https://codeload.github.com/microsoft/node-pty/tar.gz/d738123f1faf7287513b0df8b9e327be54702e94} + version: 1.0.0 + node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} @@ -2394,11 +2341,6 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - prebuild-install@7.1.3: - resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} - engines: {node: '>=10'} - hasBin: true - prettier@3.6.1: resolution: {integrity: sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==} engines: {node: '>=14'} @@ -2454,20 +2396,12 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} - rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true - react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -2588,12 +2522,6 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - simple-concat@1.0.1: - resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - - simple-get@4.0.1: - resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - sirv@3.0.1: resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} engines: {node: '>=18'} @@ -2662,9 +2590,6 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - strip-ansi@5.2.0: resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} engines: {node: '>=6'} @@ -2681,10 +2606,6 @@ packages: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} - strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - strip-literal@3.0.0: resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} @@ -2721,16 +2642,9 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - tar-fs@2.1.3: - resolution: {integrity: sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==} - tar-fs@3.0.10: resolution: {integrity: sha512-C1SwlQGNLe/jPNqapK8epDsXME7CAJR5RL3GcE6KWx1d9OUByzoHVcbu1VPI8tevg9H8Alae0AApHHFGzrD5zA==} - tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} - tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} @@ -2801,9 +2715,6 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} @@ -3299,11 +3210,6 @@ snapshots: '@hapi/bourne@3.0.0': {} - '@homebridge/node-pty-prebuilt-multiarch@0.12.0': - dependencies: - node-addon-api: 7.1.1 - prebuild-install: 7.1.3 - '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -4042,18 +3948,10 @@ snapshots: bare-events: 2.5.4 optional: true - base64-js@1.5.1: {} - basic-ftp@5.0.5: {} binary-extensions@2.3.0: {} - bl@4.1.0: - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.2 - bn.js@4.12.2: {} body-parser@1.20.3: @@ -4092,11 +3990,6 @@ snapshots: buffer-equal-constant-time@1.0.1: {} - buffer@5.7.1: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - bytes@3.1.2: {} cac@6.7.14: {} @@ -4166,8 +4059,6 @@ snapshots: dependencies: readdirp: 4.1.2 - chownr@1.1.4: {} - chromium-bidi@5.1.0(devtools-protocol@0.0.1452169): dependencies: devtools-protocol: 0.0.1452169 @@ -4292,16 +4183,10 @@ snapshots: decamelize@1.2.0: {} - decompress-response@6.0.0: - dependencies: - mimic-response: 3.1.0 - deep-eql@5.0.2: {} deep-equal@1.0.1: {} - deep-extend@0.6.0: {} - default-gateway@6.0.3: dependencies: execa: 5.1.1 @@ -4328,8 +4213,6 @@ snapshots: destroy@1.2.0: {} - detect-libc@2.0.4: {} - devtools-protocol@0.0.1452169: {} dezalgo@1.0.4: @@ -4466,8 +4349,6 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 - expand-template@2.0.3: {} - expect-type@1.2.1: {} express@4.21.2: @@ -4598,8 +4479,6 @@ snapshots: fresh@0.5.2: {} - fs-constants@1.0.0: {} - fsevents@2.3.3: optional: true @@ -4643,8 +4522,6 @@ snapshots: transitivePeerDependencies: - supports-color - github-from-package@0.0.0: {} - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -4743,8 +4620,6 @@ snapshots: dependencies: safer-buffer: 2.1.2 - ieee754@1.2.1: {} - ignore@5.3.2: {} import-fresh@3.3.1: @@ -4758,8 +4633,6 @@ snapshots: inherits@2.0.4: {} - ini@1.3.8: {} - internal-ip@6.2.0: dependencies: default-gateway: 6.0.3 @@ -5073,8 +4946,6 @@ snapshots: mimic-fn@2.1.0: {} - mimic-response@3.1.0: {} - minimalistic-assert@1.0.1: {} minimatch@9.0.5: @@ -5087,8 +4958,6 @@ snapshots: mitt@3.0.1: {} - mkdirp-classic@0.5.3: {} - mkdirp@1.0.4: {} monaco-editor@0.52.2: {} @@ -5111,16 +4980,10 @@ snapshots: nanoid@3.3.11: {} - napi-build-utils@2.0.0: {} - negotiator@0.6.3: {} netmask@2.0.2: {} - node-abi@3.75.0: - dependencies: - semver: 7.7.2 - node-addon-api@7.1.1: {} node-domexception@1.0.0: {} @@ -5131,6 +4994,10 @@ snapshots: fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 + node-pty@https://codeload.github.com/microsoft/node-pty/tar.gz/d738123f1faf7287513b0df8b9e327be54702e94: + dependencies: + node-addon-api: 7.1.1 + node-releases@2.0.19: {} normalize-path@3.0.0: {} @@ -5316,21 +5183,6 @@ snapshots: dependencies: commander: 9.5.0 - prebuild-install@7.1.3: - dependencies: - detect-libc: 2.0.4 - expand-template: 2.0.3 - github-from-package: 0.0.0 - minimist: 1.2.8 - mkdirp-classic: 0.5.3 - napi-build-utils: 2.0.0 - node-abi: 3.75.0 - pump: 3.0.3 - rc: 1.2.8 - simple-get: 4.0.1 - tar-fs: 2.1.3 - tunnel-agent: 0.6.0 - prettier@3.6.1: {} pretty-format@27.5.1: @@ -5414,25 +5266,12 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - rc@1.2.8: - dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.8 - strip-json-comments: 2.0.1 - react-is@17.0.2: {} read-cache@1.0.0: dependencies: pify: 2.3.0 - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -5586,14 +5425,6 @@ snapshots: signal-exit@4.1.0: {} - simple-concat@1.0.1: {} - - simple-get@4.0.1: - dependencies: - decompress-response: 6.0.0 - once: 1.4.0 - simple-concat: 1.0.1 - sirv@3.0.1: dependencies: '@polka/url': 1.0.0-next.29 @@ -5665,10 +5496,6 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - strip-ansi@5.2.0: dependencies: ansi-regex: 4.1.1 @@ -5683,8 +5510,6 @@ snapshots: strip-final-newline@2.0.0: {} - strip-json-comments@2.0.1: {} - strip-literal@3.0.0: dependencies: js-tokens: 9.0.1 @@ -5759,13 +5584,6 @@ snapshots: transitivePeerDependencies: - ts-node - tar-fs@2.1.3: - dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.3 - tar-stream: 2.2.0 - tar-fs@3.0.10: dependencies: pump: 3.0.3 @@ -5776,14 +5594,6 @@ snapshots: transitivePeerDependencies: - bare-buffer - tar-stream@2.2.0: - dependencies: - bl: 4.1.0 - end-of-stream: 1.4.5 - fs-constants: 1.0.0 - inherits: 2.0.4 - readable-stream: 3.6.2 - tar-stream@3.1.7: dependencies: b4a: 1.6.7 @@ -5846,10 +5656,6 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - tunnel-agent@0.6.0: - dependencies: - safe-buffer: 5.2.1 - type-fest@0.21.3: {} type-is@1.6.18: diff --git a/web/scripts/build.js b/web/scripts/build.js index 0ee83b17..972936ec 100644 --- a/web/scripts/build.js +++ b/web/scripts/build.js @@ -66,7 +66,7 @@ async function build() { format: 'cjs', outfile: 'dist/vibetunnel-cli', external: [ - '@homebridge/node-pty-prebuilt-multiarch', + 'node-pty', 'authenticate-pam', ], minify: true, diff --git a/web/scripts/ensure-native-modules.js b/web/scripts/ensure-native-modules.js new file mode 100755 index 00000000..bf353b1a --- /dev/null +++ b/web/scripts/ensure-native-modules.js @@ -0,0 +1,66 @@ +#!/usr/bin/env node + +/** + * Ensures native modules are built and available for tests + * This script handles pnpm's symlink structure where node-pty might be in .pnpm directory + */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +console.log('Ensuring native modules are built for tests...'); + +// Find the actual node-pty location (could be in .pnpm directory) +let nodePtyPath; +try { + nodePtyPath = require.resolve('node-pty/package.json'); +} catch (e) { + console.error('Could not find node-pty module'); + process.exit(1); +} + +const nodePtyDir = path.dirname(nodePtyPath); +const buildDir = path.join(nodePtyDir, 'build'); +const releaseDir = path.join(buildDir, 'Release'); + +console.log(`Found node-pty at: ${nodePtyDir}`); + +// Check if native modules are built +if (!fs.existsSync(releaseDir) || !fs.existsSync(path.join(releaseDir, 'pty.node'))) { + console.log('Native modules not found, building...'); + + try { + // Build using node-gyp directly to avoid TypeScript issues + execSync(`cd "${nodePtyDir}" && npx node-gyp rebuild`, { + stdio: 'inherit', + shell: true + }); + } catch (e) { + console.error('Failed to build native modules:', e.message); + process.exit(1); + } +} + +// For pnpm, ensure the symlinked node_modules/node-pty has the build directory +const symlinkNodePty = path.join(__dirname, '../node_modules/node-pty'); +if (fs.existsSync(symlinkNodePty) && fs.lstatSync(symlinkNodePty).isSymbolicLink()) { + const symlinkBuildDir = path.join(symlinkNodePty, 'build'); + + // If the symlinked location doesn't have a build directory, create a symlink to the real one + if (!fs.existsSync(symlinkBuildDir) && fs.existsSync(buildDir)) { + console.log('Creating symlink for build directory in node_modules/node-pty...'); + try { + const symlinkType = process.platform === 'win32' ? 'junction' : 'dir'; + fs.symlinkSync(buildDir, symlinkBuildDir, symlinkType); + console.log('Created build directory symlink'); + } catch (e) { + // If symlink fails, try copying instead + console.log('Symlink failed, trying to copy build directory...'); + fs.cpSync(buildDir, symlinkBuildDir, { recursive: true }); + console.log('Copied build directory'); + } + } +} + +console.log('Native modules are ready for tests'); \ No newline at end of file diff --git a/web/src/client/components/sidebar-header.ts b/web/src/client/components/sidebar-header.ts index 5949ea29..2e028ba5 100644 --- a/web/src/client/components/sidebar-header.ts +++ b/web/src/client/components/sidebar-header.ts @@ -6,7 +6,6 @@ import { html } from 'lit'; import { customElement } from 'lit/decorators.js'; import { HeaderBase } from './header-base.js'; -import type { Session } from './session-list.js'; import './terminal-icon.js'; @customElement('sidebar-header') @@ -58,83 +57,6 @@ export class SidebarHeader extends HeaderBase { `; } - private renderExitedToggleButton(exitedSessions: Session[], compact: boolean) { - if (exitedSessions.length === 0) return ''; - - const buttonClass = compact - ? 'relative font-mono text-xs px-3 py-1.5 w-full rounded-lg border transition-all duration-200' - : 'relative font-mono text-xs px-4 py-2 rounded-lg border transition-all duration-200'; - - const stateClass = this.hideExited - ? 'border-dark-border bg-dark-bg-tertiary text-dark-text hover:border-accent-green-darker' - : 'border-accent-green bg-accent-green text-dark-bg hover:bg-accent-green-darker'; - - return html` - - `; - } - - private renderKillAllButton(runningSessions: Session[]) { - // Only show Kill button if there are running sessions - if (runningSessions.length === 0) return ''; - - // Matching the same style as Show Exited button for consistency - const buttonClass = - 'relative font-mono text-xs px-3 py-1.5 w-full rounded-lg border transition-all duration-200'; - const stateClass = this.killingAll - ? 'border-status-error bg-status-error text-dark-bg cursor-not-allowed' - : 'border-dark-border bg-dark-bg-tertiary text-status-error hover:border-status-error hover:bg-dark-bg-secondary'; - - return html` - - `; - } - private renderCompactUserMenu() { // When no user (no-auth mode), show just a settings icon if (!this.currentUser) { @@ -179,7 +101,6 @@ export class SidebarHeader extends HeaderBase { class="w-full text-left px-3 py-1.5 text-xs font-mono text-dark-text hover:bg-dark-bg-secondary" @click=${(e: Event) => { e.stopPropagation(); - console.log('šŸ”§ Settings button clicked in sidebar header'); this.handleOpenSettings(); }} > diff --git a/web/src/server/pty/pty-manager.ts b/web/src/server/pty/pty-manager.ts index a3e4b214..6180e6b0 100644 --- a/web/src/server/pty/pty-manager.ts +++ b/web/src/server/pty/pty-manager.ts @@ -5,12 +5,12 @@ * using the node-pty library while maintaining compatibility with tty-fwd. */ -import type { IPty } from '@homebridge/node-pty-prebuilt-multiarch'; -import * as pty from '@homebridge/node-pty-prebuilt-multiarch'; import chalk from 'chalk'; import { EventEmitter } from 'events'; import * as fs from 'fs'; import * as net from 'net'; +import type { IPty } from 'node-pty'; +import * as pty from 'node-pty'; import * as path from 'path'; import { v4 as uuidv4 } from 'uuid'; import type { diff --git a/web/src/server/pty/types.ts b/web/src/server/pty/types.ts index 18d99988..1bc660a1 100644 --- a/web/src/server/pty/types.ts +++ b/web/src/server/pty/types.ts @@ -4,9 +4,9 @@ * These types match the tty-fwd format to ensure compatibility */ -import type { IPty } from '@homebridge/node-pty-prebuilt-multiarch'; import type * as fs from 'fs'; import type * as net from 'net'; +import type { IPty } from 'node-pty'; import type { SessionInfo } from '../../shared/types.js'; import type { AsciinemaWriter } from './asciinema-writer.js'; diff --git a/web/src/test/setup.ts b/web/src/test/setup.ts index 36ff3733..a72d2111 100644 --- a/web/src/test/setup.ts +++ b/web/src/test/setup.ts @@ -11,7 +11,7 @@ if (!globalThis.crypto) { } // Mock the native pty module before any imports -vi.mock('@homebridge/node-pty-prebuilt-multiarch', () => ({ +vi.mock('node-pty', () => ({ spawn: vi.fn(() => ({ pid: 12345, cols: 80,