diff --git a/README.md b/README.md index 996ab3c..3402c40 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,9 @@ Spacefast adds a delivery step after Simply Static has generated and delivered its files but before cleanup. The step hashes the generated bytes, declares a full export as an exact snapshot through Spacefast's normal version API, uploads only the files Spacefast requests, waits for activation, and records -whether that version became live. Partial, update, build, and 404-only exports +whether that version became live. Static WordPress exports are public by +default; an explicit `sf.jsonc` in the export is preserved. Partial, update, +build, and 404-only exports are additive so they cannot accidentally delete omitted site files. After a deletion, the plugin blocks partial publishing until a full export removes old files safely. Upload transfer is paged and processed one file diff --git a/includes/class-spacefast-client.php b/includes/class-spacefast-client.php index 1208d54..a83295b 100644 --- a/includes/class-spacefast-client.php +++ b/includes/class-spacefast-client.php @@ -351,6 +351,9 @@ public function upload_static_file( array $target, string $file_path ): array { } public static function content_type_for_file( string $file_path ): string { + if ( 'jsonc' === strtolower( (string) pathinfo( $file_path, PATHINFO_EXTENSION ) ) ) { + return 'application/json'; + } $filetype = wp_check_filetype( $file_path ); $type = is_array( $filetype ) ? (string) ( $filetype['type'] ?? '' ) : ''; return '' !== $type ? $type : 'application/octet-stream'; diff --git a/includes/class-spacefast-static-publisher.php b/includes/class-spacefast-static-publisher.php index b4f0d4e..d21e9c5 100644 --- a/includes/class-spacefast-static-publisher.php +++ b/includes/class-spacefast-static-publisher.php @@ -123,6 +123,7 @@ public static function step( */ public static function manifest( string $archive_dir ): array { $root = self::archive_root( $archive_dir ); + self::ensure_public_config( $root ); $files = array(); $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $root, FilesystemIterator::SKIP_DOTS ) @@ -162,6 +163,20 @@ public static function reset(): void { delete_option( self::OPTION ); } + private static function ensure_public_config( string $root ): void { + $config = $root . DIRECTORY_SEPARATOR . 'sf.jsonc'; + if ( file_exists( $config ) || is_link( $config ) ) { + if ( ! is_file( $config ) || is_link( $config ) || ! is_readable( $config ) ) { + throw new RuntimeException( 'The generated Spacefast configuration is not a readable file.' ); + } + return; + } + $written = file_put_contents( $config, "{\n \"access\": \"public\"\n}\n", LOCK_EX ); + if ( false === $written ) { + throw new RuntimeException( 'The generated export could not be made public on Spacefast.' ); + } + } + private static function archive_root( string $archive_dir ): string { $root = realpath( $archive_dir ); if ( false === $root || ! is_dir( $root ) || ! is_readable( $root ) ) { diff --git a/readme.txt b/readme.txt index 808ddfc..587cd45 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: spacefast Tags: static site, headless cms, simply static, deployment Requires at least: 6.5 Requires PHP: 8.1 -Stable tag: 0.5.5 +Stable tag: 0.5.6 License: GPLv2 or later Publish a Simply Static export to Spacefast, or rebuild a headless site when WordPress content changes. @@ -31,6 +31,9 @@ OAuth access is limited to the Team you authorize and the mode you choose. WordP == Changelog == += 0.5.6 = +* Make static WordPress exports publicly readable by default while preserving an explicit sf.jsonc access policy. + = 0.5.5 = * Declare and send each exported file's media type so managed hosting accepts binary uploads. diff --git a/spacefast-wordpress.php b/spacefast-wordpress.php index 94f7169..7178ac2 100644 --- a/spacefast-wordpress.php +++ b/spacefast-wordpress.php @@ -3,7 +3,7 @@ * Plugin Name: Spacefast * Plugin URI: https://spacefast.com/ * Description: Publishes static WordPress exports or rebuilds a headless Spacefast site. - * Version: 0.5.5 + * Version: 0.5.6 * Update URI: https://github.com/spacefast/wordpress * Requires at least: 6.5 * Requires PHP: 8.1 @@ -14,7 +14,7 @@ defined( 'ABSPATH' ) || exit; -define( 'SPACEFAST_WORDPRESS_VERSION', '0.5.5' ); +define( 'SPACEFAST_WORDPRESS_VERSION', '0.5.6' ); define( 'SPACEFAST_WORDPRESS_FILE', __FILE__ ); require_once __DIR__ . '/includes/class-spacefast-settings.php'; diff --git a/tests/acceptance/wordpress.php b/tests/acceptance/wordpress.php index b038d3d..90700e7 100644 --- a/tests/acceptance/wordpress.php +++ b/tests/acceptance/wordpress.php @@ -113,7 +113,7 @@ function spacefast_accept( bool $condition, string $message ): void { ); $plugin = get_plugin_data( WP_PLUGIN_DIR . '/spacefast-wordpress/spacefast-wordpress.php', false, false ); -spacefast_accept( '0.5.5' === $plugin['Version'], 'Unexpected plugin version.' ); +spacefast_accept( '0.5.6' === $plugin['Version'], 'Unexpected plugin version.' ); spacefast_accept( 'https://github.com/spacefast/wordpress' === $plugin['UpdateURI'], 'Update URI is missing.' ); Spacefast_Settings::disconnect(); diff --git a/tests/behavior.php b/tests/behavior.php index 3dcbff2..aea1326 100644 --- a/tests/behavior.php +++ b/tests/behavior.php @@ -847,7 +847,7 @@ static function ( string $url ) use ( &$invalid_receipt_requests ): array { file_put_contents( $archive . '/assets/app.js', 'console.log("spacefast")' ); $manifest = Spacefast_Static_Publisher::manifest( $archive ); check( - array( 'assets/app.js', 'index.html' ) === array_column( $manifest, 'path' ), + array( 'assets/app.js', 'index.html', 'sf.jsonc' ) === array_column( $manifest, 'path' ), 'builds a sorted snapshot manifest from generated files' ); check( @@ -855,6 +855,20 @@ static function ( string $url ) use ( &$invalid_receipt_requests ): array { 'hashes the generated bytes instead of trusting exporter metadata' ); check( 'text/javascript' === $manifest[0]['contentType'], 'declares generated asset media types in the publish manifest' ); +check( 'application/json' === $manifest[2]['contentType'], 'declares the generated Spacefast config as JSON' ); +check( + 'public' === ( json_decode( file_get_contents( $archive . '/sf.jsonc' ), true )['access'] ?? '' ), + 'makes a static WordPress export publicly readable by default' +); +$custom_archive = sys_get_temp_dir() . '/spacefast-wordpress-custom-config-' . uniqid(); +mkdir( $custom_archive ); +file_put_contents( $custom_archive . '/index.html', '

Custom

' ); +file_put_contents( $custom_archive . '/sf.jsonc', '{"access":{"public":["/docs/**"]}}' ); +Spacefast_Static_Publisher::manifest( $custom_archive ); +check( + '{"access":{"public":["/docs/**"]}}' === file_get_contents( $custom_archive . '/sf.jsonc' ), + 'preserves an explicit Spacefast access configuration' +); check( 'image/png' === Spacefast_Client::content_type_for_file( '/tmp/preview.png' ), 'sends binary uploads with their media type' ); check( 'application/octet-stream' === Spacefast_Client::content_type_for_file( '/tmp/unknown.custom' ), @@ -883,7 +897,7 @@ static function ( string $url, array $args ) use ( &$static_requests ): array { 'id' => 'upl_static', 'mode' => 'dist', 'expiresAt' => '2030-01-01T00:00:00.000Z', - 'summary' => array( 'upload' => 1, 'reused' => 0, 'ignored' => 0 ), + 'summary' => array( 'upload' => 1, 'reused' => 1, 'ignored' => 0 ), 'targets' => array( array( 'path' => 'assets/app.js', @@ -905,10 +919,10 @@ static function ( string $url, array $args ) use ( &$static_requests ): array { 'id' => 'upl_static', 'mode' => 'dist', 'expiresAt' => '2030-01-01T00:00:00.000Z', - 'summary' => array( 'upload' => 2, 'reused' => 0, 'ignored' => 0 ), - 'targets' => array( - array( - 'path' => 'index.html', + 'summary' => array( 'upload' => 2, 'reused' => 1, 'ignored' => 0 ), + 'targets' => array( + array( + 'path' => 'sf.jsonc', 'method' => 'PUT', 'url' => 'https://uploads.example.test/file', 'headers' => array( 'X-Upload' => 'signed' ), @@ -945,9 +959,13 @@ static function ( string $url, string $method, array $headers, string $file ) us 'attributes the published version to its public WordPress site' ); check( - str_ends_with( $static_requests[1][3], '/index.html' ), + str_ends_with( $static_requests[1][3], '/sf.jsonc' ), 'uploads only the file named by the opaque server target' ); +check( + 'public' === ( json_decode( file_get_contents( $static_requests[1][3] ), true )['access'] ?? '' ), + 'uploads the generated public-access declaration with the export' +); check( str_ends_with( $static_requests[3][3], '/assets/app.js' ), 'uploads the next opaque target after resuming' @@ -1034,7 +1052,11 @@ public function run_static_export() { self::$runs++; return true; } unlink( $archive . '/assets/app.js' ); rmdir( $archive . '/assets' ); unlink( $archive . '/index.html' ); +unlink( $archive . '/sf.jsonc' ); rmdir( $archive ); +unlink( $custom_archive . '/index.html' ); +unlink( $custom_archive . '/sf.jsonc' ); +rmdir( $custom_archive ); Spacefast_Static_Publisher::reset(); fwrite( STDOUT, "Spacefast WordPress behavior tests: PASS\n" );