Skip to content
Open
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: 1 addition & 1 deletion .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,5 @@ This folder contains the repository's configuration for GitHub, automated CI/CD
- **Purpose:** Audits one representative page per site template on the live network (performance, accessibility, best practices, SEO) and posts the category scores to the site-quality stats endpoint, so regressions show up as a trend. Settings live in [lighthouserc.json](lighthouserc.json).

### 6. [Static Analysis (JS/CSS)](workflows/js-css-lint.yml) (`js-css-lint.yml`)
- **Trigger:** Runs on pull requests and pushes to `trunk` that touch `wordpress.org/public_html/style/`, the Plugin Directory theme (`wporg-plugins-2024`), or the Support theme (`wporg-support-2024`). Any of them runs the job for all three.
- **Trigger:** Runs on pull requests and pushes to `trunk` that touch `wordpress.org/public_html/style/`, the Documentation theme (`wporg-documentation-2022`), the Plugin Directory theme (`wporg-plugins-2024`), or the Support theme (`wporg-support-2024`). Any of them runs the job for all four.
- **Actions:** Installs each package from its committed `package-lock.json`, runs its `lint:js` and `lint:css` scripts (and Prettier for `style/`), then rebuilds and fails if the committed build output differs. To cover another package with a committed build, commit its lockfile, add it to the matrix, and add its path to both triggers.
4 changes: 4 additions & 0 deletions .github/workflows/js-css-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ on:
pull_request:
paths:
- 'wordpress.org/public_html/style/**'
- 'wordpress.org/public_html/wp-content/themes/pub/wporg-documentation-2022/**'
- 'wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/**'
- 'wordpress.org/public_html/wp-content/themes/pub/wporg-support-2024/**'
- .github/workflows/js-css-lint.yml
push:
branches: [trunk]
paths:
- 'wordpress.org/public_html/style/**'
- 'wordpress.org/public_html/wp-content/themes/pub/wporg-documentation-2022/**'
- 'wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/**'
- 'wordpress.org/public_html/wp-content/themes/pub/wporg-support-2024/**'
- .github/workflows/js-css-lint.yml
Expand All @@ -36,6 +38,8 @@ jobs:
- name: Style
dir: wordpress.org/public_html/style
prettier: true
- name: Documentation Theme
dir: wordpress.org/public_html/wp-content/themes/pub/wporg-documentation-2022
- name: Plugin Directory Theme
dir: wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024
- name: Support Theme
Expand Down
34 changes: 34 additions & 0 deletions environments/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,40 @@ npm run browsehappy:env start

**Access:** `http://localhost:8888`

### Documentation

A local instance of [WordPress.org/documentation](https://wordpress.org/documentation/) with the `wporg-documentation-2022` theme and the Support HelpHub plugin.

**Start:**

```bash
npm run documentation:env start
```

First start imports the categories, pages, articles, and WordPress version pages from the live site's REST API, keeping their production IDs. The API only serves rendered content to logged-out requests, so the posts are imported as rendered HTML rather than block markup.

**Re-seed** (clears the import flag, then imports whatever is missing locally; existing posts and terms are not updated):

```bash
npm run documentation:refresh
```

**Access:** `http://localhost:8888`

**WP CLI:**

```bash
npm run documentation:env -- run cli -- wp <command>
```

**Theme assets:** the theme's `build/` directory is committed. After changing anything in `src/`, rebuild from the theme directory:

```bash
cd ../wordpress.org/public_html/wp-content/themes/pub/wporg-documentation-2022
npm install
npm run build
```

### Translate

A local instance of translate.wordpress.org with GlotPress, the `wporg-gp-*` plugins active on production, and the `pub/wporg` theme.
Expand Down
30 changes: 30 additions & 0 deletions environments/documentation/.wp-env.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"core": "WordPress/WordPress#master",
"phpVersion": "8.4",
"testsEnvironment": false,
"plugins": [
"https://downloads.wordpress.org/plugin/gutenberg.zip",
"https://downloads.wordpress.org/plugin/jetpack.zip",
"https://downloads.wordpress.org/plugin/syntaxhighlighter.zip",
"../wordpress.org/public_html/wp-content/plugins/support-helphub"
],
"themes": [
"../wordpress.org/public_html/wp-content/themes/pub/wporg-documentation-2022",
"WordPress/wporg-parent-2021#build"
],
"mappings": {
"wp-content/mu-plugins": "./mocks/mu-plugins",
"wp-content/mu-plugins/pub": "../wordpress.org/public_html/wp-content/mu-plugins/pub",
"wp-content/mu-plugins/wporg-mu-plugins": "WordPress/wporg-mu-plugins#build",
"wp-content/env-bin": "./documentation/bin"
},
"lifecycleScripts": {
"afterStart": "bash documentation/bin/after-start.sh"
},
"config": {
"WP_DEBUG": true,
"JETPACK_DEV_DEBUG": true,
"WP_ENVIRONMENT_TYPE": "local",
"GLOTPRESS_LOCALES_PATH": "/var/www/html/wp-content/mu-plugins/pub/locales/locales.php"
}
}
27 changes: 27 additions & 0 deletions environments/documentation/bin/after-start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash
#
# Runs after wp-env start. Activates the theme, sets up permalinks, and imports
# the Documentation content from WordPress.org.
#

set -euo pipefail

CONFIG="--config documentation/.wp-env.json"
WP="npx wp-env $CONFIG run cli --"

echo "Activating theme..."
$WP wp theme activate wporg-documentation-2022

$WP wp option update blogname "Documentation"
$WP wp option update blogdescription "We’ve got a variety of resources to help you get the most out of WordPress."

# Reapplied on every start because wp-env resets the structure whenever it
# reconfigures WordPress.
echo "Setting up permalinks..."
$WP wp rewrite structure '/%postname%/' --hard

# Idempotent — the import gates itself on the wporg_docs_env_imported option,
# which `npm run documentation:refresh` clears.
$WP wp eval-file wp-content/env-bin/import-content.php --user=admin

echo "Documentation environment ready!"
229 changes: 229 additions & 0 deletions environments/documentation/bin/import-content.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
<?php
/**
* Import Documentation content from WordPress.org for local development.
*
* Copies the categories, pages, articles, and WordPress version pages from the
* live site's REST API. Posts keep their production IDs, so links between them
* and the IDs in production URLs line up locally.
*
* The REST API only exposes rendered content to logged-out requests, so posts
* are imported as rendered HTML rather than block markup.
*
* Usage:
* wp eval-file wp-content/env-bin/import-content.php --user=admin
*
* No strict_types declaration: eval-file evaluates the file inline, where a
* declare() cannot be the first statement.
*
* @package wporg-env
*/

namespace WordPressdotorg\Documentation\Env;

use RuntimeException;
use WP_CLI;

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

const API_BASE = 'https://wordpress.org/documentation/wp-json/wp/v2/';

/**
* Fetches every item from a REST collection, following pagination.
*
* @param string $endpoint Collection route, relative to the wp/v2 namespace.
* @return array[] Decoded items, in API order.
*
* @throws RuntimeException When a page of the collection cannot be fetched.
*/
function fetch_all( string $endpoint ): array {
$items = array();
$page = 1;
$total_pages = 1;

do {
$url = add_query_arg(
array(
'per_page' => 100,
'page' => $page,
),
API_BASE . $endpoint
);
$response = wp_remote_get( $url, array( 'timeout' => 60 ) );

$batch = is_wp_error( $response ) ? null : json_decode( wp_remote_retrieve_body( $response ), true );
if ( 200 !== wp_remote_retrieve_response_code( $response ) || ! is_array( $batch ) ) {
throw new RuntimeException( esc_html( "Could not fetch {$endpoint} (page {$page}) from wordpress.org." ) );
}

$items = array_merge( $items, $batch );
$total_pages = (int) wp_remote_retrieve_header( $response, 'x-wp-totalpages' );
++$page;
} while ( $page <= $total_pages );

return $items;
}

/**
* Imports the terms of a taxonomy, parents before their children.
*
* @param string $endpoint Terms route, relative to the wp/v2 namespace.
* @param string $taxonomy Local taxonomy name.
* @return int[] Local term IDs, keyed by production term ID.
*/
function import_terms( string $endpoint, string $taxonomy ): array {
$terms = fetch_all( $endpoint );
$map = array();

while ( $terms ) {
$deferred = array();

foreach ( $terms as $term ) {
$parent = (int) ( $term['parent'] ?? 0 );
if ( $parent && ! isset( $map[ $parent ] ) ) {
$deferred[] = $term;
continue;
}

$existing = get_term_by( 'slug', $term['slug'], $taxonomy );
if ( $existing ) {
$map[ (int) $term['id'] ] = (int) $existing->term_id;
continue;
}

$result = wp_insert_term(
wp_specialchars_decode( $term['name'], ENT_QUOTES ),
$taxonomy,
array(
'slug' => $term['slug'],
'description' => $term['description'] ?? '',
'parent' => $parent ? $map[ $parent ] : 0,
)
);

if ( is_wp_error( $result ) ) {
WP_CLI::warning( "Could not create {$taxonomy} '{$term['slug']}': " . $result->get_error_message() );

// Its children still import, at the top level.
$map[ (int) $term['id'] ] = 0;
continue;
}

$map[ (int) $term['id'] ] = (int) $result['term_id'];
}

// Whatever is left has a parent the API did not return.
if ( count( $deferred ) === count( $terms ) ) {
WP_CLI::warning( count( $deferred ) . " {$taxonomy} terms skipped: their parents are missing." );
break;
}

$terms = $deferred;
}

WP_CLI::log( sprintf( 'Imported %d %s terms.', count( array_filter( $map ) ), $taxonomy ) );

return $map;
}

/**
* Imports the posts of a post type, keeping their production IDs.
*
* @param string $endpoint Posts route, relative to the wp/v2 namespace.
* @param string $post_type Local post type name.
* @param array[] $taxonomies Term ID maps from import_terms(), keyed by the
* REST field that holds the post's term IDs, e.g.
* `array( 'category' => array( 'taxonomy' => 'category', 'map' => $map ) )`.
* @return void
*/
function import_posts( string $endpoint, string $post_type, array $taxonomies = array() ): void {
$imported = 0;

foreach ( fetch_all( $endpoint ) as $item ) {
if ( get_post( (int) $item['id'] ) ) {
continue;
}

$post_id = wp_insert_post(
array(
'import_id' => (int) $item['id'],
'post_type' => $post_type,
'post_status' => 'publish',
'post_title' => html_entity_decode( $item['title']['rendered'], ENT_QUOTES, 'UTF-8' ),
'post_name' => $item['slug'],
'post_content' => $item['content']['rendered'],
'post_excerpt' => wp_strip_all_tags( $item['excerpt']['rendered'] ?? '' ),
'post_date' => $item['date'],
'post_date_gmt' => $item['date_gmt'],
'post_parent' => (int) ( $item['parent'] ?? 0 ),
'menu_order' => (int) ( $item['menu_order'] ?? 0 ),
),
true
);

if ( is_wp_error( $post_id ) ) {
WP_CLI::warning( "Could not create {$post_type} '{$item['slug']}': " . $post_id->get_error_message() );
continue;
}

foreach ( $taxonomies as $field => $taxonomy ) {
$term_ids = array_filter(
array_map(
static fn ( $id ): int => $taxonomy['map'][ (int) $id ] ?? 0,
(array) ( $item[ $field ] ?? array() )
)
);

if ( $term_ids ) {
wp_set_object_terms( $post_id, array_values( $term_ids ), $taxonomy['taxonomy'] );
}
}

++$imported;
}

WP_CLI::log( "Imported {$imported} {$post_type} posts." );
}

if ( get_option( 'wporg_docs_env_imported' ) ) {
WP_CLI::log( 'Already imported, skipping.' );
return;
}

WP_CLI::log( 'Importing Documentation content from WordPress.org...' );

/*
* Only mark the import done once everything arrived. Every step skips what
* already exists, so an interrupted import resumes on the next start.
*/
try {
$categories = import_terms( 'category', 'category' );
$releases = import_terms( 'helphub_major_release', 'helphub_major_release' );

import_posts( 'pages', 'page' );
import_posts(
'articles',
'helphub_article',
array(
'category' => array(
'taxonomy' => 'category',
'map' => $categories,
),
)
);
import_posts(
'wordpress-versions',
'helphub_version',
array(
'helphub_major_release' => array(
'taxonomy' => 'helphub_major_release',
'map' => $releases,
),
)
);

update_option( 'wporg_docs_env_imported', time() );
} catch ( RuntimeException $e ) {
WP_CLI::warning( $e->getMessage() . ' The import resumes on the next start.' );
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
2 changes: 2 additions & 0 deletions environments/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
"jobs:test:env": "wp-env --config jobs/.wp-env.test.json",
"jobs:test": "npm run jobs:test:env -- start && npm run jobs:test:env -- run cli --env-cwd=wp-content/plugins/jobswp phpunit",
"browsehappy:env": "wp-env --config browsehappy/.wp-env.json",
"documentation:env": "wp-env --config documentation/.wp-env.json",
"documentation:refresh": "npm run documentation:env -- run cli -- wp option delete wporg_docs_env_imported && npm run documentation:env -- run cli -- wp eval-file wp-content/env-bin/import-content.php --user=admin",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
"openverse:test:env": "wp-env --config openverse/.wp-env.test.json",
"openverse:test": "npm run openverse:test:env -- start && npm run openverse:test:env -- run cli --env-cwd=wp-content/themes/wporg-openverse phpunit",
"translate:env": "wp-env --config translate/.wp-env.json",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "wporg/article-list",
"version": "0.1.0",
"title": "Article list",
"category": "design",
"icon": "",
"description": "Article list for a top-level category.",
"textdomain": "wporg",
"supports": {
"html": false
},
"attributes": {
"category": {
"type": "string"
}
},
"editorScript": "file:./index.js",
"style": "file:./style-index.css"
}
Loading
Loading