diff --git a/.github/workflows/contract-smoke.yml b/.github/workflows/contract-smoke.yml index f2c05ae..38d0ded 100644 --- a/.github/workflows/contract-smoke.yml +++ b/.github/workflows/contract-smoke.yml @@ -23,19 +23,26 @@ jobs: - name: Checkout Canio package uses: actions/checkout@v5 - - name: Clone Canio Cloud + - name: Check cross-repo token + id: cross-repo-token run: | if [[ -z "${CANIO_CROSS_REPO_TOKEN:-}" ]]; then - echo "Missing CANIO_CROSS_REPO_TOKEN secret." >&2 - exit 1 + echo "available=false" >> "$GITHUB_OUTPUT" + echo "::notice::Skipping contract smoke because CANIO_CROSS_REPO_TOKEN is not available for this run." + else + echo "available=true" >> "$GITHUB_OUTPUT" fi + - name: Clone Canio Cloud + if: steps.cross-repo-token.outputs.available == 'true' + run: | rm -rf "$CANIO_CLOUD_DIR" git clone --depth 1 \ "https://x-access-token:${CANIO_CROSS_REPO_TOKEN}@github.com/oxhq/canio-cloud.git" \ "$CANIO_CLOUD_DIR" - name: Setup PHP + if: steps.cross-repo-token.outputs.available == 'true' uses: shivammathur/setup-php@v2 with: php-version: "8.4" @@ -44,25 +51,30 @@ jobs: extensions: intl, zip - name: Setup Node + if: steps.cross-repo-token.outputs.available == 'true' uses: actions/setup-node@v6 with: node-version: "22" - name: Install Canio Cloud dependencies + if: steps.cross-repo-token.outputs.available == 'true' working-directory: ${{ env.CANIO_CLOUD_DIR }} run: composer install --no-interaction --prefer-dist - name: Setup Go + if: steps.cross-repo-token.outputs.available == 'true' uses: actions/setup-go@v6 with: go-version-file: ${{ env.CANIO_PACKAGE_DIR }}/runtime/stagehand/go.mod cache-dependency-path: ${{ env.CANIO_PACKAGE_DIR }}/runtime/stagehand/go.sum - name: Setup Chrome + if: steps.cross-repo-token.outputs.available == 'true' id: setup-chrome uses: browser-actions/setup-chrome@v2 - name: Run cross-repo contract smoke + if: steps.cross-repo-token.outputs.available == 'true' env: CANIO_CHROMIUM_PATH: ${{ steps.setup-chrome.outputs.chrome-path }} CANIO_CHROMIUM_NO_SANDBOX: "true" diff --git a/docs/releases/v1.0.2.md b/docs/releases/v1.0.2.md new file mode 100644 index 0000000..029e0ab --- /dev/null +++ b/docs/releases/v1.0.2.md @@ -0,0 +1,28 @@ +# v1.0.2 + +This release fixes a runtime/package drift that could make local Stagehand startup fail after installing the Laravel package. + +The Laravel package had learned to launch Stagehand with newer navigation and request-limit flags, but the default installer could still fetch the older `v1.0.1` Stagehand binary. That binary did not know those flags, so it exited before serving. `v1.0.2` aligns the package and runtime surfaces and adds a compatibility guard so this class of mismatch fails early with a clear installer error instead of reaching end users as a broken local runtime. + +## Fixed + +- The Laravel installer now validates downloaded Stagehand binaries before accepting them. +- Stale Stagehand binaries that do not expose required serve flags are rejected and removed. +- The default Stagehand runtime release now points at `v1.0.2`, matching the package command surface. +- Windows binary resolution now handles absolute drive-letter paths and runnable `.exe`, `.cmd`, and `.bat` files correctly. +- Stagehand Chromium profiles are namespaced per runtime process, so stale browser children from a crashed or force-stopped runtime cannot lock the next process out of local PDF rendering. +- Browser-pool startup no longer returns a fresh lease with a cancelled browser context after acquire-timeout cleanup races. + +## For Laravel Users + +If you use embedded local rendering, run the installer again after upgrading: + +```bash +php artisan canio:runtime:install +``` + +The installer will fetch the `v1.0.2` Stagehand binary for your platform and verify that it supports the flags required by this package. + +## Release Assets + +This release publishes Stagehand binaries for Linux, macOS, and Windows on amd64 and arm64, plus `checksums.txt`. diff --git a/packages/laravel/src/CanioServiceProvider.php b/packages/laravel/src/CanioServiceProvider.php index 91e30a2..eddf5bd 100644 --- a/packages/laravel/src/CanioServiceProvider.php +++ b/packages/laravel/src/CanioServiceProvider.php @@ -8,7 +8,6 @@ use Illuminate\Support\ServiceProvider; use Oxhq\Canio\Bridge\CloudStagehandClient; use Oxhq\Canio\Bridge\HttpStagehandClient; -use Oxhq\Canio\Contracts\CanioCloudSyncer; use Oxhq\Canio\Console\CanioDoctorCommand; use Oxhq\Canio\Console\CanioInstallCommand; use Oxhq\Canio\Console\CanioRuntimeArtifactCommand; @@ -24,14 +23,16 @@ use Oxhq\Canio\Console\CanioRuntimeRetryCommand; use Oxhq\Canio\Console\CanioRuntimeStatusCommand; use Oxhq\Canio\Console\CanioServeCommand; +use Oxhq\Canio\Contracts\CanioCloudSyncer; use Oxhq\Canio\Contracts\StagehandClient; use Oxhq\Canio\Contracts\StagehandRuntimeBootstrapper; use Oxhq\Canio\Support\CanioCloudRequestor; use Oxhq\Canio\Support\CanioCloudSyncFailureRecorder; use Oxhq\Canio\Support\EmbeddedStagehandRuntimeBootstrapper; use Oxhq\Canio\Support\HttpCanioCloudSyncer; -use Oxhq\Canio\Support\NullStagehandRuntimeBootstrapper; use Oxhq\Canio\Support\NullCanioCloudSyncer; +use Oxhq\Canio\Support\NullStagehandRuntimeBootstrapper; +use Oxhq\Canio\Support\StagehandBinaryCompatibility; use Oxhq\Canio\Support\StagehandBinaryResolver; use Oxhq\Canio\Support\StagehandHealthProbe; use Oxhq\Canio\Support\StagehandProcessLauncher; @@ -44,6 +45,7 @@ public function register(): void { $this->mergeConfigFrom(__DIR__.'/../config/canio.php', 'canio'); + $this->app->singleton(StagehandBinaryCompatibility::class); $this->app->singleton(StagehandBinaryResolver::class); $this->app->singleton(StagehandReleaseInstaller::class); $this->app->singleton(StagehandServeCommandBuilder::class); diff --git a/packages/laravel/src/Console/CanioRuntimeInstallCommand.php b/packages/laravel/src/Console/CanioRuntimeInstallCommand.php index 3175313..b51162c 100644 --- a/packages/laravel/src/Console/CanioRuntimeInstallCommand.php +++ b/packages/laravel/src/Console/CanioRuntimeInstallCommand.php @@ -27,7 +27,7 @@ public function handle(StagehandReleaseInstaller $installer): int $os = $installer->resolveOperatingSystem($this->option('os')); $arch = $installer->resolveArchitecture($this->option('arch')); - $this->line(sprintf('Downloading %s for %s/%s…', $tag, $os, $arch)); + $this->line(sprintf('Downloading %s for %s/%s...', $tag, $os, $arch)); $result = $installer->install( config: $config, diff --git a/packages/laravel/src/Support/PackageVersion.php b/packages/laravel/src/Support/PackageVersion.php index e8fedd7..4ba3fe9 100644 --- a/packages/laravel/src/Support/PackageVersion.php +++ b/packages/laravel/src/Support/PackageVersion.php @@ -6,7 +6,7 @@ final class PackageVersion { - public const TAG = 'v1.0.1'; + public const TAG = 'v1.0.2'; public static function label(): string { diff --git a/packages/laravel/src/Support/StagehandBinaryCompatibility.php b/packages/laravel/src/Support/StagehandBinaryCompatibility.php new file mode 100644 index 0000000..8e5b713 --- /dev/null +++ b/packages/laravel/src/Support/StagehandBinaryCompatibility.php @@ -0,0 +1,49 @@ + + */ + private const REQUIRED_SERVE_FLAGS = [ + 'allow-private-targets', + 'request-body-limit-bytes', + ]; + + public function assertCompatible(string $binary): void + { + $process = new Process([$binary, 'serve', '--help']); + $process->setTimeout(10); + $process->run(); + + $this->assertCompatibleHelp( + $process->getOutput().PHP_EOL.$process->getErrorOutput(), + $binary, + ); + } + + public function assertCompatibleHelp(string $help, string $binary): void + { + $missing = array_values(array_filter( + self::REQUIRED_SERVE_FLAGS, + static fn (string $flag): bool => ! str_contains($help, '-'.$flag), + )); + + if ($missing === []) { + return; + } + + throw new RuntimeException(sprintf( + 'Stagehand binary %s is incompatible with this Canio package. Missing serve flags: %s.', + $binary, + implode(', ', array_map(static fn (string $flag): string => '--'.$flag, $missing)), + )); + } +} diff --git a/packages/laravel/src/Support/StagehandBinaryResolver.php b/packages/laravel/src/Support/StagehandBinaryResolver.php index 0b7d1ae..3eba3cc 100644 --- a/packages/laravel/src/Support/StagehandBinaryResolver.php +++ b/packages/laravel/src/Support/StagehandBinaryResolver.php @@ -27,7 +27,7 @@ public function resolve(array $config, string $workingDirectory): string if ($this->looksLikePath($configured)) { $candidate = $this->normalizePath($configured, $workingDirectory); - if (is_file($candidate) && is_executable($candidate)) { + if ($this->isRunnableFile($candidate)) { return $candidate; } @@ -46,7 +46,7 @@ public function resolve(array $config, string $workingDirectory): string if ($installPath !== '') { $installedBinary = $this->normalizePath($installPath, $workingDirectory); - if (is_file($installedBinary) && is_executable($installedBinary)) { + if ($this->isRunnableFile($installedBinary)) { return $installedBinary; } @@ -69,6 +69,8 @@ public function resolve(array $config, string $workingDirectory): string private function looksLikePath(string $binary): bool { return str_contains($binary, DIRECTORY_SEPARATOR) + || str_contains($binary, '/') + || str_contains($binary, '\\') || str_starts_with($binary, '.') || str_starts_with($binary, '~'); } @@ -81,10 +83,31 @@ private function normalizePath(string $binary, string $workingDirectory): string return ($home !== '' ? rtrim($home, DIRECTORY_SEPARATOR) : '').DIRECTORY_SEPARATOR.ltrim(substr($binary, 2), DIRECTORY_SEPARATOR); } - if (str_starts_with($binary, DIRECTORY_SEPARATOR)) { + if ($this->isAbsolutePath($binary)) { return $binary; } return rtrim($workingDirectory, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.ltrim($binary, DIRECTORY_SEPARATOR); } + + private function isAbsolutePath(string $path): bool + { + return str_starts_with($path, DIRECTORY_SEPARATOR) + || str_starts_with($path, '/') + || preg_match('/^[A-Za-z]:[\/\\\\]/', $path) === 1; + } + + private function isRunnableFile(string $path): bool + { + if (! is_file($path)) { + return false; + } + + if (is_executable($path)) { + return true; + } + + return PHP_OS_FAMILY === 'Windows' + && in_array(strtolower(pathinfo($path, PATHINFO_EXTENSION)), ['bat', 'cmd', 'exe'], true); + } } diff --git a/packages/laravel/src/Support/StagehandReleaseInstaller.php b/packages/laravel/src/Support/StagehandReleaseInstaller.php index 9a0b2b4..b5159d4 100644 --- a/packages/laravel/src/Support/StagehandReleaseInstaller.php +++ b/packages/laravel/src/Support/StagehandReleaseInstaller.php @@ -10,6 +10,14 @@ final class StagehandReleaseInstaller { + private readonly StagehandBinaryCompatibility $compatibility; + + public function __construct( + ?StagehandBinaryCompatibility $compatibility = null, + ) { + $this->compatibility = $compatibility ?? new StagehandBinaryCompatibility; + } + /** * @param array $config */ @@ -85,6 +93,14 @@ public function install( @chmod($installPath, 0755); } + try { + $this->compatibility->assertCompatible($installPath); + } catch (RuntimeException $exception) { + File::delete($installPath); + + throw $exception; + } + return new StagehandInstallResult( tag: $tag, os: $resolvedOs, @@ -153,7 +169,7 @@ public function resolveInstallPath(string $path, string $os): string throw new RuntimeException('Install path is empty. Set CANIO_RUNTIME_INSTALL_PATH or pass a path.'); } - $resolved = str_starts_with($trimmed, DIRECTORY_SEPARATOR) + $resolved = $this->isAbsolutePath($trimmed) ? $trimmed : base_path($trimmed); @@ -164,6 +180,13 @@ public function resolveInstallPath(string $path, string $os): string return $resolved; } + private function isAbsolutePath(string $path): bool + { + return str_starts_with($path, DIRECTORY_SEPARATOR) + || str_starts_with($path, '/') + || preg_match('/^[A-Za-z]:[\/\\\\]/', $path) === 1; + } + public function assetName(string $tag, string $os, string $arch): string { return sprintf( diff --git a/packages/laravel/tests/Feature/InstallRuntimeCommandTest.php b/packages/laravel/tests/Feature/InstallRuntimeCommandTest.php index 3c3d1dd..dbad54a 100644 --- a/packages/laravel/tests/Feature/InstallRuntimeCommandTest.php +++ b/packages/laravel/tests/Feature/InstallRuntimeCommandTest.php @@ -8,18 +8,20 @@ it('downloads and installs the matching stagehand binary', function () { $directory = sys_get_temp_dir().'/canio-install-'.bin2hex(random_bytes(6)); - $binaryPath = $directory.'/bin/stagehand'; - $contents = "fake-stagehand-binary\n"; + $binaryPath = $directory.'/bin/stagehand'.(PHP_OS_FAMILY === 'Windows' ? '.bat' : ''); + $contents = PHP_OS_FAMILY === 'Windows' + ? "@echo off\r\necho Usage of serve:\r\necho -allow-private-targets\r\necho -request-body-limit-bytes int\r\nexit /b 1\r\n" + : "#!/usr/bin/env sh\necho 'Usage of serve:'\necho ' -allow-private-targets'\necho ' -request-body-limit-bytes int'\nexit 1\n"; $checksum = hash('sha256', $contents); config()->set('canio.runtime.release.repository', 'oxhq/canio'); config()->set('canio.runtime.release.base_url', 'https://github.com'); Http::fake([ - 'https://github.com/oxhq/canio/releases/download/v1.0.1/checksums.txt' => Http::response( - "{$checksum} stagehand_v1.0.1_linux_amd64\n", + 'https://github.com/oxhq/canio/releases/download/v1.0.2/checksums.txt' => Http::response( + "{$checksum} stagehand_v1.0.2_linux_amd64\n", ), - 'https://github.com/oxhq/canio/releases/download/v1.0.1/stagehand_v1.0.1_linux_amd64' => Http::response( + 'https://github.com/oxhq/canio/releases/download/v1.0.2/stagehand_v1.0.2_linux_amd64' => Http::response( $contents, 200, ['Content-Type' => 'application/octet-stream'], @@ -27,20 +29,20 @@ ]); $this->artisan('canio:runtime:install', [ - 'version' => 'v1.0.1', + 'version' => 'v1.0.2', '--path' => $binaryPath, '--os' => 'linux', '--arch' => 'amd64', ]) - ->expectsOutput('Downloading v1.0.1 for linux/amd64…') - ->expectsOutput(sprintf('Installed Stagehand v1.0.1 to %s', $binaryPath)) + ->expectsOutput('Downloading v1.0.2 for linux/amd64...') + ->expectsOutput(sprintf('Installed Stagehand v1.0.2 to %s', $binaryPath)) ->assertSuccessful(); expect(File::exists($binaryPath))->toBeTrue() ->and(File::get($binaryPath))->toBe($contents); Http::assertSent(fn (Request $request): bool => str_contains($request->url(), 'checksums.txt')); - Http::assertSent(fn (Request $request): bool => str_contains($request->url(), 'stagehand_v1.0.1_linux_amd64')); + Http::assertSent(fn (Request $request): bool => str_contains($request->url(), 'stagehand_v1.0.2_linux_amd64')); File::deleteDirectory($directory); }); diff --git a/packages/laravel/tests/Unit/StagehandBinaryCompatibilityTest.php b/packages/laravel/tests/Unit/StagehandBinaryCompatibilityTest.php new file mode 100644 index 0000000..ba20f59 --- /dev/null +++ b/packages/laravel/tests/Unit/StagehandBinaryCompatibilityTest.php @@ -0,0 +1,28 @@ +assertCompatibleHelp(implode(PHP_EOL, [ + 'Usage of serve:', + ' -allowed-target-hosts string', + ' -allow-private-targets', + ' -request-body-limit-bytes int', + ]), 'stagehand'); + + expect(true)->toBeTrue(); +}); + +it('rejects stale stagehand binaries that do not expose required serve flags', function () { + $compatibility = new StagehandBinaryCompatibility; + + $compatibility->assertCompatibleHelp(implode(PHP_EOL, [ + 'Usage of serve:', + ' -allowed-target-hosts string', + ' -request-logging', + ]), 'stagehand_v1.0.1_windows_amd64.exe'); +})->throws(RuntimeException::class, 'Stagehand binary stagehand_v1.0.1_windows_amd64.exe is incompatible with this Canio package. Missing serve flags: --allow-private-targets, --request-body-limit-bytes.'); diff --git a/packages/laravel/tests/Unit/StagehandBinaryResolverTest.php b/packages/laravel/tests/Unit/StagehandBinaryResolverTest.php index 849bbf3..9ef496d 100644 --- a/packages/laravel/tests/Unit/StagehandBinaryResolverTest.php +++ b/packages/laravel/tests/Unit/StagehandBinaryResolverTest.php @@ -7,20 +7,33 @@ it('resolves binaries from the configured install path', function () { $workspace = sys_get_temp_dir().'/canio-binary-resolver-'.bin2hex(random_bytes(6)); - $binaryPath = $workspace.'/runtime/bin/stagehand'; + $binaryPath = $workspace.'/runtime/bin/stagehand'.(PHP_OS_FAMILY === 'Windows' ? '.bat' : ''); File::ensureDirectoryExists(dirname($binaryPath)); - File::put($binaryPath, "#!/bin/sh\nexit 0\n"); + File::put($binaryPath, PHP_OS_FAMILY === 'Windows' ? "@echo off\r\nexit /b 0\r\n" : "#!/bin/sh\nexit 0\n"); @chmod($binaryPath, 0755); $resolver = new StagehandBinaryResolver; try { - expect($resolver->resolve([ + $resolved = str_replace('\\', '/', $resolver->resolve([ 'binary' => 'stagehand', 'install_path' => 'runtime/bin/stagehand', - ], $workspace))->toBe($binaryPath); + ], $workspace)); + + $expected = str_replace('\\', '/', $binaryPath); + + expect(PHP_OS_FAMILY === 'Windows' ? strtolower($resolved) : $resolved) + ->toBe(PHP_OS_FAMILY === 'Windows' ? strtolower($expected) : $expected); } finally { File::deleteDirectory($workspace); } }); + +it('resolves absolute drive-letter paths on windows', function () { + $resolver = new StagehandBinaryResolver; + $reflection = new ReflectionMethod($resolver, 'normalizePath'); + + expect($reflection->invoke($resolver, 'C:/canio/bin/stagehand.exe', 'D:/app')) + ->toBe('C:/canio/bin/stagehand.exe'); +}); diff --git a/packages/laravel/tests/Unit/StagehandServeCommandBuilderTest.php b/packages/laravel/tests/Unit/StagehandServeCommandBuilderTest.php index d232435..51274b2 100644 --- a/packages/laravel/tests/Unit/StagehandServeCommandBuilderTest.php +++ b/packages/laravel/tests/Unit/StagehandServeCommandBuilderTest.php @@ -10,12 +10,12 @@ config()->set('app.key', 'base64:'.base64_encode(str_repeat('b', 32))); $workspace = sys_get_temp_dir().'/canio-serve-builder-'.bin2hex(random_bytes(6)); - $binaryPath = $workspace.'/bin/stagehand'; + $binaryPath = $workspace.'/bin/stagehand'.(PHP_OS_FAMILY === 'Windows' ? '.bat' : ''); $statePath = $workspace.'/state'; $logPath = $workspace.'/logs/runtime.log'; File::ensureDirectoryExists(dirname($binaryPath)); - File::put($binaryPath, "#!/bin/sh\nexit 0\n"); + File::put($binaryPath, PHP_OS_FAMILY === 'Windows' ? "@echo off\r\nexit /b 0\r\n" : "#!/bin/sh\nexit 0\n"); @chmod($binaryPath, 0755); $builder = app(StagehandServeCommandBuilder::class); @@ -44,12 +44,12 @@ config()->set('app.key', 'base64:'.base64_encode(str_repeat('c', 32))); $workspace = sys_get_temp_dir().'/canio-serve-builder-'.bin2hex(random_bytes(6)); - $binaryPath = $workspace.'/bin/stagehand'; + $binaryPath = $workspace.'/bin/stagehand'.(PHP_OS_FAMILY === 'Windows' ? '.bat' : ''); $statePath = $workspace.'/state'; $logPath = $workspace.'/logs/runtime.log'; File::ensureDirectoryExists(dirname($binaryPath)); - File::put($binaryPath, "#!/bin/sh\nexit 0\n"); + File::put($binaryPath, PHP_OS_FAMILY === 'Windows' ? "@echo off\r\nexit /b 0\r\n" : "#!/bin/sh\nexit 0\n"); @chmod($binaryPath, 0755); $builder = app(StagehandServeCommandBuilder::class); diff --git a/runtime/stagehand/internal/browser/pool.go b/runtime/stagehand/internal/browser/pool.go index 68cc007..6c2a6fa 100644 --- a/runtime/stagehand/internal/browser/pool.go +++ b/runtime/stagehand/internal/browser/pool.go @@ -95,13 +95,13 @@ func NewPool(factory Factory, opts Options) (*Pool, error) { lifecycleCtx, lifecycleCancel := context.WithCancel(context.Background()) return &Pool{ - factory: factory, - opts: opts, - lifecycleCtx: lifecycleCtx, + factory: factory, + opts: opts, + lifecycleCtx: lifecycleCtx, lifecycleCancel: lifecycleCancel, - notify: make(chan struct{}), - done: make(chan struct{}), - slots: make(map[int]*slot), + notify: make(chan struct{}), + done: make(chan struct{}), + slots: make(map[int]*slot), }, nil } @@ -132,10 +132,10 @@ func (p *Pool) Acquire(ctx context.Context) (*Lease, error) { return nil, err } - var staleSlot *slot - p.mu.Lock() - if p.closed { - p.mu.Unlock() + var staleSlot *slot + p.mu.Lock() + if p.closed { + p.mu.Unlock() return nil, ErrPoolClosed } @@ -295,21 +295,23 @@ func (p *Pool) startSlot(ctx context.Context, leased bool) (bool, *slot, error) } slotCtx, slotCancel := context.WithCancel(p.lifecycleCtx) - startupDone := make(chan struct{}) - go func() { - select { - case <-ctx.Done(): - slotCancel() - case <-startupDone: - case <-slotCtx.Done(): - } - }() + cancelStartup := context.AfterFunc(ctx, slotCancel) process, err := p.factory.Start(slotCtx, id) - close(startupDone) + cancelStartup() p.mu.Lock() p.starting-- + if err == nil && slotCtx.Err() != nil { + if process != nil { + process.Close() + } + err = ctx.Err() + if err == nil { + err = slotCtx.Err() + } + } + if err != nil { slotCancel() p.signalLocked() diff --git a/runtime/stagehand/internal/browser/process.go b/runtime/stagehand/internal/browser/process.go index b0c3ee2..eab84f3 100644 --- a/runtime/stagehand/internal/browser/process.go +++ b/runtime/stagehand/internal/browser/process.go @@ -113,7 +113,7 @@ func allocatorOptions(cfg config.RuntimeConfig, id int) ([]chromedp.ExecAllocato } if dir := strings.TrimSpace(cfg.UserDataDir); dir != "" { - slotDir := filepath.Join(dir, "browser-"+sanitizeSlotID(id)) + slotDir := slotUserDataDir(dir, id) if err := os.MkdirAll(slotDir, 0o755); err != nil { return nil, err } @@ -141,6 +141,14 @@ func allocatorOptions(cfg config.RuntimeConfig, id int) ([]chromedp.ExecAllocato return opts, nil } +func slotUserDataDir(base string, id int) string { + return filepath.Join(strings.TrimSpace(base), processProfileNamespace(), "browser-"+sanitizeSlotID(id)) +} + +func processProfileNamespace() string { + return fmt.Sprintf("stagehand-%d", os.Getpid()) +} + func sanitizeSlotID(id int) string { return strings.TrimSpace(fmt.Sprintf("%03d", id)) } diff --git a/runtime/stagehand/internal/browser/process_test.go b/runtime/stagehand/internal/browser/process_test.go new file mode 100644 index 0000000..1f5788b --- /dev/null +++ b/runtime/stagehand/internal/browser/process_test.go @@ -0,0 +1,19 @@ +package browser + +import ( + "os" + "path/filepath" + "strconv" + "testing" +) + +func TestSlotUserDataDirIsNamespacedByStagehandProcess(t *testing.T) { + base := filepath.Join("runtime", "chromium-profile") + + got := slotUserDataDir(base, 7) + want := filepath.Join(base, "stagehand-"+strconv.Itoa(os.Getpid()), "browser-007") + + if got != want { + t.Fatalf("slotUserDataDir() = %q, want %q", got, want) + } +} diff --git a/runtime/stagehand/internal/version/version.go b/runtime/stagehand/internal/version/version.go index 410c4d3..6e35d1c 100644 --- a/runtime/stagehand/internal/version/version.go +++ b/runtime/stagehand/internal/version/version.go @@ -1,3 +1,3 @@ package version -const Value = "v1.0.1" +const Value = "v1.0.2" diff --git a/scripts/smoke-launch.sh b/scripts/smoke-launch.sh index bbe8e47..039126b 100755 --- a/scripts/smoke-launch.sh +++ b/scripts/smoke-launch.sh @@ -6,7 +6,7 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PACKAGE_CONSTRAINT="${CANIO_PACKAGE_CONSTRAINT:-^1.0}" PACKAGE_SOURCE_MODE="${CANIO_PACKAGE_SOURCE_MODE:-vcs}" PACKAGE_SOURCE_URL="${CANIO_PACKAGE_SOURCE_URL:-}" -RUNTIME_RELEASE_VERSION="${CANIO_RUNTIME_RELEASE_VERSION:-v1.0.1}" +RUNTIME_RELEASE_VERSION="${CANIO_RUNTIME_RELEASE_VERSION:-v1.0.2}" RUNTIME_RELEASE_REPOSITORY="${CANIO_RUNTIME_RELEASE_REPOSITORY:-oxhq/canio}" RUNTIME_RELEASE_SOURCE="${CANIO_RUNTIME_RELEASE_SOURCE:-local}" RUNTIME_RELEASE_BASE_URL="${CANIO_RUNTIME_RELEASE_BASE_URL:-}"