Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions packages/runpane-py/src/runpane/version.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
from __future__ import annotations

import os
import subprocess
import sys
from importlib import metadata
from typing import Optional
from . import __version__

PANE_VERSION_TIMEOUT_SECONDS = 2
POWERSHELL_TIMEOUT_SECONDS = 2


def wrapper_version() -> str:
Expand All @@ -21,6 +24,9 @@ def print_version(pane_path: object = None) -> int:


def pane_version(executable_path: str) -> Optional[str]:
if sys.platform == "win32":
return _windows_file_version(executable_path)

try:
result = subprocess.run(
[executable_path, "--version"],
Expand All @@ -32,3 +38,38 @@ def pane_version(executable_path: str) -> Optional[str]:
return None
output = (result.stdout + result.stderr).strip()
return output or None


def _windows_file_version(executable_path: str) -> Optional[str]:
script = "; ".join(
[
"$ErrorActionPreference = 'Stop'",
"$target = $env:RUNPANE_PANE_VERSION_PATH",
"if (-not $target) { exit 1 }",
"$info = (Get-Item -LiteralPath $target).VersionInfo",
"if ($info.FileVersion) { $info.FileVersion } elseif ($info.ProductVersion) { $info.ProductVersion }",
]
)
try:
env = {
**os.environ,
"RUNPANE_PANE_VERSION_PATH": executable_path,
}
result = subprocess.run(
[
"powershell.exe",
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy",
"Bypass",
"-Command",
script,
],
capture_output=True,
env=env,
text=True,
timeout=POWERSHELL_TIMEOUT_SECONDS,
)
except (OSError, subprocess.TimeoutExpired):
return None
return result.stdout.strip() or None
41 changes: 41 additions & 0 deletions packages/runpane/src/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fs from 'fs';
import path from 'path';

const PANE_VERSION_TIMEOUT_MS = 2_000;
const POWERSHELL_TIMEOUT_MS = 2_000;

export function getWrapperVersion(): string {
const packagePath = path.resolve(__dirname, '..', 'package.json');
Expand All @@ -21,6 +22,10 @@ export async function printVersion(_panePath?: string): Promise<number> {
}

export function getPaneVersion(executablePath: string): string | undefined {
if (process.platform === 'win32') {
return getWindowsFileVersion(executablePath);
}

try {
const result = childProcess.spawnSync(executablePath, ['--version'], {
encoding: 'utf8',
Expand All @@ -37,3 +42,39 @@ export function getPaneVersion(executablePath: string): string | undefined {
return undefined;
}
}

function getWindowsFileVersion(executablePath: string): string | undefined {
const script = [
"$ErrorActionPreference = 'Stop'",
'$target = $env:RUNPANE_PANE_VERSION_PATH',
'if (-not $target) { exit 1 }',
'$info = (Get-Item -LiteralPath $target).VersionInfo',
'if ($info.FileVersion) { $info.FileVersion } elseif ($info.ProductVersion) { $info.ProductVersion }'
].join('; ');

try {
const result = childProcess.spawnSync('powershell.exe', [
'-NoProfile',
'-NonInteractive',
'-ExecutionPolicy',
'Bypass',
'-Command',
script
], {
encoding: 'utf8',
env: {
...process.env,
RUNPANE_PANE_VERSION_PATH: executablePath
},
stdio: ['ignore', 'pipe', 'pipe'],
timeout: POWERSHELL_TIMEOUT_MS,
windowsHide: true
});
if (result.error) {
return undefined;
}
return result.stdout.trim() || undefined;
} catch {
return undefined;
}
}
75 changes: 75 additions & 0 deletions scripts/test-runpane-contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,80 @@ finally:
});
}

function checkWindowsPaneVersionDoesNotLaunchExecutable() {
if (process.platform === 'win32') {
const versionModule = require(path.join(rootDir, 'packages', 'runpane', 'dist', 'version.js'));
const originalSpawnSync = childProcess.spawnSync;
const paneExe = 'C:\\Program Files\\Pane\\Pane.exe';
const calls = [];

try {
childProcess.spawnSync = (command, args, options) => {
calls.push({ command, args, options });
assert.notStrictEqual(command, paneExe);
assert.strictEqual(command, 'powershell.exe');
assert.strictEqual(options.env.RUNPANE_PANE_VERSION_PATH, paneExe);
return { stdout: '2.3.19\r\n', stderr: '', status: 0 };
};

assert.strictEqual(versionModule.getPaneVersion(paneExe), '2.3.19');
assert.strictEqual(calls.length, 1);

childProcess.spawnSync = (command) => {
assert.notStrictEqual(command, paneExe);
return { error: new Error('metadata unavailable'), stdout: '', stderr: '' };
};
assert.strictEqual(versionModule.getPaneVersion(paneExe), undefined);
} finally {
childProcess.spawnSync = originalSpawnSync;
}
}

const pythonOutput = runPythonSnippet(`
import json
import runpane.version as version

original_platform = version.sys.platform
original_run = version.subprocess.run
pane_exe = r"C:\\Program Files\\Pane\\Pane.exe"
calls = []

class Result:
def __init__(self, stdout):
self.stdout = stdout
self.stderr = ""

def fake_run(args, **kwargs):
calls.append(args)
assert args[0] == "powershell.exe"
assert kwargs["env"]["RUNPANE_PANE_VERSION_PATH"] == pane_exe
return Result("2.3.19\\n")

try:
version.sys.platform = "win32"
version.subprocess.run = fake_run
first = version.pane_version(pane_exe)

def missing_metadata(args, **kwargs):
assert args[0] == "powershell.exe"
return Result("")

version.subprocess.run = missing_metadata
second = version.pane_version(pane_exe)
finally:
version.sys.platform = original_platform
version.subprocess.run = original_run

print(json.dumps({"first": first, "second": second, "calls": len(calls)}))
`);

assert.deepStrictEqual(JSON.parse(pythonOutput), {
first: '2.3.19',
second: null,
calls: 1
});
}

async function checkFromJsonAcceptsBom() {
const payloadPath = path.join(os.tmpdir(), `runpane-from-json-bom-${process.pid}.json`);
const payload = {
Expand Down Expand Up @@ -920,6 +994,7 @@ async function runChecks() {
compareExistingReusePolicy();
checkPlatformMatchingEdgeCases();
await checkExistingDaemonShortCircuit();
checkWindowsPaneVersionDoesNotLaunchExecutable();
await checkFromJsonAcceptsBom();
checkHelpOutput();
compareAgentContextParity();
Expand Down
Loading