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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ jobs:
run: sudo apt-get update && sudo apt-get install --yes php-cli zip unzip
- name: Test
run: ./bin/test.sh
- name: WordPress acceptance
run: ./bin/test-wordpress.sh
41 changes: 21 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,17 @@ One WordPress plugin, two ways to publish a static site to Spacefast:

## Install

1. In Spacefast, open the Space's WordPress data-source setup and choose
**Connect WordPress**.
2. Download [`spacefast-wordpress.zip`](https://github.com/spacefast/wordpress/releases/latest/download/spacefast-wordpress.zip)
and create the one-time connection.
3. In WordPress, upload and activate the ZIP, then open **Settings → Spacefast**.
4. Paste the complete connection, choose a publishing mode, and test it.

The generated credential is scoped to one Space. It can trigger that Space's
production repository, publish static files to it, and read its version status.
It cannot edit WordPress, change Space settings, manage domains, or access
another Space. The secret is stored in a non-autoloaded WordPress option and is
never displayed after save. A managed host can instead define
`SPACEFAST_WORDPRESS_API_URL`, `SPACEFAST_WORDPRESS_SPACE_ID`, and
`SPACEFAST_WORDPRESS_TOKEN` in `wp-config.php`.
1. Download [`spacefast-wordpress.zip`](https://github.com/spacefast/wordpress/releases/latest/download/spacefast-wordpress.zip), then install and activate it.
2. Open **Settings → Spacefast** and choose a publishing mode.
3. Continue to Spacefast, sign in, and authorize exactly one Team.
4. Back in WordPress, choose the Space. The plugin verifies it before showing **Connected**.

OAuth access is bounded by the user's live Team role, the one Team approved on
the consent screen, and the chosen mode: static mode can publish to Spaces;
headless mode can trigger repository builds. WordPress then selects one Space
inside that Team. Tokens are stored in non-autoloaded options and are never
displayed. `SPACEFAST_WORDPRESS_API_URL` may override the API origin for local
development.

## Static WordPress

Expand All @@ -36,8 +33,9 @@ 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
are additive so they cannot accidentally delete omitted site files; run a full
export after deleting content. Upload transfer is paged and processed one file
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
per Simply Static background step so a large upload does not become one
unbounded WordPress request.

Expand Down Expand Up @@ -70,8 +68,9 @@ The settings page shows the active mode, pending work, delivery status, and the
last build or version ID. It also provides a connection test and appears in
Site Health.

**Disconnect** removes the local secret and pending work. Revoke the matching
WordPress key in Spacefast Developer settings. Uninstall removes all plugin
**Disconnect** revokes refresh access in Spacefast, removes local tokens, and
clears pending work. A short-lived access token may remain valid until it
expires; Connected Apps can revoke the Team grant immediately. Uninstall removes all plugin
options, cron events, locks, notices, and interrupted static-publish state; it
never deletes WordPress content or Simply Static exports.

Expand All @@ -81,11 +80,13 @@ From this directory:

```bash
./bin/test.sh
./bin/test-wordpress.sh
```

This lints every runtime PHP file, runs dependency-free behavioral tests, builds
The first command lints every runtime PHP file, runs dependency-free behavioral tests, builds
the deterministic installable ZIP in `dist/`, checks the archive, and proves a
second build produces identical bytes.
second build produces identical bytes. The second runs activation and integration
acceptance against WordPress 6.5 on PHP 8.1 in Docker.

Private beta supports WordPress 6.5+, PHP 8.1+, and Simply Static 3.8+ for direct
static publishing. Activate it per site; network activation is intentionally
Expand Down
20 changes: 20 additions & 0 deletions bin/test-wordpress.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail

plugin_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
compose_file="${plugin_root}/tests/acceptance/compose.yml"
project_name="spacefast-wordpress-acceptance"

cleanup() {
docker compose --project-name "${project_name}" --file "${compose_file}" down --volumes --remove-orphans
}
trap cleanup EXIT

docker compose --project-name "${project_name}" --file "${compose_file}" up --detach --wait db wordpress
docker compose --project-name "${project_name}" --file "${compose_file}" run --rm wpcli \
wp core install --url=http://wordpress --title=Spacefast --admin_user=admin \
--admin_password=acceptance-password --admin_email=acceptance@example.test
docker compose --project-name "${project_name}" --file "${compose_file}" run --rm wpcli \
wp plugin activate spacefast-wordpress
docker compose --project-name "${project_name}" --file "${compose_file}" run --rm wpcli \
wp eval-file wp-content/plugins/spacefast-wordpress/tests/acceptance/wordpress.php
56 changes: 55 additions & 1 deletion includes/class-spacefast-client.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ public function __construct( ?callable $transport = null, ?callable $upload_tran
*/
private function request( string $method, string $path, ?array $body = null, array $headers = array() ): array {
$settings = Spacefast_Settings::get();
$access_token = ( new Spacefast_OAuth( $this->transport ) )->access_token();
if ( '' === $access_token ) {
return array(
'ok' => false,
'retryable' => false,
'code' => 'reauthorization_required',
'message' => 'Reconnect Spacefast to continue.',
'data' => array(),
);
}
$args = array(
'method' => $method,
'timeout' => 10,
Expand All @@ -27,7 +37,7 @@ private function request( string $method, string $path, ?array $body = null, arr
'limit_response_size' => 65536,
'headers' => array_merge(
array(
'Authorization' => 'Bearer ' . $settings['token'],
'Authorization' => 'Bearer ' . $access_token,
'Accept' => 'application/json',
'User-Agent' => 'Spacefast-WordPress/' . SPACEFAST_WORDPRESS_VERSION,
),
Expand Down Expand Up @@ -63,6 +73,9 @@ private function request( string $method, string $path, ?array $body = null, arr
'code' => 'ok',
'message' => '',
'data' => $data,
'pagination' => isset( $decoded['pagination'] ) && is_array( $decoded['pagination'] )
? $decoded['pagination']
: array(),
);
}

Expand All @@ -83,6 +96,47 @@ private function request( string $method, string $path, ?array $body = null, arr
);
}

/** @return array{ok:bool,retryable:bool,code:string,message:string,data:array<string,mixed>} */
public function list_teams(): array {
return $this->list_all( '/v1/teams' );
}

/** @return array{ok:bool,retryable:bool,code:string,message:string,data:array<string,mixed>} */
public function list_spaces(): array {
return $this->list_all( '/v1/spaces' );
}

/** @return array{ok:bool,retryable:bool,code:string,message:string,data:array<string,mixed>} */
private function list_all( string $path ): array {
$items = array();
$cursor = '';
for ( $page = 0; $page < 100; $page++ ) {
$query = $path . '?limit=100';
if ( '' !== $cursor ) {
$query .= '&cursor=' . rawurlencode( $cursor );
}
$result = $this->request( 'GET', $query );
if ( ! $result['ok'] ) {
return $result;
}
$items = array_merge( $items, array_values( $result['data'] ) );
$pagination = is_array( $result['pagination'] ?? null ) ? $result['pagination'] : array();
$next_cursor = (string) ( $pagination['nextCursor'] ?? '' );
if ( true !== ( $pagination['hasMore'] ?? false ) || '' === $next_cursor ) {
$result['data'] = $items;
return $result;
}
$cursor = $next_cursor;
}
return array(
'ok' => false,
'retryable' => false,
'code' => 'pagination_limit',
'message' => 'Spacefast returned too many pages while loading choices.',
'data' => array(),
);
}

/**
* @return array{ok:bool,retryable:bool,code:string,message:string,data:array<string,mixed>}
*/
Expand Down
Loading
Loading