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
44 changes: 39 additions & 5 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,41 @@ permissions:
contents: read

jobs:
# The template as a user receives it, generated under another slug: no
# placeholder left, no concrete name left, PHP that parses. No site needed.
contract:
name: Contract
runs-on: ubuntu-latest

if: github.repository == 'Pollora/theme-buzz'

steps:
- name: Checkout the theme
uses: actions/checkout@v6

- name: Generate the theme under another slug
run: php bin/ci/install.php --source="$PWD" --themes="$RUNNER_TEMP" --slug=my-journal

- name: Every placeholder is substituted
run: |
if grep -rn '%theme_[a-z_]*%' "$RUNNER_TEMP/my-journal"; then
echo "::error::a placeholder the scaffolder does not substitute is left (see bin/replacements.php)"
exit 1
fi

# The slug is the only concrete name in the template: a buzz/ left in a
# pattern slug or a handle collides with another theme generated from it.
- name: No concrete theme name is left
run: |
# README.md documents the template itself, and names it on purpose.
if grep -rn --exclude=README.md 'buzz/\|Theme\\Buzz' "$RUNNER_TEMP/my-journal"; then
echo "::error::a concrete theme name is left; use %theme_name% / %theme_namespace%"
exit 1
fi

- name: PHP parses
run: find "$RUNNER_TEMP/my-journal" -name '*.php' -print0 | xargs -0 -n1 php -l > /dev/null

browser:
name: Browser (framework ${{ matrix.framework }})
runs-on: ubuntu-latest
Expand Down Expand Up @@ -124,14 +159,13 @@ jobs:
--no-interaction || true
ddev exec wp core is-installed

# The commit under test, copied as it is: Buzz carries no scaffolding
# placeholders. The directory name matters — the Vite build folder is
# named after it.
# The commit under test, with its placeholders substituted as
# pollora:make:theme would (bin/ci/install.php). The directory name
# matters — the Vite build folder is named after it.
- name: Install and build the theme
working-directory: site
run: |
cp -r ../theme themes/buzz
rm -rf themes/buzz/.git themes/buzz/bin themes/buzz/.github
php ../theme/bin/ci/install.php --source="$PWD/../theme" --themes="$PWD/themes" --slug=buzz
ddev exec bash -c 'cd themes/buzz && npm install --no-audit --no-fund && npm run build'
ddev exec wp theme activate buzz
ddev exec wp rewrite flush
Expand Down
36 changes: 29 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ A typographic, journal/magazine [Pollora](https://pollora.dev) theme, built in W
Site Editing** (block theme) mode: real `templates/*.html` and `parts/*.html`, editable in the Site
Editor, instead of Pollora's usual Blade-based template hierarchy.

## Use it

Buzz is one of the templates of `pollora:make:theme`:

```bash
php artisan pollora:make:theme my-journal # then choose "Magazine"
# or, without the prompt
php artisan pollora:make:theme my-journal --repository=Pollora/theme-buzz
```

This repository is that template: its files carry `%theme_name%`, `%theme_namespace%`… which the
command substitutes, so the pattern slugs (`my-journal/masthead`), the namespace
(`Theme\MyJournal`) and the `style.css` header are the new theme's own. The design's vocabulary
— the `buzz-*` classes, the `buzz_card` image size — keeps its name.

## Why a block theme, in a Blade-first framework

Pollora normally resolves every page through its own Blade template hierarchy, and its guidance is
Expand Down Expand Up @@ -77,16 +92,23 @@ edge everywhere) are the design system itself, authored by hand.

## Local development

Develop on a theme generated from this template, never in this repository — its placeholders do
not run:

```bash
npm install
npm run dev # HMR
npm run build # Writes public/build/theme/<theme-folder-name>/…
php artisan pollora:make:theme buzz --repository=Pollora/theme-buzz # in a test project
cd themes/buzz && npm install && npm run dev
# … then package the changes back into this repository:
./bin/package-theme.sh /path/to/project/themes/buzz
git diff # review: only buzz/, Theme\Buzz and the style.css header become placeholders
```

The build's output folder name is derived from the theme's own directory name on disk — so build
from the path WordPress actually serves the theme from (`wp-content/themes/<slug>`), not from a
symlink or a clone under a different name: Node resolves `__dirname` to the real path, and a
mismatch there breaks every asset and font URL silently.
The build's output folder is named after the theme's directory on disk, so build from where
WordPress serves the theme (`themes/<slug>`), not from a symlink: Node resolves `__dirname` to the
real path, and a mismatch breaks every asset and font URL silently.

`bin/` is dropped by the scaffolder. It holds the packaging script, `bin/ci/install.php` (the
commit under test, substituted as `make:theme` would — what CI installs) and the browser tests.

## Status

Expand Down
4 changes: 2 additions & 2 deletions app/Providers/AssetServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Theme\Buzz\Providers;
namespace %theme_namespace%\Providers;

use Illuminate\Support\ServiceProvider;
use Pollora\Support\Facades\Asset;
Expand All @@ -19,7 +19,7 @@ public function register(): void {}
*/
public function boot(): void
{
Asset::add('buzz/script', 'app.js')
Asset::add('%theme_name%/script', 'app.js')
->container('theme')
->toFrontend()
->loadInFooter()
Expand Down
65 changes: 65 additions & 0 deletions bin/ci/install.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env php
<?php

declare(strict_types=1);

/**
* Put the commit under test on a site the way `pollora:make:theme` would.
*
* php bin/ci/install.php --source=<this repository> --themes=<site>/themes [--slug=buzz]
*
* This repository is a template: `style.css` carries `%theme_name%`, the
* provider `%theme_namespace%`, every pattern slug `%theme_name%/…`. A user only
* ever sees them substituted. So CI copies the commit under test, drops what the
* scaffolder drops (bin/, .github/, .git/) and substitutes the placeholders with
* the same table — measuring the branch, as a user would receive it.
*
* It installs. It asserts nothing: the browser tests do that.
*/

require_once dirname(__DIR__).'/replacements.php';

$options = getopt('', ['source:', 'themes:', 'slug::']);

if (! isset($options['source'], $options['themes'])) {
fwrite(STDERR, "Usage: php bin/ci/install.php --source=<repo> --themes=<site>/themes [--slug=buzz]\n");
exit(2);
}

$source = rtrim((string) $options['source'], '/');
$slug = (string) ($options['slug'] ?? 'buzz');
$target = rtrim((string) $options['themes'], '/').'/'.$slug;
$replacements = scaffolderReplacements($slug);
$dropped = ['.git', '.github', 'bin', 'node_modules'];
$binary = ['woff2', 'woff', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'ico'];

if (is_dir($target)) {
fwrite(STDERR, "{$target} already exists.\n");
exit(1);
}

$files = new RecursiveIteratorIterator(
new RecursiveCallbackFilterIterator(
new RecursiveDirectoryIterator($source, FilesystemIterator::SKIP_DOTS),
fn (SplFileInfo $file): bool => ! in_array($file->getFilename(), $dropped, true),
),
);

foreach ($files as $file) {
$relative = substr($file->getPathname(), strlen($source) + 1);
$destination = $target.'/'.$relative;

if (! is_dir(dirname($destination))) {
mkdir(dirname($destination), 0775, true);
}

$contents = (string) file_get_contents($file->getPathname());

if (! in_array(strtolower($file->getExtension()), $binary, true)) {
$contents = strtr($contents, $replacements);
}

file_put_contents($destination, $contents);
}

echo "Installed {$slug} in {$target}\n";
51 changes: 51 additions & 0 deletions bin/package-theme.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/bin/bash
set -euo pipefail

# Package a Buzz theme developed on a site back into this template.
#
# Usage: ./bin/package-theme.sh <site>/themes/buzz
#
# Develop on a theme generated from this template under the slug "buzz"
# (`php artisan pollora:make:theme buzz --repository=Pollora/theme-buzz`), then
# run this: it copies the theme back and turns the slug and namespace into the
# placeholders the scaffolder substitutes. Only the anchored forms are
# replaced — `buzz/` (pattern slugs, handles), `Theme\Buzz` and the style.css
# header — so the design's own vocabulary, the `buzz-*` classes and the
# `buzz_card` image size, is left alone.

SOURCE="${1:?Usage: $0 <site>/themes/buzz}"
TARGET="$(cd "$(dirname "$0")/.." && pwd)"

[ -d "$SOURCE" ] || { echo "Not a directory: $SOURCE"; exit 1; }

rsync -a --delete \
--exclude='.git' --exclude='.github' --exclude='bin/' \
--exclude='node_modules' --exclude='package-lock.json' \
"$SOURCE/" "$TARGET/"

find "$TARGET" -type f \
-not -path "*/.git/*" -not -path "*/bin/*" -not -path "*/.github/*" \
-not -path "*/node_modules/*" -not -name "README.md" \
-not -name "*.woff2" -not -name "*.png" -not -name "*.jpg" \
| while read -r file; do
sed -i \
-e 's|Theme\\Buzz|%theme_namespace%|g' \
-e 's|buzz/|%theme_name%/|g' \
-e "s|'name' => 'buzz'|'name' => '%theme_name%'|g" \
-e "s|'label' => 'buzz'|'label' => '%theme_name%'|g" \
"$file"
done

# The theme header, in style.css only.
sed -i \
-e 's|^Theme Name: .*|Theme Name: %theme_name%|' \
-e 's|^Theme URI: .*|Theme URI: %theme_uri%|' \
-e 's|^Description: .*|Description: %theme_description%|' \
-e 's|^Author: .*|Author: %theme_author%|' \
-e 's|^Author URI: .*|Author URI: %theme_author_uri%|' \
-e 's|^Version: .*|Version: %theme_version%|' \
"$TARGET/style.css"

cd "$TARGET"
git status --short
echo "Review with git diff, then commit."
28 changes: 28 additions & 0 deletions bin/replacements.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

/**
* The placeholders the scaffolder substitutes when it generates a theme.
*
* Mirrors MakeThemeCommand::getReplacements() in the framework, for
* bin/ci/install.php, which substitutes them in the commit under test so CI
* measures the theme as a user receives it, not the template.
*
* @return array<string, string> placeholder => a plausible substituted value
*/
function scaffolderReplacements(string $slug = 'my-theme'): array
{
$studly = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $slug)));

return [
'%theme_name%' => $slug,
'%theme_camel%' => lcfirst($studly),
'%theme_namespace%' => 'Theme\\'.$studly,
'%theme_author%' => 'Pollora',
'%theme_author_uri%' => 'https://pollora.dev',
'%theme_uri%' => 'https://pollora.dev',
'%theme_description%' => 'Theme under test',
'%theme_version%' => '1.0.0',
];
}
2 changes: 1 addition & 1 deletion config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
|
*/

'name' => 'buzz',
'name' => '%theme_name%',

/*
|--------------------------------------------------------------------------
Expand Down
4 changes: 2 additions & 2 deletions config/gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
|
*/
'patterns' => [
'buzz/patterns' => [
'label' => 'Buzz',
'%theme_name%/patterns' => [
'label' => '%theme_name%',
],
],
],
Expand Down
2 changes: 1 addition & 1 deletion parts/footer.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<!-- wp:pattern {"slug":"buzz/colophon"} /-->
<!-- wp:pattern {"slug":"%theme_name%/colophon"} /-->
2 changes: 1 addition & 1 deletion parts/header.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<!-- wp:pattern {"slug":"buzz/masthead"} /-->
<!-- wp:pattern {"slug":"%theme_name%/masthead"} /-->
4 changes: 2 additions & 2 deletions patterns/article.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php
/**
* Title: Article
* Slug: buzz/article
* Categories: buzz/patterns
* Slug: %theme_name%/article
* Categories: %theme_name%/patterns
* Inserter: false
*/
?>
Expand Down
4 changes: 2 additions & 2 deletions patterns/design-system.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php
/**
* Title: Design system
* Slug: buzz/design-system
* Categories: buzz/patterns
* Slug: %theme_name%/design-system
* Categories: %theme_name%/patterns
* Inserter: true
*/
?>
Expand Down
4 changes: 2 additions & 2 deletions patterns/index-list.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php
/**
* Title: Journal index list
* Slug: buzz/index-list
* Categories: buzz/patterns
* Slug: %theme_name%/index-list
* Categories: %theme_name%/patterns
* Inserter: false
*/
?>
Expand Down
4 changes: 2 additions & 2 deletions patterns/masthead.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php
/**
* Title: Masthead
* Slug: buzz/masthead
* Categories: buzz/patterns
* Slug: %theme_name%/masthead
* Categories: %theme_name%/patterns
* Block Types: core/template-part/header
* Inserter: false
*/
Expand Down
4 changes: 2 additions & 2 deletions patterns/not-found.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php
/**
* Title: Not found
* Slug: buzz/not-found
* Categories: buzz/patterns
* Slug: %theme_name%/not-found
* Categories: %theme_name%/patterns
* Inserter: false
*/
?>
Expand Down
4 changes: 2 additions & 2 deletions patterns/page-body.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php
/**
* Title: Page body
* Slug: buzz/page-body
* Categories: buzz/patterns
* Slug: %theme_name%/page-body
* Categories: %theme_name%/patterns
* Inserter: false
*/
?>
Expand Down
4 changes: 2 additions & 2 deletions resources/views/patterns/colophon.blade.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{{--
Title: Colophon
Slug: buzz/colophon
Categories: buzz/patterns
Slug: %theme_name%/colophon
Categories: %theme_name%/patterns
Block Types: core/template-part/footer
Inserter: false
--}}
Expand Down
12 changes: 6 additions & 6 deletions style.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/*
Theme Name: Buzz
Theme URI: https://pollora.dev
Description: A typographic, journal/magazine Full Site Editing theme.
Author: Pollora
Author URI: https://pollora.dev
Version: 1.0.0
Theme Name: %theme_name%
Theme URI: %theme_uri%
Description: %theme_description%
Author: %theme_author%
Author URI: %theme_author_uri%
Version: %theme_version%
License: MIT
License URI: https://opensource.org/licenses/MIT
Tags: full-site-editing, block-patterns, blog, entertainment
Expand Down
Loading
Loading