From 5609c895173cd2a017c514e9e25ae2ae94c04407 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Tue, 10 Mar 2026 20:43:06 +0700 Subject: [PATCH] Simplify desktop release pipeline: single build, no process smoke test Replace the 855-line managed-daemon-smoke.mjs (which spawned relay servers, daemons, and tested E2E connectivity in CI) with a fast validate-managed-runtime.mjs that checks the bundle is correctly assembled without launching any processes. Structural changes: - Eliminate double-build: removed the pre-build step that compiled the Tauri app just for smoke testing before tauri-action rebuilt it - Move version-setting before the build so there's no version confusion - Sign managed runtime before tauri-action build (macOS) - Smoke tags now do a real tauri build instead of --no-bundle, giving actual signal about whether the release would succeed - Reduce each platform job from ~15 steps to ~12 Co-Authored-By: Claude Opus 4.6 --- .github/workflows/desktop-release.yml | 363 ++++++++---------- packages/desktop/package.json | 1 + .../scripts/validate-managed-runtime.mjs | 62 +++ 3 files changed, 227 insertions(+), 199 deletions(-) create mode 100644 packages/desktop/scripts/validate-managed-runtime.mjs diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml index d6e38a1bd..48377a8b1 100644 --- a/.github/workflows/desktop-release.yml +++ b/.github/workflows/desktop-release.yml @@ -54,29 +54,61 @@ jobs: fetch-depth: 0 ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref }} - - name: Normalize release tag + - name: Resolve release metadata shell: bash run: | set -euo pipefail source_tag="${SOURCE_TAG}" - if [[ "$source_tag" == desktop-windows-v* ]]; then - release_tag="v${source_tag#desktop-windows-v}" - elif [[ "$source_tag" == desktop-linux-v* ]]; then - release_tag="v${source_tag#desktop-linux-v}" - elif [[ "$source_tag" == desktop-macos-v* ]]; then - release_tag="v${source_tag#desktop-macos-v}" - elif [[ "$source_tag" == desktop-v* ]]; then - release_tag="v${source_tag#desktop-v}" - else - release_tag="$source_tag" - fi + release_tag="$source_tag" + for prefix in desktop-windows-v desktop-linux-v desktop-macos-v desktop-v; do + if [[ "$source_tag" == ${prefix}* ]]; then + release_tag="v${source_tag#${prefix}}" + break + fi + done echo "RELEASE_TAG=$release_tag" >> "$GITHUB_ENV" + + version="${release_tag#v}" + echo "DESKTOP_VERSION=$version" >> "$GITHUB_ENV" + if [[ "$source_tag" == *gha-smoke* ]]; then echo "IS_SMOKE_TAG=true" >> "$GITHUB_ENV" else echo "IS_SMOKE_TAG=false" >> "$GITHUB_ENV" fi + - name: Set desktop version from tag + shell: bash + run: | + node <<'NODE' + const fs = require('node:fs'); + const path = require('node:path'); + const version = process.env.DESKTOP_VERSION; + if (!version) throw new Error('DESKTOP_VERSION env var is missing'); + console.log(`Setting desktop version to ${version}`); + + const tauriConfPath = path.join('packages', 'desktop', 'src-tauri', 'tauri.conf.json'); + const tauriConfText = fs.readFileSync(tauriConfPath, 'utf8'); + const tauriRe = /("version"\s*:\s*")([^"]+)(")/; + if (!tauriRe.test(tauriConfText)) throw new Error('Failed to find version in tauri.conf.json'); + fs.writeFileSync(tauriConfPath, tauriConfText.replace(tauriRe, `$1${version}$3`)); + + const cargoTomlPath = path.join('packages', 'desktop', 'src-tauri', 'Cargo.toml'); + const lines = fs.readFileSync(cargoTomlPath, 'utf8').split(/\r?\n/); + let inPackage = false, updated = false; + const result = lines.map((line) => { + if (/^\[package\]\s*$/.test(line)) inPackage = true; + else if (inPackage && /^\[/.test(line)) inPackage = false; + if (inPackage && /^version\s*=\s*".*"\s*$/.test(line)) { + updated = true; + return `version = "${version}"`; + } + return line; + }); + if (!updated) throw new Error('Failed to update Cargo.toml version'); + fs.writeFileSync(cargoTomlPath, result.join('\n') + '\n'); + NODE + - name: Setup Node uses: actions/setup-node@v4 with: @@ -107,79 +139,23 @@ jobs: - name: Build web app for Tauri run: npm run build:web --workspace=@getpaseo/app - - name: Build managed runtime for macOS + - name: Build managed runtime run: npm run prepare:managed-runtime --workspace=@getpaseo/desktop + - name: Validate managed runtime bundle + run: npm run validate:managed-runtime --workspace=@getpaseo/desktop + - name: Import Apple code-signing certificate uses: apple-actions/import-codesign-certs@v3 with: p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }} p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - - name: Build packaged app for macOS smoke - shell: bash - run: | - set -euo pipefail - app_path="packages/desktop/src-tauri/target/${{ matrix.rust_target }}/release/bundle/macos/Paseo.app/Contents/MacOS/Paseo" - if npm run tauri --workspace=@getpaseo/desktop build -- --target ${{ matrix.rust_target }}; then - exit 0 - fi - if [[ -f "$app_path" ]]; then - echo "Smoke app created at $app_path despite tauri build exiting non-zero; continuing." - exit 0 - fi - exit 1 - - - name: Smoke check managed runtime for macOS - env: - PASEO_MANAGED_SMOKE_SKIP_BUILD: "1" - PASEO_MANAGED_SMOKE_RUST_TARGET: ${{ matrix.rust_target }} - run: npm run smoke:managed-daemon --workspace=@getpaseo/desktop - - - name: Sign bundled managed runtime for macOS + - name: Sign bundled managed runtime env: APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} run: node ./packages/desktop/scripts/sign-managed-runtime-macos.mjs - - name: Set desktop version from tag - shell: bash - run: | - node <<'NODE' - const fs = require('node:fs'); - const path = require('node:path'); - - const rawTag = process.env.SOURCE_TAG; - if (!rawTag) throw new Error('SOURCE_TAG env var is missing'); - - const version = rawTag.replace(/^desktop-(windows-|linux-|macos-)?/, '').replace(/^v/, ''); - console.log(`Using desktop version ${version} from tag ${rawTag}`); - - const tauriConfPath = path.join('packages', 'desktop', 'src-tauri', 'tauri.conf.json'); - const tauriConfText = fs.readFileSync(tauriConfPath, 'utf8'); - const tauriRe = /("version"\s*:\s*")([^"]+)(")/; - if (!tauriRe.test(tauriConfText)) { - throw new Error(`Failed to find version field in ${tauriConfPath}`); - } - fs.writeFileSync(tauriConfPath, tauriConfText.replace(tauriRe, `$1${version}$3`)); - - const cargoTomlPath = path.join('packages', 'desktop', 'src-tauri', 'Cargo.toml'); - const cargoLines = fs.readFileSync(cargoTomlPath, 'utf8').split(/\r?\n/); - let inPackage = false; - let updated = false; - const nextLines = cargoLines.map((line) => { - if (/^\[package\]\s*$/.test(line)) inPackage = true; - else if (inPackage && /^\[/.test(line)) inPackage = false; - - if (inPackage && /^version\s*=\s*".*"\s*$/.test(line)) { - updated = true; - return `version = "${version}"`; - } - return line; - }); - if (!updated) throw new Error(`Failed to update Cargo package version in ${cargoTomlPath}`); - fs.writeFileSync(cargoTomlPath, `${nextLines.join('\n')}\n`); - NODE - - name: Detect existing GitHub release state if: env.IS_SMOKE_TAG != 'true' env: @@ -216,6 +192,13 @@ jobs: prerelease: false args: --target ${{ matrix.rust_target }} + - name: Build macOS app (smoke only) + if: env.IS_SMOKE_TAG == 'true' + run: npm run tauri --workspace=@getpaseo/desktop build -- --target ${{ matrix.rust_target }} + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + publish-linux: if: ${{ (github.event_name == 'workflow_dispatch' && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'linux')) || (github.event_name == 'push' && (startsWith(github.ref_name, 'v') || startsWith(github.ref_name, 'desktop-v') || startsWith(github.ref_name, 'desktop-linux-v'))) }} permissions: @@ -229,29 +212,61 @@ jobs: fetch-depth: 0 ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref }} - - name: Normalize release tag + - name: Resolve release metadata shell: bash run: | set -euo pipefail source_tag="${SOURCE_TAG}" - if [[ "$source_tag" == desktop-windows-v* ]]; then - release_tag="v${source_tag#desktop-windows-v}" - elif [[ "$source_tag" == desktop-linux-v* ]]; then - release_tag="v${source_tag#desktop-linux-v}" - elif [[ "$source_tag" == desktop-macos-v* ]]; then - release_tag="v${source_tag#desktop-macos-v}" - elif [[ "$source_tag" == desktop-v* ]]; then - release_tag="v${source_tag#desktop-v}" - else - release_tag="$source_tag" - fi + release_tag="$source_tag" + for prefix in desktop-windows-v desktop-linux-v desktop-macos-v desktop-v; do + if [[ "$source_tag" == ${prefix}* ]]; then + release_tag="v${source_tag#${prefix}}" + break + fi + done echo "RELEASE_TAG=$release_tag" >> "$GITHUB_ENV" + + version="${release_tag#v}" + echo "DESKTOP_VERSION=$version" >> "$GITHUB_ENV" + if [[ "$source_tag" == *gha-smoke* ]]; then echo "IS_SMOKE_TAG=true" >> "$GITHUB_ENV" else echo "IS_SMOKE_TAG=false" >> "$GITHUB_ENV" fi + - name: Set desktop version from tag + shell: bash + run: | + node <<'NODE' + const fs = require('node:fs'); + const path = require('node:path'); + const version = process.env.DESKTOP_VERSION; + if (!version) throw new Error('DESKTOP_VERSION env var is missing'); + console.log(`Setting desktop version to ${version}`); + + const tauriConfPath = path.join('packages', 'desktop', 'src-tauri', 'tauri.conf.json'); + const tauriConfText = fs.readFileSync(tauriConfPath, 'utf8'); + const tauriRe = /("version"\s*:\s*")([^"]+)(")/; + if (!tauriRe.test(tauriConfText)) throw new Error('Failed to find version in tauri.conf.json'); + fs.writeFileSync(tauriConfPath, tauriConfText.replace(tauriRe, `$1${version}$3`)); + + const cargoTomlPath = path.join('packages', 'desktop', 'src-tauri', 'Cargo.toml'); + const lines = fs.readFileSync(cargoTomlPath, 'utf8').split(/\r?\n/); + let inPackage = false, updated = false; + const result = lines.map((line) => { + if (/^\[package\]\s*$/.test(line)) inPackage = true; + else if (inPackage && /^\[/.test(line)) inPackage = false; + if (inPackage && /^version\s*=\s*".*"\s*$/.test(line)) { + updated = true; + return `version = "${version}"`; + } + return line; + }); + if (!updated) throw new Error('Failed to update Cargo.toml version'); + fs.writeFileSync(cargoTomlPath, result.join('\n') + '\n'); + NODE + - name: Install Linux packaging dependencies run: | sudo apt-get update @@ -285,50 +300,11 @@ jobs: - name: Build web app for Tauri run: npm run build:web --workspace=@getpaseo/app - - name: Build managed runtime for Linux + - name: Build managed runtime run: npm run prepare:managed-runtime --workspace=@getpaseo/desktop - - name: Smoke check managed runtime for Linux - run: npm run smoke:managed-daemon --workspace=@getpaseo/desktop - - - name: Set desktop version from tag - shell: bash - run: | - node <<'NODE' - const fs = require('node:fs'); - const path = require('node:path'); - - const rawTag = process.env.SOURCE_TAG; - if (!rawTag) throw new Error('SOURCE_TAG env var is missing'); - - const version = rawTag.replace(/^desktop-(windows-|linux-|macos-)?/, '').replace(/^v/, ''); - console.log(`Using desktop version ${version} from tag ${rawTag}`); - - const tauriConfPath = path.join('packages', 'desktop', 'src-tauri', 'tauri.conf.json'); - const tauriConfText = fs.readFileSync(tauriConfPath, 'utf8'); - const tauriRe = /("version"\s*:\s*")([^"]+)(")/; - if (!tauriRe.test(tauriConfText)) { - throw new Error(`Failed to find version field in ${tauriConfPath}`); - } - fs.writeFileSync(tauriConfPath, tauriConfText.replace(tauriRe, `$1${version}$3`)); - - const cargoTomlPath = path.join('packages', 'desktop', 'src-tauri', 'Cargo.toml'); - const cargoLines = fs.readFileSync(cargoTomlPath, 'utf8').split(/\r?\n/); - let inPackage = false; - let updated = false; - const nextLines = cargoLines.map((line) => { - if (/^\[package\]\s*$/.test(line)) inPackage = true; - else if (inPackage && /^\[/.test(line)) inPackage = false; - - if (inPackage && /^version\s*=\s*".*"\s*$/.test(line)) { - updated = true; - return `version = "${version}"`; - } - return line; - }); - if (!updated) throw new Error(`Failed to update Cargo package version in ${cargoTomlPath}`); - fs.writeFileSync(cargoTomlPath, `${nextLines.join('\n')}\n`); - NODE + - name: Validate managed runtime bundle + run: npm run validate:managed-runtime --workspace=@getpaseo/desktop - name: Detect existing GitHub release state if: env.IS_SMOKE_TAG != 'true' @@ -360,6 +336,13 @@ jobs: prerelease: false args: --bundles appimage + - name: Build Linux app (smoke only) + if: env.IS_SMOKE_TAG == 'true' + run: npm run tauri --workspace=@getpaseo/desktop build -- --bundles appimage + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + publish-windows: if: ${{ (github.event_name == 'workflow_dispatch' && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'windows')) || (github.event_name == 'push' && (startsWith(github.ref_name, 'v') || startsWith(github.ref_name, 'desktop-v') || startsWith(github.ref_name, 'desktop-windows-v'))) }} permissions: @@ -373,29 +356,61 @@ jobs: fetch-depth: 0 ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref }} - - name: Normalize release tag + - name: Resolve release metadata shell: bash run: | set -euo pipefail source_tag="${SOURCE_TAG}" - if [[ "$source_tag" == desktop-windows-v* ]]; then - release_tag="v${source_tag#desktop-windows-v}" - elif [[ "$source_tag" == desktop-linux-v* ]]; then - release_tag="v${source_tag#desktop-linux-v}" - elif [[ "$source_tag" == desktop-macos-v* ]]; then - release_tag="v${source_tag#desktop-macos-v}" - elif [[ "$source_tag" == desktop-v* ]]; then - release_tag="v${source_tag#desktop-v}" - else - release_tag="$source_tag" - fi + release_tag="$source_tag" + for prefix in desktop-windows-v desktop-linux-v desktop-macos-v desktop-v; do + if [[ "$source_tag" == ${prefix}* ]]; then + release_tag="v${source_tag#${prefix}}" + break + fi + done echo "RELEASE_TAG=$release_tag" >> "$GITHUB_ENV" + + version="${release_tag#v}" + echo "DESKTOP_VERSION=$version" >> "$GITHUB_ENV" + if [[ "$source_tag" == *gha-smoke* ]]; then echo "IS_SMOKE_TAG=true" >> "$GITHUB_ENV" else echo "IS_SMOKE_TAG=false" >> "$GITHUB_ENV" fi + - name: Set desktop version from tag + shell: bash + run: | + node <<'NODE' + const fs = require('node:fs'); + const path = require('node:path'); + const version = process.env.DESKTOP_VERSION; + if (!version) throw new Error('DESKTOP_VERSION env var is missing'); + console.log(`Setting desktop version to ${version}`); + + const tauriConfPath = path.join('packages', 'desktop', 'src-tauri', 'tauri.conf.json'); + const tauriConfText = fs.readFileSync(tauriConfPath, 'utf8'); + const tauriRe = /("version"\s*:\s*")([^"]+)(")/; + if (!tauriRe.test(tauriConfText)) throw new Error('Failed to find version in tauri.conf.json'); + fs.writeFileSync(tauriConfPath, tauriConfText.replace(tauriRe, `$1${version}$3`)); + + const cargoTomlPath = path.join('packages', 'desktop', 'src-tauri', 'Cargo.toml'); + const lines = fs.readFileSync(cargoTomlPath, 'utf8').split(/\r?\n/); + let inPackage = false, updated = false; + const result = lines.map((line) => { + if (/^\[package\]\s*$/.test(line)) inPackage = true; + else if (inPackage && /^\[/.test(line)) inPackage = false; + if (inPackage && /^version\s*=\s*".*"\s*$/.test(line)) { + updated = true; + return `version = "${version}"`; + } + return line; + }); + if (!updated) throw new Error('Failed to update Cargo.toml version'); + fs.writeFileSync(cargoTomlPath, result.join('\n') + '\n'); + NODE + - name: Setup Node uses: actions/setup-node@v4 with: @@ -424,68 +439,11 @@ jobs: - name: Build web app for Tauri run: npm run build:web --workspace=@getpaseo/app - - name: Build managed runtime for Windows + - name: Build managed runtime run: npm run prepare:managed-runtime --workspace=@getpaseo/desktop - - name: Build packaged app for Windows smoke - shell: bash - run: | - set -euo pipefail - app_path="packages/desktop/src-tauri/target/release/Paseo.exe" - if npm run tauri --workspace=@getpaseo/desktop build -- --no-bundle; then - exit 0 - fi - if [[ -f "$app_path" ]]; then - echo "Smoke app created at $app_path despite tauri build exiting non-zero; continuing." - exit 0 - fi - echo "Expected Windows smoke app at $app_path" - exit 1 - - - name: Smoke check managed runtime for Windows - timeout-minutes: 12 - env: - PASEO_MANAGED_SMOKE_SKIP_BUILD: "1" - run: npm run smoke:managed-daemon --workspace=@getpaseo/desktop - - - name: Set desktop version from tag - shell: bash - run: | - node <<'NODE' - const fs = require('node:fs'); - const path = require('node:path'); - - const rawTag = process.env.SOURCE_TAG; - if (!rawTag) throw new Error('SOURCE_TAG env var is missing'); - - const version = rawTag.replace(/^desktop-(windows-|linux-|macos-)?/, '').replace(/^v/, ''); - console.log(`Using desktop version ${version} from tag ${rawTag}`); - - const tauriConfPath = path.join('packages', 'desktop', 'src-tauri', 'tauri.conf.json'); - const tauriConfText = fs.readFileSync(tauriConfPath, 'utf8'); - const tauriRe = /("version"\s*:\s*")([^"]+)(")/; - if (!tauriRe.test(tauriConfText)) { - throw new Error(`Failed to find version field in ${tauriConfPath}`); - } - fs.writeFileSync(tauriConfPath, tauriConfText.replace(tauriRe, `$1${version}$3`)); - - const cargoTomlPath = path.join('packages', 'desktop', 'src-tauri', 'Cargo.toml'); - const cargoLines = fs.readFileSync(cargoTomlPath, 'utf8').split(/\r?\n/); - let inPackage = false; - let updated = false; - const nextLines = cargoLines.map((line) => { - if (/^\[package\]\s*$/.test(line)) inPackage = true; - else if (inPackage && /^\[/.test(line)) inPackage = false; - - if (inPackage && /^version\s*=\s*".*"\s*$/.test(line)) { - updated = true; - return `version = "${version}"`; - } - return line; - }); - if (!updated) throw new Error(`Failed to update Cargo package version in ${cargoTomlPath}`); - fs.writeFileSync(cargoTomlPath, `${nextLines.join('\n')}\n`); - NODE + - name: Validate managed runtime bundle + run: npm run validate:managed-runtime --workspace=@getpaseo/desktop - name: Detect existing GitHub release state if: env.IS_SMOKE_TAG != 'true' @@ -516,3 +474,10 @@ jobs: releaseDraft: ${{ env.RELEASE_DRAFT }} prerelease: false args: --bundles nsis,msi + + - name: Build Windows app (smoke only) + if: env.IS_SMOKE_TAG == 'true' + run: npm run tauri --workspace=@getpaseo/desktop build -- --bundles nsis,msi + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 4ef943030..f588a767f 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -8,6 +8,7 @@ "prepare:managed-runtime": "npm --prefix ../.. run build:daemon && npm run build:managed-runtime", "dev": "npm run prepare:managed-runtime && tauri dev", "build": "npm --prefix ../.. run build:web --workspace=@getpaseo/app && npm run prepare:managed-runtime && tauri build", + "validate:managed-runtime": "node ./scripts/validate-managed-runtime.mjs", "smoke:managed-daemon": "node ./scripts/managed-daemon-smoke.mjs", "tauri": "tauri" }, diff --git a/packages/desktop/scripts/validate-managed-runtime.mjs b/packages/desktop/scripts/validate-managed-runtime.mjs new file mode 100644 index 000000000..1a7082eaa --- /dev/null +++ b/packages/desktop/scripts/validate-managed-runtime.mjs @@ -0,0 +1,62 @@ +import assert from "node:assert/strict"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const desktopRoot = fileURLToPath(new URL("..", import.meta.url)); +const resourcesRoot = path.join(desktopRoot, "src-tauri", "resources", "managed-runtime"); + +const desktopPackageJson = JSON.parse( + await fs.readFile(path.join(desktopRoot, "package.json"), "utf8") +); +const expectedVersion = desktopPackageJson.version; + +const currentRuntime = JSON.parse( + await fs.readFile(path.join(resourcesRoot, "current-runtime.json"), "utf8") +); +assert.equal(currentRuntime.runtimeVersion, expectedVersion, "current-runtime.json version mismatch"); +assert.ok(currentRuntime.runtimeId, "current-runtime.json missing runtimeId"); +assert.ok(currentRuntime.relativeRoot, "current-runtime.json missing relativeRoot"); + +const runtimeRoot = path.join(resourcesRoot, currentRuntime.relativeRoot); +const manifest = JSON.parse( + await fs.readFile(path.join(runtimeRoot, "runtime-manifest.json"), "utf8") +); +assert.equal(manifest.runtimeId, currentRuntime.runtimeId, "manifest runtimeId mismatch"); +assert.equal(manifest.runtimeVersion, expectedVersion, "manifest version mismatch"); +assert.ok(manifest.nodeRelativePath, "manifest missing nodeRelativePath"); +assert.ok(manifest.cliEntrypointRelativePath, "manifest missing cliEntrypointRelativePath"); +assert.ok(manifest.serverRunnerRelativePath, "manifest missing serverRunnerRelativePath"); + +const nodeBinary = path.join(runtimeRoot, manifest.nodeRelativePath); +await fs.access(nodeBinary).catch(() => { + throw new Error(`Bundled Node binary not found: ${nodeBinary}`); +}); + +const cliEntry = path.join(runtimeRoot, manifest.cliEntrypointRelativePath); +await fs.access(cliEntry).catch(() => { + throw new Error(`CLI entrypoint not found: ${cliEntry}`); +}); + +const serverRunner = path.join(runtimeRoot, manifest.serverRunnerRelativePath); +await fs.access(serverRunner).catch(() => { + throw new Error(`Server runner not found: ${serverRunner}`); +}); + +const runtimePackageJson = JSON.parse( + await fs.readFile(path.join(runtimeRoot, "package.json"), "utf8") +); +assert.equal(runtimePackageJson.version, expectedVersion, "runtime package.json version mismatch"); + +for (const pkg of ["@getpaseo/relay", "@getpaseo/server", "@getpaseo/cli"]) { + const pkgDir = path.join(runtimeRoot, "node_modules", ...pkg.split("/")); + await fs.access(pkgDir).catch(() => { + throw new Error(`Missing bundled dependency: ${pkg} (expected at ${pkgDir})`); + }); +} + +console.log(`[validate-managed-runtime] PASS`); +console.log(` runtimeId: ${manifest.runtimeId}`); +console.log(` version: ${expectedVersion}`); +console.log(` platform: ${manifest.platform}`); +console.log(` arch: ${manifest.arch}`);