From 4e0127e1c5de31c5e0f13a676410b6cf6a5e14ab Mon Sep 17 00:00:00 2001 From: Luca Tumedei Date: Mon, 23 Feb 2026 15:43:12 +0100 Subject: [PATCH 1/5] build: fix required version in composer.json 7.4 for production, 8.2 for development --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8519896..e88567a 100644 --- a/composer.json +++ b/composer.json @@ -4,9 +4,10 @@ "type": "project", "license": "GPL-2.0-or-later", "require": { - "php": ">=8.2" + "php": ">=7.4" }, "require-dev": { + "php": ">=8.2", "phpunit/phpunit": "^11.0" }, "autoload-dev": { From b389ca74022e31c6f25c0712dc8945f83cc1e32c Mon Sep 17 00:00:00 2001 From: Luca Tumedei Date: Mon, 23 Feb 2026 16:13:00 +0100 Subject: [PATCH 2/5] feat: add phar archive distribution support Add support for distributing slic as a self-contained phar archive, enabling simpler installation and self-update via GitHub Releases. - Add is_phar() and slic_data_dir() to separate writable data paths from the read-only phar filesystem (~/.slic for phar, SLIC_ROOT_DIR for git clone) - Update all writable file references (.env.slic.run, .build-version, .remote-version, .cache, .architecture_*, .env.slic.stacks) to use slic_data_dir() - Add phar self-update in upgrade command via GitHub Releases API with atomic file replacement - Add box.json for humbug/box phar builder - Add GitHub Actions workflow to build and release slic.phar on tag push --- .github/workflows/release-phar.yml | 41 +++++++ .gitignore | 3 + box.json | 36 ++++++ slic.php | 2 +- src/commands/composer.php | 4 +- src/commands/php-version.php | 2 +- src/commands/reset.php | 2 +- src/commands/upgrade.php | 139 +++++++++++++++++++++-- src/slic.php | 176 +++++++++++++++++++++++------ src/stacks.php | 6 +- src/utils.php | 2 +- src/xdebug.php | 2 +- 12 files changed, 361 insertions(+), 54 deletions(-) create mode 100644 .github/workflows/release-phar.yml create mode 100644 box.json diff --git a/.github/workflows/release-phar.yml b/.github/workflows/release-phar.yml new file mode 100644 index 0000000..e40587c --- /dev/null +++ b/.github/workflows/release-phar.yml @@ -0,0 +1,41 @@ +name: Build and release slic.phar + +on: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+' + - 'v[0-9]+.[0-9]+.[0-9]+' + +permissions: + contents: write + +jobs: + build-phar: + name: Build phar and create release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + tools: composer:v2 + extensions: phar + ini-values: phar.readonly=0 + + - name: Install humbug/box + run: composer global require humbug/box:^4.0 + + - name: Build phar + run: box compile + + - name: Verify phar works + run: php slic.phar help + + - name: Create GitHub release + uses: softprops/action-gh-release@v2 + with: + files: slic.phar + generate_release_notes: true diff --git a/.gitignore b/.gitignore index 77766ae..96094b7 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ _plugins # PHPUnit cache .phpunit.result.cache + +# Phar build artifacts +*.phar diff --git a/box.json b/box.json new file mode 100644 index 0000000..823b000 --- /dev/null +++ b/box.json @@ -0,0 +1,36 @@ +{ + "main": "slic.php", + "output": "slic.phar", + "shebang": "#!/usr/bin/env php", + "directories": [ + "src", + "includes", + "containers", + "completions" + ], + "files": [ + "slic.php", + "version.php", + ".env.slic", + "slic-stack.yml", + "slic-stack.site.yml", + "slic-stack.worktree.yml" + ], + "blacklist": [ + "tests", + "docs", + "vendor", + ".github", + ".git", + "_wordpress", + "_plugins", + ".cache", + ".idea", + ".data" + ], + "compactors": [ + "KevinGH\\Box\\Compactor\\Php" + ], + "compression": "NONE", + "chmod": "0755" +} diff --git a/slic.php b/slic.php index 5e1d672..b68408b 100644 --- a/slic.php +++ b/slic.php @@ -291,7 +291,7 @@ $subcommand = $args( 'subcommand', 'help' ); // Both these variables will be used by commands. -$run_settings_file = root( '/.env.slic.run' ); +$run_settings_file = \StellarWP\Slic\slic_data_dir() . '/.env.slic.run'; $cli_name = basename( $argv[0] ); if ( 'help' !== $subcommand ) { diff --git a/src/commands/composer.php b/src/commands/composer.php index b186927..3ad736d 100644 --- a/src/commands/composer.php +++ b/src/commands/composer.php @@ -58,7 +58,7 @@ echo magenta( "Error: set-version requires a Composer version number, either 1 or 2." . PHP_EOL ); exit( 1 ); } - $run_settings_file = root( '/.env.slic.run' ); + $run_settings_file = slic_data_dir() . '/.env.slic.run'; write_env_file( $run_settings_file, [ 'SLIC_COMPOSER_VERSION' => (int) $version ], true ); echo colorize( "Composer version set to $version\n" ); @@ -69,7 +69,7 @@ exit( slic_realtime()( [ 'exec', 'slic', $composer_bin, '--version' ] ) ); case 'reset-version': $version = 2; - $run_settings_file = root( '/.env.slic.run' ); + $run_settings_file = slic_data_dir() . '/.env.slic.run'; write_env_file( $run_settings_file, [ 'SLIC_COMPOSER_VERSION' => (int) $version ], true ); echo colorize( "Composer version reset to default: $default_version\n" ); diff --git a/src/commands/php-version.php b/src/commands/php-version.php index 041ccb8..1dcef94 100644 --- a/src/commands/php-version.php +++ b/src/commands/php-version.php @@ -65,7 +65,7 @@ } // Read .env.slic.run to get runtime value. -$run_env_file = root( '/.env.slic.run' ); +$run_env_file = slic_data_dir() . '/.env.slic.run'; $runtime_version = null; if ( file_exists( $run_env_file ) ) { $run_env = read_env_file( $run_env_file ); diff --git a/src/commands/reset.php b/src/commands/reset.php index d094d86..81e8c4d 100644 --- a/src/commands/reset.php +++ b/src/commands/reset.php @@ -38,7 +38,7 @@ quietly_tear_down_stack(); -$run_settings_file = root( '/.env.slic.run' ); +$run_settings_file = slic_data_dir() . '/.env.slic.run'; echo "Removing {$run_settings_file} ... "; echo ( ! file_exists( $run_settings_file ) || unlink( $run_settings_file ) ) ? light_cyan( 'done' ) diff --git a/src/commands/upgrade.php b/src/commands/upgrade.php index 183f9f9..b75bd72 100644 --- a/src/commands/upgrade.php +++ b/src/commands/upgrade.php @@ -17,14 +17,139 @@ return; } -$today = date( 'Y-m-d' ); -chdir( SLIC_ROOT_DIR ); -$status = passthru( 'git checkout main && git pull' ); +if ( is_phar() ) { + phar_self_update(); +} else { + git_upgrade(); +} + +/** + * Upgrades slic via git pull on the main branch. + */ +function git_upgrade() { + chdir( SLIC_ROOT_DIR ); + $status = passthru( 'git checkout main && git pull' ); -if ( ! $status ) { - unlink( SLIC_ROOT_DIR . '/.remote-version' ); + if ( ! $status ) { + $remote_version_file = slic_data_dir() . '/.remote-version'; + if ( file_exists( $remote_version_file ) ) { + unlink( $remote_version_file ); + } - $status = passthru( 'php slic update' ); + $status = passthru( PHP_BINARY . ' ' . escapeshellarg( $GLOBALS['argv'][0] ) . ' update' ); + } + + exit( $status ); } -exit( $status ); +/** + * Self-updates the phar archive from GitHub Releases. + */ +function phar_self_update() { + $current_version = CLI_VERSION; + + echo colorize( "Checking for updates..." . PHP_EOL ); + + $latest_version = fetch_latest_github_release_version(); + + if ( null === $latest_version ) { + echo magenta( "Error: Could not check for updates. Please try again later." . PHP_EOL ); + exit( 1 ); + } + + if ( version_compare( $latest_version, $current_version, '<=' ) ) { + echo light_cyan( "You are already running the latest version ({$current_version})." . PHP_EOL ); + exit( 0 ); + } + + echo colorize( "Updating from {$current_version} to {$latest_version}..." . PHP_EOL ); + + // Fetch the release to get the slic.phar asset URL. + $context = stream_context_create( [ + 'http' => [ + 'method' => 'GET', + 'header' => "User-Agent: slic-cli\r\nAccept: application/vnd.github.v3+json\r\n", + 'timeout' => 10, + ], + ] ); + + $response = @file_get_contents( 'https://api.github.com/repos/stellarwp/slic/releases/latest', false, $context ); + + if ( false === $response ) { + echo magenta( "Error: Could not fetch release information." . PHP_EOL ); + exit( 1 ); + } + + $release = json_decode( $response, true ); + + if ( ! is_array( $release ) || empty( $release['assets'] ) ) { + echo magenta( "Error: No assets found in the latest release." . PHP_EOL ); + exit( 1 ); + } + + // Find the slic.phar asset. + $phar_url = null; + foreach ( $release['assets'] as $asset ) { + if ( $asset['name'] === 'slic.phar' ) { + $phar_url = $asset['browser_download_url']; + break; + } + } + + if ( null === $phar_url ) { + echo magenta( "Error: slic.phar not found in the latest release assets." . PHP_EOL ); + exit( 1 ); + } + + // Download the new phar. + $download_context = stream_context_create( [ + 'http' => [ + 'method' => 'GET', + 'header' => "User-Agent: slic-cli\r\n", + 'timeout' => 60, + 'follow_location' => true, + ], + ] ); + + $new_phar = @file_get_contents( $phar_url, false, $download_context ); + + if ( false === $new_phar || empty( $new_phar ) ) { + echo magenta( "Error: Failed to download the new phar." . PHP_EOL ); + exit( 1 ); + } + + // Get the path to the currently running phar. + $phar_path = \Phar::running( false ); + + if ( empty( $phar_path ) ) { + echo magenta( "Error: Could not determine the running phar path." . PHP_EOL ); + exit( 1 ); + } + + // Write the new phar to a temp file first, then rename for atomicity. + $tmp_path = $phar_path . '.tmp'; + + if ( false === file_put_contents( $tmp_path, $new_phar ) ) { + echo magenta( "Error: Could not write the new phar to {$tmp_path}." . PHP_EOL ); + exit( 1 ); + } + + // Make the temp file executable. + chmod( $tmp_path, 0755 ); + + // Replace the old phar with the new one. + if ( ! rename( $tmp_path, $phar_path ) ) { + unlink( $tmp_path ); + echo magenta( "Error: Could not replace the phar at {$phar_path}." . PHP_EOL ); + exit( 1 ); + } + + // Clear the cached remote version. + $remote_version_file = slic_data_dir() . '/.remote-version'; + if ( file_exists( $remote_version_file ) ) { + unlink( $remote_version_file ); + } + + echo light_cyan( "Successfully updated slic to version {$latest_version}." . PHP_EOL ); + exit( 0 ); +} diff --git a/src/slic.php b/src/slic.php index 1d1d1ef..df3b402 100644 --- a/src/slic.php +++ b/src/slic.php @@ -10,6 +10,57 @@ require_once __DIR__ . '/xdebug.php'; +/** + * Returns whether the current script is running inside a phar archive. + * + * @return bool + */ +function is_phar() { + return strlen( \Phar::running() ) > 0; +} + +/** + * Returns the directory to use for writable data files. + * + * When running as a phar archive, the phar filesystem is read-only, so writable files + * (.env.slic.run, .build-version, .remote-version, .cache, etc.) must be stored in a + * real filesystem directory. This function returns: + * - When phar: the value of SLIC_DATA_DIR env var, or ~/.slic + * - When git clone: SLIC_ROOT_DIR (the project root) + * + * @return string The absolute path to the writable data directory. + */ +function slic_data_dir() { + if ( ! is_phar() ) { + return SLIC_ROOT_DIR; + } + + $data_dir = getenv( 'SLIC_DATA_DIR' ); + + if ( ! empty( $data_dir ) ) { + if ( ! is_dir( $data_dir ) ) { + mkdir( $data_dir, 0755, true ); + } + + return rtrim( $data_dir, '/' ); + } + + $home = getenv( 'HOME' ) ?: getenv( 'USERPROFILE' ); + + if ( empty( $home ) ) { + // Fallback: use system temp directory. + $home = sys_get_temp_dir(); + } + + $data_dir = $home . '/.slic'; + + if ( ! is_dir( $data_dir ) ) { + mkdir( $data_dir, 0755, true ); + } + + return $data_dir; +} + /** * Get the CLI header. * @@ -287,7 +338,7 @@ function setup_slic_env( $root_dir, $reset = false, $stack_id = null ) { } // Start by loading the run file for all stacks, if any. - $run_file = $root_dir . '/.env.slic.run'; + $run_file = slic_data_dir() . '/.env.slic.run'; $staged_php_version = null; if ( is_file( $run_file ) ) { @@ -369,11 +420,11 @@ function setup_slic_env( $root_dir, $reset = false, $stack_id = null ) { // Set up XDebug host for Linux xdebug_setup_linux_host(); - $default_wp_dir = root( '/_wordpress' ); + $default_wp_dir = slic_data_dir() . '/_wordpress'; $wp_dir = getenv( 'SLIC_WP_DIR' ); - if ( $wp_dir === './_wordpress' || $wp_dir === $default_wp_dir ) { - // Default WordPress directory, inside slic. + if ( $wp_dir === './_wordpress' || $wp_dir === root( '/_wordpress' ) || $wp_dir === $default_wp_dir ) { + // Default WordPress directory, inside slic data dir. $wp_dir = ensure_dir( $default_wp_dir ); } else if ( ! is_dir( $wp_dir ) ) { // Custom WordPress directory, it falls on the user to have it set up correctly. @@ -384,12 +435,12 @@ function setup_slic_env( $root_dir, $reset = false, $stack_id = null ) { $wp_themes_dir = $wp_dir . '/wp-content/themes'; putenv( 'SLIC_WP_DIR=' . $wp_dir ); - putenv( 'SLIC_PLUGINS_DIR=' . ensure_dir( getenv( 'SLIC_PLUGINS_DIR' ) ?: root( '_plugins' ) ) ); + putenv( 'SLIC_PLUGINS_DIR=' . ensure_dir( getenv( 'SLIC_PLUGINS_DIR' ) ?: slic_data_dir() . '/_plugins' ) ); putenv( 'SLIC_THEMES_DIR=' . ensure_dir( getenv( 'SLIC_THEMES_DIR' ) ?: $wp_themes_dir ) ); putenv( 'SLIC_CACHE=' . cache() ); if ( empty( getenv( 'COMPOSER_CACHE_DIR' ) ) ) { - ensure_dir( root( '.cache' ) ); + ensure_dir( slic_data_dir() . '/.cache' ); putenv( 'COMPOSER_CACHE_DIR=' . cache( '/composer' ) ); } @@ -490,7 +541,7 @@ function slic_set_php_version( $version, $require_confirm = false, $skip_rebuild $message .= PHP_EOL . "The PHP version will be set for the current stack."; } else { // Apply this version to all the stacks. - $run_settings_file = root( '/.env.slic.run' ); + $run_settings_file = slic_data_dir() . '/.env.slic.run'; $message .= PHP_EOL . "The PHP version will be set for all stacks."; } @@ -533,7 +584,7 @@ function slic_set_php_version( $version, $require_confirm = false, $skip_rebuild * @return void */ function slic_clear_staged_php_flag() { - $run_settings_file = root( '/.env.slic.run' ); + $run_settings_file = slic_data_dir() . '/.env.slic.run'; write_env_file( $run_settings_file, [ 'SLIC_PHP_VERSION_STAGED' => false ], true ); } @@ -1088,7 +1139,7 @@ function rebuild_stack(): void { * Write the current CLI_VERSION to the build-version file */ function write_build_version() { - file_put_contents( SLIC_ROOT_DIR . '/.build-version', CLI_VERSION ); + file_put_contents( slic_data_dir() . '/.build-version', CLI_VERSION ); } /** @@ -1137,7 +1188,7 @@ function slic_info() { ); // Read .env.slic.run directly to show runtime state. - $run_env_file = root( '/.env.slic.run' ); + $run_env_file = slic_data_dir() . '/.env.slic.run'; $run_env = []; if ( file_exists( $run_env_file ) ) { $run_env = read_env_file( $run_env_file ); @@ -1150,7 +1201,7 @@ function slic_info() { file_exists( $slic_root . '/.env.slic' ) ? " - " . $slic_root . '/.env.slic' : null, file_exists( $slic_root . '/.env.slic.local' ) ? " - " . $slic_root . '/.env.slic.local' : null, file_exists( $target_path . '/.env.slic.local' ) ? " - " . $target_path . '/.env.slic.local' : null, - file_exists( $slic_root . '/.env.slic.run' ) ? " - " . $slic_root . '/.env.slic.run' : null, + file_exists( slic_data_dir() . '/.env.slic.run' ) ? " - " . slic_data_dir() . '/.env.slic.run' : null, ] ) ) . PHP_EOL . PHP_EOL; echo colorize( "Current configuration:" . PHP_EOL ); @@ -1221,7 +1272,7 @@ function composer_cache_status() { * @param callable $args The closure that will produce the current interactive request arguments. */ function slic_handle_composer_cache( callable $args ) { - $run_settings_file = root( '/.env.slic.run' ); + $run_settings_file = slic_data_dir() . '/.env.slic.run'; $toggle = $args( 'toggle', 'status' ); if ( 'status' === $toggle ) { @@ -1274,7 +1325,7 @@ function build_prompt_status() { * @param callable $args The closure that will produce the current interactive request arguments. */ function slic_handle_build_prompt( callable $args ) { - $run_settings_file = root( '/.env.slic.run' ); + $run_settings_file = slic_data_dir() . '/.env.slic.run'; $toggle = $args( 'toggle', 'on' ); if ( 'status' === $toggle ) { @@ -1313,7 +1364,7 @@ function is_interactive() { * @param callable $args The closure that will produce the current interactive request arguments. */ function slic_handle_interactive( callable $args ) { - $run_settings_file = root( '/.env.slic.run' ); + $run_settings_file = slic_data_dir() . '/.env.slic.run'; $toggle = $args( 'toggle', 'on' ); if ( 'status' === $toggle ) { @@ -1666,29 +1717,22 @@ function maybe_prompt_for_repo_update() { $check_date = null; $cli_version = CLI_VERSION; $today = date( 'Y-m-d' ); + $data_dir = slic_data_dir(); - if ( is_file( SLIC_ROOT_DIR . '/.remote-version' ) ) { - list( $check_date, $remote_version ) = explode( ':', file_get_contents( SLIC_ROOT_DIR . '/.remote-version' ) ); + if ( is_file( $data_dir . '/.remote-version' ) ) { + list( $check_date, $remote_version ) = explode( ':', file_get_contents( $data_dir . '/.remote-version' ) ); } if ( empty( $remote_version ) || empty( $check_date ) || $today > $check_date ) { - $current_dir = getcwd(); - chdir( SLIC_ROOT_DIR ); - - $tags = explode( "\n", shell_exec( 'git ls-remote --tags origin' ) ); + $remote_version = fetch_latest_remote_version(); - chdir( $current_dir ); - - foreach ( $tags as &$tag ) { - $tag_parts = explode( '/', $tag ); - $tag = array_pop( $tag_parts ); + if ( null !== $remote_version ) { + file_put_contents( $data_dir . '/.remote-version', "{$today}:{$remote_version}" ); } + } - natsort( $tags ); - - $remote_version = array_pop( $tags ); - - file_put_contents( SLIC_ROOT_DIR . '/.remote-version', "{$today}:{$remote_version}" ); + if ( null === $remote_version ) { + return; } // If the version of the CLI is the same as the most recently built version, bail. @@ -1703,19 +1747,76 @@ function maybe_prompt_for_repo_update() { echo magenta( "****************************************************************" . PHP_EOL ); } +/** + * Fetches the latest remote version from git tags or GitHub Releases API. + * + * @return string|null The latest remote version, or null on failure. + */ +function fetch_latest_remote_version() { + if ( is_phar() ) { + return fetch_latest_github_release_version(); + } + + $current_dir = getcwd(); + chdir( SLIC_ROOT_DIR ); + + $tags = explode( "\n", shell_exec( 'git ls-remote --tags origin' ) ); + + chdir( $current_dir ); + + foreach ( $tags as &$tag ) { + $tag_parts = explode( '/', $tag ); + $tag = array_pop( $tag_parts ); + } + + natsort( $tags ); + + return array_pop( $tags ); +} + +/** + * Fetches the latest release version from the GitHub Releases API. + * + * @return string|null The latest version tag, or null on failure. + */ +function fetch_latest_github_release_version() { + $context = stream_context_create( [ + 'http' => [ + 'method' => 'GET', + 'header' => "User-Agent: slic-cli\r\nAccept: application/vnd.github.v3+json\r\n", + 'timeout' => 5, + ], + ] ); + + $response = @file_get_contents( 'https://api.github.com/repos/stellarwp/slic/releases/latest', false, $context ); + + if ( false === $response ) { + return null; + } + + $data = json_decode( $response, true ); + + if ( ! is_array( $data ) || empty( $data['tag_name'] ) ) { + return null; + } + + return ltrim( $data['tag_name'], 'v' ); +} + /** * If slic stack is out of date, prompt for an execution of slic update. */ function maybe_prompt_for_stack_update() { $build_version = '0.0.1'; $cli_version = CLI_VERSION; + $data_dir = slic_data_dir(); - if ( is_file( SLIC_ROOT_DIR . '/.build-version' ) ) { - $build_version = file_get_contents( SLIC_ROOT_DIR . '/.build-version' ); + if ( is_file( $data_dir . '/.build-version' ) ) { + $build_version = file_get_contents( $data_dir . '/.build-version' ); } // If there isn't a .env.slic.run, this is likely a fresh install. Bail. - if ( ! file_exists( SLIC_ROOT_DIR . '/.env.slic.run' ) ) { + if ( ! file_exists( $data_dir . '/.env.slic.run' ) ) { return; } @@ -1743,7 +1844,7 @@ function maybe_prompt_for_stack_update() { * @param callable $args The closure that will produce the current subdirectories build arguments. */ function slic_handle_build_subdir( callable $args ) { - $run_settings_file = root( '/.env.slic.run' ); + $run_settings_file = slic_data_dir() . '/.env.slic.run'; $toggle = $args( 'toggle', 'on' ); if ( 'status' === $toggle ) { @@ -1889,8 +1990,9 @@ function collect_target_suites() { * @return bool Whether the current system is ARM-based, or not. */ function is_arm64() { - $arm64_architecture_file = __DIR__ . '/../.architecture_arm64'; - $x86_architecture_file = __DIR__ . '/../.architecture_x86'; + $data_dir = slic_data_dir(); + $arm64_architecture_file = $data_dir . '/.architecture_arm64'; + $x86_architecture_file = $data_dir . '/.architecture_x86'; if ( is_file( $arm64_architecture_file ) ) { return true; @@ -1951,7 +2053,7 @@ function setup_architecture_env() { * @return string The absolute path to the created directory or file. */ function cache( $path = '/', $create = true ) { - $cache_root_dir = __DIR__ . '/../.cache'; + $cache_root_dir = slic_data_dir() . '/.cache'; if ( ! is_dir( $cache_root_dir ) && ! mkdir( $cache_root_dir, 0755, true ) && ! is_dir( $cache_root_dir ) ) { echo magenta( "Failed to create cache root directory {$cache_root_dir}." ); diff --git a/src/stacks.php b/src/stacks.php index 5f0ee9d..d67f028 100644 --- a/src/stacks.php +++ b/src/stacks.php @@ -11,7 +11,7 @@ * @return string The absolute path to the .env.slic.stacks file. */ function slic_stacks_registry_file() { - return dirname(__DIR__) . '/.env.slic.stacks'; + return slic_data_dir() . '/.env.slic.stacks'; } /** @@ -497,7 +497,7 @@ function slic_stacks_get_project_name($stack_id) { */ function slic_stacks_get_state_file($stack_id) { $hash = substr(md5($stack_id), 0, 8); - return dirname(__DIR__) . '/.env.slic.run.' . $hash; + return slic_data_dir() . '/.env.slic.run.' . $hash; } /** @@ -918,7 +918,7 @@ function slic_stacks_detect_worktree($path) { * @return bool True if migration was performed, false otherwise. */ function slic_stacks_migrate_legacy() { - $root_dir = dirname( __DIR__ ); + $root_dir = slic_data_dir(); $legacy_run_file = $root_dir . '/.env.slic.run'; $registry_file = slic_stacks_registry_file(); diff --git a/src/utils.php b/src/utils.php index 35c0920..d9d9ede 100644 --- a/src/utils.php +++ b/src/utils.php @@ -733,7 +733,7 @@ function normalize_php_version( $version ) { */ function get_stack_env_file( $stack_id = null ) { if ( null === $stack_id ) { - return root( '.env.slic.run' ); + return slic_data_dir() . '/.env.slic.run'; } // Load stacks.php functions if not already loaded diff --git a/src/xdebug.php b/src/xdebug.php index 496cb34..d6e395f 100644 --- a/src/xdebug.php +++ b/src/xdebug.php @@ -208,7 +208,7 @@ function slic_handle_xdebug( callable $args ) { $run_settings_file = get_stack_env_file( $stack_id ); } else { // Fall back to legacy file if no stack - $run_settings_file = root( '/.env.slic.run' ); + $run_settings_file = slic_data_dir() . '/.env.slic.run'; } $toggle = $args( 'toggle', 'on' ); From c774ed829f908d9b415182a814ed59170a25ca40 Mon Sep 17 00:00:00 2001 From: Luca Tumedei Date: Mon, 23 Feb 2026 16:20:14 +0100 Subject: [PATCH 3/5] ci: add workflow to build and test phar on PRs Builds the phar with PHP 8.1 then tests it on PHP 7.4, 8.1, and 8.2 to verify boot, upgrade check, and that no test/docs files are bundled. --- .github/workflows/build-phar.yml | 64 ++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .github/workflows/build-phar.yml diff --git a/.github/workflows/build-phar.yml b/.github/workflows/build-phar.yml new file mode 100644 index 0000000..98b3e78 --- /dev/null +++ b/.github/workflows/build-phar.yml @@ -0,0 +1,64 @@ +name: Build and test phar + +on: + push: + branches: + - main + - '[0-9]+.[0-9]+.[0-9]+' + pull_request: + +jobs: + build-phar: + name: Build and test phar (PHP ${{ matrix.php-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + php-version: ['7.4', '8.1', '8.2'] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP for building + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + tools: composer:v2 + extensions: phar + ini-values: phar.readonly=0 + + - name: Install humbug/box + run: composer global require humbug/box:^4.0 + + - name: Build phar + run: box compile + + - name: Setup PHP ${{ matrix.php-version }} for testing + if: matrix.php-version != '8.1' + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: phar + + - name: Verify phar boots + run: php slic.phar help + + - name: Verify phar version output + run: php slic.phar help 2>&1 | grep -q "version" + + - name: Verify phar upgrade check + run: php slic.phar upgrade + + - name: Verify no test files in phar + run: | + php -r " + foreach (new RecursiveIteratorIterator(new Phar('slic.phar')) as \$f) { + \$path = (string) \$f; + if (preg_match('#/(tests|docs|\.github)/#', \$path)) { + echo 'FAIL: found excluded file: ' . \$path . PHP_EOL; + exit(1); + } + } + echo 'OK: no test/docs/.github files in phar' . PHP_EOL; + " From 2caab66b925c6d51d6d31b635b94b5c76e848ee6 Mon Sep 17 00:00:00 2001 From: Luca Tumedei Date: Mon, 23 Feb 2026 16:23:32 +0100 Subject: [PATCH 4/5] fix: remove redundant php constraint from require-dev to fix phar min version Box was picking up the require-dev php >=8.2 constraint, causing the phar to reject PHP < 8.2. PHPUnit 11 already enforces this naturally. --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index e88567a..646d615 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,6 @@ "php": ">=7.4" }, "require-dev": { - "php": ">=8.2", "phpunit/phpunit": "^11.0" }, "autoload-dev": { From fa645eab8499a2d3c19434d86f92ecb43175bfb5 Mon Sep 17 00:00:00 2001 From: Luca Tumedei Date: Mon, 23 Feb 2026 16:26:26 +0100 Subject: [PATCH 5/5] fix: update composer.lock platform php to >=7.4 The lock file had "platform": {"php": ">=8.2"} which Box used for the phar requirements checker, blocking PHP < 8.2. --- composer.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.lock b/composer.lock index 8b1057e..cbdb1cc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b388d73aaec46a4546e7ec2780b48839", + "content-hash": "d134efd2fa9b49b4e1273d1277292399", "packages": [], "packages-dev": [ { @@ -1795,7 +1795,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=8.2" + "php": ">=7.4" }, "platform-dev": {}, "plugin-api-version": "2.6.0"