init
WP Plugin Version & Release / release (push) Failing after 10m54s

This commit is contained in:
Mohamed Youssef
2026-08-02 08:08:26 +03:00
parent 13396847e8
commit d27b4f716f
2 changed files with 272 additions and 83 deletions
+33 -83
View File
@@ -1,95 +1,45 @@
# WP Plugin Auto-Release (Gitea Actions)
# Advanced SEO Meta + AI FAQ Schema
Automatically versions and releases this WordPress plugin whenever the
plugin's `Version:` header changes on `main`. No manual tagging, no
hardcoded plugin name/slug — everything is read straight from the plugin's
own file header.
Advanced SEO Meta + AI FAQ Schema is a WordPress plugin that helps you control how your content appears in search results and AI-powered search experiences.
## What it does
## Features
On every push to `main`, the workflow at
[`.gitea/workflows/wp-plugin-release.yml`](.gitea/workflows/wp-plugin-release.yml) runs:
- Custom SEO title field for posts and pages
- Custom meta description field for better search snippet control
- FAQ builder that lets you add multiple questions and answers
- Automatic JSON-LD FAQPage schema output for AI search engines such as Google SGE and Bing Chat
- Open Graph tags for Facebook and LinkedIn sharing
- Clean meta box in the WordPress editor for easy content optimization
1. **Locate the main plugin file** — scans all `*.php` files for one
containing a `Plugin Name:` header (skips `vendor/`, `node_modules/`,
`build/`).
2. **Extract metadata** — reads `Plugin Name:` and `Version:` from that
file's header comment, then derives a slug from the plugin name
(e.g. `My Cool Plugin``my-cool-plugin`).
3. **Check for a new version** — looks for a git tag `vX.Y.Z` matching the
version found in step 2.
- **Tag already exists** → workflow stops here. Pushing commits without
bumping the version does nothing.
- **Tag doesn't exist** → it's a new release, continue.
4. **Sync `readme.txt`** — updates the `Stable tag:` line to match, if the
file exists, and commits that change back to `main`.
5. **Tag** the commit as `vX.Y.Z` and push the tag.
6. **Build a zip** of the plugin (excluding `.git`, `.gitea`, `.github`,
`node_modules`, `build`), named `<slug>-<version>.zip`.
7. **Publish a Gitea Release** for the new tag with the zip attached as a
downloadable asset.
## What the plugin adds
## Requirements
When editing a post or page, you can fill in:
- A **Gitea Actions runner** (`act_runner`) registered on your instance.
- The runner image needs `rsync`, `zip`, and `git` available (standard on
`ubuntu-latest`-style runner images).
- A **repo secret** named `RELEASE_TOKEN` — a Gitea access token with
repo write / release permissions. Add it under
**Repo → Settings → Secrets**.
- SEO Title
- Meta Description
- FAQ questions and answers
The plugin then outputs:
- A custom page title override
- A meta description tag
- Open Graph meta tags
- Structured FAQ schema in JSON-LD format
## Installation
1. Upload the plugin folder to your WordPress installation's plugins directory.
2. Activate the plugin from the WordPress admin panel.
3. Edit any post or page and look for the "Advanced SEO & AI FAQ Schema" meta box.
## Usage
1. Bump the version in your plugin's main file header:
1. Open a post or page in the editor.
2. Enter a custom SEO title and meta description.
3. Add one or more FAQ entries using the built-in builder.
4. Publish or update the post to make the SEO and schema output available on the front end.
```php
/**
* Plugin Name: My Cool Plugin
* Version: 1.3.0
*/
```
## Notes
2. Commit and push to `main`:
The plugin is designed for posts and pages and works best when used alongside strong on-page content. The FAQ schema is especially useful for improving visibility in AI-driven search experiences.
```bash
git add .
git commit -m "Release 1.3.0"
git push origin main
```
3. The workflow detects the new version, tags `v1.3.0`, builds
`my-cool-plugin-1.3.0.zip`, and publishes it as a Gitea release —
nothing else to do.
## Plugin file requirements
Your main plugin file must use the standard WordPress header format,
with `Plugin Name:` and `Version:` inside the top `/** ... */` comment
block:
```php
<?php
/**
* Plugin Name: My Cool Plugin
* Description: Does cool things.
* Version: 1.3.0
* Author: You
*/
```
If your header format differs significantly (e.g. Version defined only
as a PHP constant with no header line), the parsing step in the workflow
will need a small adjustment.
## Notes & caveats
- **One release per version.** If you push multiple commits without
changing `Version:`, no new tag/release is created — this avoids
duplicate releases on every push.
- **`readme.txt` sync** only runs if the file exists at the repo root;
it's skipped otherwise.
- **Tags are immutable once created** by this workflow — to "redo" a
release, delete the tag and Gitea release first, then push again.
- The zip excludes `.git`, `.gitea`, `.github`, `node_modules`, and
`build` — add more `--exclude` patterns in the "Build plugin zip" step
if you have other dev-only directories (e.g. `tests/`, `.vscode/`).
+239
View File
@@ -0,0 +1,239 @@
<?php
/**
* Plugin Name: Advanced SEO Meta + AI FAQ Schema
* Plugin URI: https://my-dev.pro/advanced-seo-meta
* Description: Full SEO meta box (Title, Description) + built-in FAQ builder with JSON-LD Schema for AI search engines.
* Version: 3.0.0
* Author: MYDev
* Author URI: https://my-dev.pro
* License: GPL-2.0+
* Text Domain: advanced-seo-meta
* Domain Path: /languages
*/
if ( ! defined( 'ABSPATH' ) ) exit;
class Advanced_SEO_Meta {
public function __construct() {
add_action( 'add_meta_boxes', array( $this, 'add_meta_box' ) );
add_action( 'save_post', array( $this, 'save_meta_data' ) );
add_filter( 'pre_get_document_title', array( $this, 'custom_title' ), 10 );
add_action( 'wp_head', array( $this, 'output_meta_tags' ), 1 );
add_action( 'wp_head', array( $this, 'output_og_tags' ), 2 );
add_action( 'wp_head', array( $this, 'output_faq_schema' ), 5 ); // High priority for AI crawlers
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
}
/**
* Load a tiny JS script to handle adding/removing FAQ rows in the admin.
*/
public function enqueue_admin_assets() {
wp_add_inline_script( 'jquery', '
jQuery(document).ready(function($) {
// Add FAQ row
$(document).on("click", ".add-faq-row", function(e) {
e.preventDefault();
var index = $(".faq-row").length;
var row = \'<div class="faq-row" style="background:#f9f9f9;padding:15px;margin-bottom:15px;border:1px solid #ddd;">\' +
\'<p><label>Question:</label><input type="text" name="faq_question[]" style="width:100%;" /></p>\' +
\'<p><label>Answer:</label><textarea name="faq_answer[]" rows="2" style="width:100%;"></textarea></p>\' +
\'<button class="remove-faq-row button button-small">Remove Question</button>\' +
\'</div>\';
$(this).before(row);
});
// Remove FAQ row
$(document).on("click", ".remove-faq-row", function(e) {
e.preventDefault();
$(this).closest(".faq-row").remove();
});
});
' );
}
/**
* Add the main SEO + FAQ meta box.
*/
public function add_meta_box() {
add_meta_box(
'advanced_seo_meta_box',
__( 'Advanced SEO & AI FAQ Schema', 'advanced-seo-meta' ),
array( $this, 'render_meta_box' ),
['post', 'page'],
'normal',
'default'
);
}
/**
* Render the meta box (Title, Description, FAQ builder).
*/
public function render_meta_box( $post ) {
wp_nonce_field( 'advanced_seo_nonce', 'advanced_seo_nonce' );
// Get saved values
$seo_title = get_post_meta( $post->ID, '_seo_title', true );
$seo_description = get_post_meta( $post->ID, '_seo_description', true );
$faq_data = get_post_meta( $post->ID, '_seo_faq', true );
if ( ! is_array( $faq_data ) ) $faq_data = array();
?>
<style>
.seo-field { margin-bottom: 20px; }
.seo-field label { font-weight: 600; display: block; margin-bottom: 5px; }
.seo-field input, .seo-field textarea { width: 100%; max-width: 700px; }
.faq-row { background: #f5f5f5; padding: 15px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 4px; }
</style>
<!-- 1. SEO Title -->
<div class="seo-field">
<label for="seo_title"><?php esc_html_e( 'SEO Title (For Google search results)', 'advanced-seo-meta' ); ?></label>
<input type="text" id="seo_title" name="seo_title" value="<?php echo esc_attr( $seo_title ); ?>" placeholder="e.g., Comprehensive Genomic Profiling (CGP) & WES for Precision Oncology" />
</div>
<!-- 2. Meta Description -->
<div class="seo-field">
<label for="seo_description"><?php esc_html_e( 'Meta Description (150-160 characters)', 'advanced-seo-meta' ); ?></label>
<textarea id="seo_description" name="seo_description" rows="3"><?php echo esc_textarea( $seo_description ); ?></textarea>
</div>
<hr style="margin: 30px 0;" />
<!-- 3. FAQ Builder (For AI Search Engines) -->
<h3 style="margin-top:0;"><?php esc_html_e( 'FAQ Schema for AI Search (Google SGE, Bing Chat)', 'advanced-seo-meta' ); ?></h3>
<p class="description"><?php esc_html_e( 'Add questions and answers here. This will be output as JSON-LD structured data so AI engines can display your answers directly in search results.', 'advanced-seo-meta' ); ?></p>
<div id="faq-container">
<?php if ( ! empty( $faq_data ) ) : ?>
<?php foreach ( $faq_data as $faq ) : ?>
<div class="faq-row">
<p><label>Question:</label><input type="text" name="faq_question[]" value="<?php echo esc_attr( $faq['question'] ); ?>" style="width:100%;" /></p>
<p><label>Answer:</label><textarea name="faq_answer[]" rows="2" style="width:100%;"><?php echo esc_textarea( $faq['answer'] ); ?></textarea></p>
<button class="remove-faq-row button button-small"><?php esc_html_e( 'Remove Question', 'advanced-seo-meta' ); ?></button>
</div>
<?php endforeach; ?>
<?php else : ?>
<!-- Default Example Pre-filled (Your Oncology Content) -->
<div class="faq-row">
<p><label>Question:</label><input type="text" name="faq_question[]" value="What is Comprehensive Genomic Profiling (CGP) in oncology?" style="width:100%;" /></p>
<p><label>Answer:</label><textarea name="faq_answer[]" rows="2" style="width:100%;">Comprehensive Genomic Profiling (CGP) is a next-generation sequencing test that analyzes all classes of genomic alterations across hundreds of cancer-associated genes to identify targeted therapy options, immunotherapy biomarkers, and clinical trial opportunities for cancer patients.</textarea></p>
<button class="remove-faq-row button button-small">Remove Question</button>
</div>
<div class="faq-row">
<p><label>Question:</label><input type="text" name="faq_question[]" value="What is the difference between WES (Whole Exome Sequencing) and CGP?" style="width:100%;" /></p>
<p><label>Answer:</label><textarea name="faq_answer[]" rows="2" style="width:100%;">Whole Exome Sequencing (WES) sequences all protein-coding regions of the entire genome, covering ~20,000 genes. Comprehensive Genomic Profiling (CGP) focuses specifically on a curated panel of known cancer-driver genes, providing deeper coverage (higher read depth) for those specific genes, making it more sensitive for detecting low-frequency tumor mutations in clinical oncology.</textarea></p>
<button class="remove-faq-row button button-small">Remove Question</button>
</div>
<div class="faq-row">
<p><label>Question:</label><input type="text" name="faq_question[]" value="How does precision medicine use genomic profiling?" style="width:100%;" /></p>
<p><label>Answer:</label><textarea name="faq_answer[]" rows="2" style="width:100%;">Precision medicine uses genomic profiling to identify specific genetic mutations driving a patient's disease. By matching these mutations with FDA-approved targeted therapies or immunotherapies, clinicians can deliver highly personalized treatment plans that improve efficacy and reduce adverse side effects compared to traditional chemotherapy.</textarea></p>
<button class="remove-faq-row button button-small">Remove Question</button>
</div>
<div class="faq-row">
<p><label>Question:</label><input type="text" name="faq_question[]" value="Is CGP the same as standard clinical genetics testing?" style="width:100%;" /></p>
<p><label>Answer:</label><textarea name="faq_answer[]" rows="2" style="width:100%;">No. Standard clinical genetics testing typically looks for inherited germline mutations (e.g., BRCA1/2). CGP analyzes somatic (tumor-acquired) mutations within the cancer tissue itself. While both fall under clinical genetics, CGP is specifically used for guiding oncology treatment decisions, whereas standard testing assesses hereditary cancer risk.</textarea></p>
<button class="remove-faq-row button button-small">Remove Question</button>
</div>
<?php endif; ?>
</div>
<button class="add-faq-row button button-primary"><?php esc_html_e( '+ Add New FAQ', 'advanced-seo-meta' ); ?></button>
<?php
}
/**
* Save all the new fields.
*/
public function save_meta_data( $post_id ) {
if ( ! isset( $_POST['advanced_seo_nonce'] ) || ! wp_verify_nonce( $_POST['advanced_seo_nonce'], 'advanced_seo_nonce' ) ) return;
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
if ( ! current_user_can( 'edit_post', $post_id ) ) return;
// Save Title & Description
if ( isset( $_POST['seo_title'] ) ) update_post_meta( $post_id, '_seo_title', sanitize_text_field( $_POST['seo_title'] ) );
if ( isset( $_POST['seo_description'] ) ) update_post_meta( $post_id, '_seo_description', sanitize_textarea_field( $_POST['seo_description'] ) );
// Save FAQ Array
$questions = isset( $_POST['faq_question'] ) ? array_map( 'sanitize_text_field', $_POST['faq_question'] ) : array();
$answers = isset( $_POST['faq_answer'] ) ? array_map( 'wp_kses_post', $_POST['faq_answer'] ) : array(); // Allow basic HTML for answers
$faq_pairs = array();
for ( $i = 0; $i < count( $questions ); $i++ ) {
if ( ! empty( $questions[ $i ] ) && ! empty( $answers[ $i ] ) ) {
$faq_pairs[] = array(
'question' => $questions[ $i ],
'answer' => $answers[ $i ]
);
}
}
update_post_meta( $post_id, '_seo_faq', $faq_pairs );
}
/**
* Override the <title> tag.
*/
public function custom_title( $title ) {
if ( is_singular() ) {
$custom = get_post_meta( get_queried_object_id(), '_seo_title', true );
if ( ! empty( $custom ) ) return $custom;
}
return $title;
}
/**
* Output Description & Keywords.
*/
public function output_meta_tags() {
if ( ! is_singular() ) return;
$id = get_queried_object_id();
if ( $desc = get_post_meta( $id, '_seo_description', true ) ) echo '<meta name="description" content="' . esc_attr( $desc ) . '" />' . "\n";
// (Optional: We leave keywords empty intentionally, but if you insist, uncomment below)
// if ( $kw = get_post_meta( $id, '_seo_keywords', true ) ) echo '<meta name="keywords" content="' . esc_attr( $kw ) . '" />' . "\n";
}
/**
* Output Open Graph (Facebook/LinkedIn).
*/
public function output_og_tags() {
if ( ! is_singular() ) return;
$id = get_queried_object_id();
$post = get_post( $id );
$title = get_post_meta( $id, '_seo_title', true ) ?: $post->post_title;
$desc = get_post_meta( $id, '_seo_description', true ) ?: wp_trim_words( $post->post_content, 30 );
echo '<meta property="og:title" content="' . esc_attr( $title ) . '" />' . "\n";
echo '<meta property="og:description" content="' . esc_attr( $desc ) . '" />' . "\n";
echo '<meta property="og:url" content="' . esc_url( get_permalink() ) . '" />' . "\n";
echo '<meta property="og:type" content="article" />' . "\n";
}
/**
* Output JSON-LD FAQ Schema (The secret weapon for AI search).
*/
public function output_faq_schema() {
if ( ! is_singular() ) return;
$id = get_queried_object_id();
$faq_data = get_post_meta( $id, '_seo_faq', true );
if ( empty( $faq_data ) || ! is_array( $faq_data ) ) return;
$schema = array(
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => array()
);
foreach ( $faq_data as $faq ) {
$schema['mainEntity'][] = array(
'@type' => 'Question',
'name' => $faq['question'],
'acceptedAnswer' => array(
'@type' => 'Answer',
'text' => $faq['answer']
)
);
}
echo '<script type="application/ld+json">' . json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) . '</script>' . "\n";
}
}
new Advanced_SEO_Meta();