Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
12 changes: 12 additions & 0 deletions .github/workflows/releases.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ jobs:
/tmp/${{ github.event.repository.name }}-${{ steps.tag.outputs.tag }}.zip
/tmp/fair-dist/*

- name: Upload to Fastly Object Storage
# note the plugin filename is always 'fair-connect', regardless of the repo name or slug
run: |
aws s3 cp /tmp/${{ github.event.repository.name }}-${{ steps.tag.outputs.tag }}.zip s3://download.fair.pm/release/fair-connect-${{ steps.tag.outputs.tag }}.zip
aws s3 cp --recursive --exclude '*' --include '*.zip' /tmp/fair-dist s3://download.fair.pm/release/
env:
AWS_ACCESS_KEY_ID: ${{ secrets.DOWNLOAD_BUCKET_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DOWNLOAD_BUCKET_SECRET_KEY }}
AWS_DEFAULT_REGION: 'us-west'
AWS_ENDPOINT_URL: 'https://us-west.object.fastlystorage.app'
AWS_REQUEST_CHECKSUM_CALCULATION: 'WHEN_REQUIRED'

- name: Build provenance attestation
uses: actions/attest-build-provenance@v2
with:
Expand Down
45 changes: 34 additions & 11 deletions RELEASE.MD
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Releasing a new version of FAIR Connect

## 0. Ensure adequate permissions
To perform a release of FAIR Connect, you need the following:
- `write` or higher permissions on the [FAIR Connect repository](https://github.com/fairpm/fair-plugin)
- `maintain` or higher permissions on the [TSC repository](https://github.com/fairpm/tsc) (this is required to post a Discussion in the Announcements category per step 7.3.)

## 1. Verify milestone readiness

Before starting the release process, ensure that the milestone for the upcoming release is finalized and clear.
Expand Down Expand Up @@ -63,7 +68,7 @@ Once the milestone contains no open issues or pull requests, the release is read

7. Continue to the next step once all workflows finish.

## 5. Create a new release on GitHub and update CHANGELOG.md
## 5. Create a new release on GitHub

1. From the repository’s main page, click the **Releases** link — or go directly to the [Releases page](https://github.com/fairpm/fair-plugin/releases).

Expand All @@ -79,25 +84,43 @@ Once the milestone contains no open issues or pull requests, the release is read
- Leave **Previous tag** set to `Auto`.
- Click **Generate release notes**.
- Review and edit the generated notes as needed.
- Copy/paste the release notes into the **CHANGELOG.md** file under the new version heading and date.
- Click the **Save draft** button.

> [!TIP]
> You can add additional information directly in the **Describe this release** field.
> If a teammate is preparing a release post for FAIR.pm, coordinate with them to include any relevant details.

6. Check **Set as the latest release**.
## 6. Update the changelog before publishing the GitHub release

1. In a new browser tab, open [`CHANGELOG.md`](/CHANGELOG.md).

2. Click the pencil icon to edit the file directly in the browser.

3. Copy the release notes into `CHANGELOG.md` under the new version heading and date (e.g., `1.2.0 / 2025-12-11`).

4. Click the **Commit changes** button. In the panel that opens:
- Select **Create a new branch for this commit and start a pull request**.
- Enter a branch name or use the default (e.g., `update-changelog-1.2.0`).
- Click the **Sign off and propose changes** button.

5. Create a pull request for the updated `CHANGELOG.md` file.

6. Review, approve, and merge the pull request.


## 7. Finalize and publish the release

1. Return to the **Draft Release** page.

7. Check **Create a discussion for this release** and choose the **Announcements** category.
2. Check **Set as the latest release**.

8. In a new browser tab, go to the repository’s **Actions** tab and confirm all workflows have completed.
3. Check **Create a discussion for this release** and choose the **Announcements** category.

9. Return to the **Draft Release** page and click **Publish release**.
4. In a new browser tab, go to the repository’s **Actions** tab and confirm all workflows have completed.

10. To verify release processing:
- Go to the **Actions** tab.
- Once workflows complete, the release is live.
5. Return to the **Draft Release** page and click **Publish release**. This initiates the remaining release workflows.

11. Verify successful release
6. Verify the release:
- Visit the [Releases page](https://github.com/fairpm/fair-plugin/releases) to confirm latest release.
- Check any site using FAIR Connect to ensure the new version is available.
- Check the API response for the updated version number.
- Verify the updated version number in the API response. (Example URL to check: https://api.fair.pm/git-updater/v1/update-api/?slug=fair-plugin)
1 change: 1 addition & 0 deletions inc/default-repo/namespace.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ function replace_repo_api_urls( $status, $args, $url ) {
if (
! str_contains( $url, 'api.wordpress.org/plugins/' )
&& ! str_contains( $url, 'api.wordpress.org/themes/' )
&& ! str_contains( $url, 'api.wordpress.org/translations/' )
&& ! str_contains( $url, 'api.wordpress.org/core/version-check/' )
) {
return $status;
Expand Down
5 changes: 4 additions & 1 deletion inc/packages/namespace.php
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,9 @@ function cache_did_for_install( array $options ): array {
$did = array_find_key(
$releases,
function ( $release ) use ( $options ) {
if ( ! is_array( $release->artifacts->package ) ) {
return false;
}
$artifact = pick_artifact_by_lang( $release->artifacts->package );
return $artifact && $artifact->url === $options['package'];
}
Expand Down Expand Up @@ -754,7 +757,7 @@ function delete_cached_did_for_install(): void {
*
* This is commonly required for packages from Git hosts.
*
* @param string $source Path of $source.
* @param string|WP_Error $source Path of $source, or a WP_Error object.
* @param string $remote_source Path of $remote_source.
* @param WP_Upgrader $upgrader An Upgrader object.
* @param array $hook_extra Array of hook data.
Expand Down
30 changes: 20 additions & 10 deletions inc/updater/class-lite.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ public function run() {
);
$response = get_site_transient( "git-updater-lite_{$this->file}" );
if ( ! $response ) {
/* Apply filter to API URL.
* Add `channel=development` query arg to URL to get pre-release versions.
*
* @param string $url The API URL.
* @param string $slug The plugin/theme slug
*/
$url = apply_filters( 'git_updater_lite_api_url', $url, $this->slug );
$response = wp_remote_get( $url );
if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) === 404 ) {
return $response;
Expand All @@ -136,11 +143,9 @@ public function run() {
}
$this->api_data->file = $this->file;

/*
* Set transient for 5 minutes as AWS sets 5 minute timeout
* for release asset redirect.
*/
set_site_transient( "git-updater-lite_{$this->file}", $this->api_data, 5 * \MINUTE_IN_SECONDS );
// Set timeout for transient via filter.
$timeout = apply_filters( 'git_updater_lite_transient_timeout', 6 * HOUR_IN_SECONDS, $this->file );
set_site_transient( "git-updater-lite_{$this->file}", $this->api_data, $timeout );
} else {
if ( property_exists( $response, 'error' ) ) {
return new WP_Error( 'repo-no-exist', 'Specified repo does not exist' );
Expand Down Expand Up @@ -178,18 +183,23 @@ function () {
/**
* Correctly rename dependency for activation.
*
* @param string $source Path of $source.
* @param string $remote_source Path of $remote_source.
* @param WP_Upgrader $upgrader An Upgrader object.
* @param array $hook_extra Array of hook data.
* @param string|WP_Error $source Path of $source, or a WP_Error object.
* @param string $remote_source Path of $remote_source.
* @param WP_Upgrader $upgrader An Upgrader object.
* @param array $hook_extra Array of hook data.
*
* @throws TypeError If the type of $upgrader is not correct.
*
* @return string|WP_Error
*/
public function upgrader_source_selection( string $source, string $remote_source, WP_Upgrader $upgrader, $hook_extra = null ) {
public function upgrader_source_selection( $source, string $remote_source, WP_Upgrader $upgrader, $hook_extra = null ) {
global $wp_filesystem;

// Exit early for errors.
if ( is_wp_error( $source ) ) {
return $source;
}

$new_source = $source;

// Exit if installing.
Expand Down
109 changes: 109 additions & 0 deletions inc/updater/class-package.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php
/**
* Package data container.
*
* @package FAIR
*/

namespace FAIR\Updater;

use FAIR\Packages;

/**
* Represents a registered FAIR package (plugin or theme).
*/
abstract class Package {

/**
* The DID of the package.
*
* @var string
*/
public string $did;

/**
* Absolute path to the main file.
*
* @var string
*/
public string $filepath;

/**
* Current installed version.
*
* @var string|null
*/
public ?string $local_version;

/**
* Cached metadata document.
*
* @var \FAIR\Packages\MetadataDocument|null
*/
private $metadata = null;

/**
* Cached release document.
*
* @var \FAIR\Packages\ReleaseDocument|null
*/
private $release = null;

/**
* Constructor.
*
* @param string $did The DID of the package.
* @param string $filepath Absolute path to the main file.
*/
public function __construct( string $did, string $filepath ) {
$this->did = $did;
$this->filepath = $filepath;
$this->local_version = $filepath ? get_file_data( $filepath, [ 'Version' => 'Version' ] )['Version'] : null;
}

/**
* Get the package slug.
*
* @return string The slug (directory name for plugins, stylesheet for themes).
*/
abstract public function get_slug(): string;

/**
* Get the relative path used in update transients.
*
* @return string The relative path.
*/
abstract public function get_relative_path(): string;

/**
* Get the metadata document, fetching and caching if needed.
*
* @return \FAIR\Packages\MetadataDocument|\WP_Error|null
*/
final public function get_metadata() {
if ( $this->metadata === null ) {
$metadata = Packages\fetch_package_metadata( $this->did );
if ( ! is_wp_error( $metadata ) ) {
$this->metadata = $metadata;
}
return $metadata;
}
return $this->metadata;
}

/**
* Get the release document, fetching and caching if needed.
*
* @return \FAIR\Packages\ReleaseDocument|\WP_Error|null
*/
final public function get_release() {
if ( $this->release === null ) {
$release = Packages\get_latest_release_from_did( $this->did );
if ( ! is_wp_error( $release ) ) {
$this->release = $release;
}
return $release;
}
return $this->release;
}
}
32 changes: 32 additions & 0 deletions inc/updater/class-pluginpackage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php
/**
* Plugin package data container.
*
* @package FAIR
*/

namespace FAIR\Updater;

/**
* Represents a registered FAIR plugin.
*/
final class PluginPackage extends Package {

/**
* Get the plugin slug.
*
* @return string The plugin directory name.
*/
public function get_slug(): string {
return dirname( plugin_basename( $this->filepath ) );
}

/**
* Get the relative path used in update transients.
*
* @return string The relative path (e.g., 'my-plugin/my-plugin.php').
*/
public function get_relative_path(): string {
return plugin_basename( $this->filepath );
}
}
32 changes: 32 additions & 0 deletions inc/updater/class-themepackage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php
/**
* Theme package data container.
*
* @package FAIR
*/

namespace FAIR\Updater;

/**
* Represents a registered FAIR theme.
*/
final class ThemePackage extends Package {

/**
* Get the theme slug.
*
* @return string The theme stylesheet (directory name).
*/
public function get_slug(): string {
return basename( dirname( $this->filepath ) );
}

/**
* Get the relative path used in update transients.
*
* @return string The relative path (theme directory name).
*/
public function get_relative_path(): string {
return dirname( plugin_basename( $this->filepath ) );
}
}
Loading