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
60 changes: 60 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Deploy docs to GitHub Pages

on:
push:
branches: [trunk]
paths:
- 'components/**'
- 'docs/**'
- 'bin/build-docs*'
- 'composer.json'
- 'composer.lock'
- '.github/workflows/docs.yml'
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
tools: composer
coverage: none

- name: Install dependencies
run: composer install --no-dev --optimize-autoloader --no-progress

- name: Bundle toolkit and regenerate docs
run: |
mkdir -p docs/assets
rm -f docs/assets/php-toolkit.zip
zip -qr docs/assets/php-toolkit.zip components vendor bootstrap.php composer.json \
-x "*/Tests/*" "*/tests/*" "*/.git/*" "*/.github/*" "*/node_modules/*"
python3 bin/build-docs.py

- uses: actions/upload-pages-artifact@v3
with:
path: ./docs

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
47 changes: 47 additions & 0 deletions .github/workflows/snippet-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Verify docs snippets

# Runs every PHP snippet in bin/_docs_components.py against the local toolkit
# and compares stdout against bin/_expected_outputs.json. Anything that drifts
# fails CI; anything that errors out also fails CI.
#
# Snippets that can't run locally (need network, a listening port, or other
# Playground-only features) are not in the JSON and are skipped — the runner
# only enforces matches for snippets that already have captured output.

on:
pull_request:
paths:
- 'components/**'
- 'bin/_docs_components.py'
- 'bin/_expected_outputs.json'
- 'bin/run-snippets.py'
- 'composer.json'
- 'composer.lock'
- '.github/workflows/snippet-tests.yml'
push:
branches: [trunk]
workflow_dispatch:

jobs:
run-snippets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
tools: composer
coverage: none

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install toolkit dependencies
run: composer install --no-dev --optimize-autoloader --no-progress

- name: Run docs snippets and compare to expected output
run: bin/run-snippets.py --check
2,871 changes: 2,871 additions & 0 deletions bin/_docs_components.py

Large diffs are not rendered by default.

81 changes: 81 additions & 0 deletions bin/_expected_outputs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
"blockparser::audit-embeds.php": "ok https://twitter.com/wordpress/status/1\nok https://youtube.com/watch?v=abc\nSTALE https://vine.co/v/xyz\n",
"blockparser::count-blocks.php": " 2 core/paragraph\n 1 core/group\n 1 core/heading\n 1 core/image\n",
"blockparser::find-custom-block.php": "1. Jane (5/5): Loved it.\n2. Joe (4/5): Pretty good.\n",
"blockparser::has-block.php": "has button\nmissing gallery\n",
"blockparser::lint-headings.php": "ok Intro: H2\nWARN Subsection: jumped from H2 to H4\nok Body: H3\n",
"blockparser::parse.php": "core/heading: Welcome\ncore/paragraph: Hello from the block editor.\n",
"blueprints::build-json.php": "{\n \"version\": 2,\n \"steps\": [\n {\n \"step\": \"setSiteOptions\",\n \"options\": {\n \"blogname\": \"Demo Site\",\n \"permalink_structure\": \"/%postname%/\",\n \"show_on_front\": \"page\"\n }\n },\n {\n \"step\": \"installPlugin\",\n \"pluginData\": \"https://downloads.wordpress.org/plugin/gutenberg.zip\"\n },\n {\n \"step\": \"activatePlugin\",\n \"plugin\": \"gutenberg/gutenberg.php\"\n },\n {\n \"step\": \"installPlugin\",\n \"pluginData\": \"https://downloads.wordpress.org/plugin/classic-editor.zip\"\n },\n {\n \"step\": \"activatePlugin\",\n \"plugin\": \"classic-editor/classic-editor.php\"\n }\n ]\n}\n",
"blueprints::configure.php": "mode: apply-to-existing-site\nroot: /wordpress\nurl: http://playground.test/\n",
"blueprints::validate.php": "Blueprint root[\"steps\"][0]: Missing required field: step.\n",
"bytestream::deflate-roundtrip.php": "original : 1050 bytes\ndeflated : 45 bytes (4.3%)\nround-trip: OK\n",
"bytestream::limited.php": "body sees: BODY:hello there\nremaining in source: |FOOTER:done\n",
"bytestream::lines.php": "[1] alpha\n[2] bravo\n[3] charlie\n[4] delta\n[5] echo\n",
"bytestream::memory-pipe.php": "got: first chunk\nsecond chunk\nthird chunk\n",
"bytestream::teaser-read.php": "Read 1800 bytes in 256-byte chunks.\n",
"cli::help-text.php": "Usage: mytool [options] <input>\n\nOptions:\n -o, --output=VALUE Write result to FILE\n -f, --force Overwrite existing files\n -v, --verbose Verbose output\n -h, --help Show this help and exit\n",
"cli::mix-shapes.php": "input: input.json\nflags: all, force, verbose\noutput: /tmp/<tempfile>.txt\nport: 8080\n",
"cli::parse-flag.php": "verbose: yes\ninput: input.txt\n",
"cli::require-options.php": "error: Missing required option --site-path\n",
"cli::subcommands.php": "command=deploy\noptions: {\"env\":\"production\",\"dry-run\":true}\npositionals: [\"web-01\",\"web-02\"]\n---\ncommand=rollback\noptions: {\"to\":\"abc123\"}\npositionals: []\n",
"dataliberation::build-wxr.php": "items: 2\nterms: 3\nBlog post exported\n",
"dataliberation::md-to-wxr.php": "posts: 2\nblock markup exported\nfrontmatter title exported\n",
"dataliberation::rewrite-urls.php": "new URL present\nold URL removed\n",
"dataliberation::wxr-quickstart.php": "bytes: 475\ntitle exported\nstatus exported\n",
"dataliberation::wxr-read.php": "site_option: {\"option_name\":\"blogname\",\"option_value\":\"Demo\"}\npost: {\"post_title\":\"First\",\"post_id\":\"1\",\"post_type\":\"post\",\"post_content\":\"Body 1\"}\npost: {\"post_title\":\"Second\",\"post_id\":\"2\",\"post_type\":\"post\",\"post_content\":\"Body 2\"}\n",
"encoding::mixed-encoding.php": "#1 ok: Plain ASCII\n#2 ok: Caf\u00e9\n#3 recovered as latin1: caf\u00e9\n#4 recovered as latin1: weird \u00c0 byte\n",
"encoding::noncharacters.php": "normal text: ok\nU+FFFE: reject\nU+FDD0: reject\n",
"encoding::pipeline.php": "good valid=Y noncharacter=N -> Caf\u00e9\nlatin1 valid=N noncharacter=N -> caf\ufffd\noverlong valid=N noncharacter=N -> x\ufffd\ufffdy\nnoncharac valid=Y noncharacter=Y -> hi \ufffe there\n",
"encoding::scrub.php": "the byte \ufffd should not be here.\n.\ufffd\ufffd.\n",
"encoding::validate.php": "ASCII: valid\nUTF-8 pencil: valid\nlatin-1 byte: invalid\noverlong slash: invalid\nsurrogate half: invalid\n",
"filesystem::atomic-write.php": "config: {\"v\":2}\nno .tmp leftovers: 1 entries in root\n",
"filesystem::cross-backend-copy.php": "in memory after two copies:\n posts: 2024-01.md\n index: <h1>Home</h1>\n",
"filesystem::local-chroot.php": "Hi from local disk.\nexists after cleanup? no\n",
"filesystem::path-helpers.php": "/var/www/site/index.php\n/a/b\na/c/e\n",
"filesystem::sqlite.php": "post-1.md: # Post 1\npost-2.md: # Post 2\npost-3.md: # Post 3\n",
"filesystem::teaser-memory.php": "Hello, world!",
"filesystem::test-without-disk.php": "{\"version\":\"1.2.4\"}\n",
"git::branches.php": "on experiment: {\"flag\":true}\non trunk: {\"flag\":false}\n",
"git::commit-in-memory.php": "commit: <oid>\nHEAD: <oid>\nREADME: # My Project\n",
"git::git-filesystem.php": "tree:\n /posts/about.md\n /posts/hello.md\n\nhello.md now:\n# Hello\nSecond draft.\n",
"git::merge-branches.php": "merge head: <oid>\nconflicts: none\nresult:\nbuy oat milk\nwalk dog\nread book\nwrite blog post\n",
"git::options-snapshot.php": "Files changed in last snapshot:\n options.json\n",
"git::walk-history.php": "<hash> expand examples\n<hash> fix typo\n<hash> add intro\n",
"html::absolute-links.php": "<p>See <a href=\"https://my-site.test/about\">about</a>, <a href=\"https://example.com/x\">x</a>, and <a href=\"https://my-site.test/contact.html\">contact</a>.</p>",
"html::bookmarks.php": "<ul data-progress=\"2/3\"><li><input type=\"checkbox\" checked> Buy milk</li><li><input type=\"checkbox\"> Walk the dog</li><li><input type=\"checkbox\" checked> Read book</li></ul>",
"html::breadcrumbs.php": "found 2 figure images\n<article><figure><img class=\"figure-image\" src=\"hero.jpg\" alt=\"Hero\"><figcaption>Hero shot</figcaption></figure><p>Body copy <img src=\"emoji.png\" alt=\"\"> mid-paragraph.</p><figure><img class=\"figure-image\" src=\"diagram.png\" alt=\"Diagram\"></figure></article>",
"html::csp-nonce.php": "nonce: <random>\n\n<head><style nonce=\"<random>\">body{font:16px sans-serif}</style></head><body><script nonce=\"<random>\">console.log(\"hi\")</script><script nonce=\"<random>\" src=\"vendor.js\"></script></body>",
"html::decode-entities.php": "attribute: path?a=1&b=2&copy\ntext: AT&T \u2014 100% \ud83d\ude00\nbool(false)\n",
"html::lazy-load-images.php": "<article>\n\t<img decoding=\"async\" loading=\"lazy\" src=\"hero.jpg\" alt=\"Hero\">\n\t<p>Intro copy.</p>\n\t<img decoding=\"async\" loading=\"lazy\" src=\"inline.jpg\" alt=\"Inline\">\n</article>",
"html::outline.php": " H1 Title\n H2 Chapter 1\n H2 Chapter 2\n",
"html::sanitize-html.php": "<p>Hi <b >friend</b>!</p><script></script><img src=x >",
"html::srcset-rewrite.php": "<figure><img sizes=\"(max-width: 768px) 100vw, 768px\" srcset=\"https://cdn.test/uploads/photo.jpg?w=480 480w, https://cdn.test/uploads/photo.jpg?w=768 768w, https://cdn.test/uploads/photo.jpg?w=1200 1200w\" src=\"https://cdn.test/uploads/photo.jpg\" alt=\"Sunset\"></figure>",
"httpclient::parse-response.php": "status: 201 Created\nok: yes\ntype: application/json\nsize: 27 bytes\n",
"httpclient::request-object.php": "POST https://api.example.test/posts\ncontent-type: application/json\ncontent-length: 39\nauthorization: Basic dXNl...\n",
"httpserver::buffered-writer.php": "headers before send:\nContent-Type: text/html\n\nbody:\n<!doctype html><title>Hi</title><h1>Hello</h1><p>Buffered body, sent at the end.</p>",
"markdown::count-blocks.php": "core/heading: 1\ncore/paragraph: 2\ncore/table: 1\ncore/code: 1\ncore/quote: 1\n",
"markdown::frontmatter.php": "Title: The Name of the Wind\nStatus: publish\nTags: fantasy, kingkiller\n",
"markdown::migrate-folder.php": "=== roadmap (/tmp/<tempfile>/roadmap.md) ===\n<!-- wp:heading {\"level\":1} -->\n<h1 class=\"wp-block-heading\" id=\"roadmap\">Roadmap</h1>\n<!-- /wp:heading -->\n\n<!-- wp:lis...\n\n=== Welcome (/tmp/<tempfile>/welcome.md) ===\n<!-- wp:paragraph -->\n<p>Hello world.</p>\n<!-- /wp:paragraph -->\n\n...\n\n",
"markdown::quickstart.php": "<!-- wp:heading {\"level\":1} -->\n<h1 class=\"wp-block-heading\" id=\"hello\">Hello</h1>\n<!-- /wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Welcome to <b>WordPress</b>.</p>\n<!-- /wp:paragraph -->\n\n",
"markdown::roundtrip.php": "## Round trip\n\n- one\n- two\n- three\n\n",
"merge::conflicts.php": "ours: line 2 from Alice\ntheirs: line 2 from Bob\n\n--- merged content with markers ---\nline 1\n\n<<<<<<< HEAD\nline 2 from Alice\n\n=======\nline 2 from Bob\n\n>>>>>>> incoming \n\n",
"merge::git-patch.php": "diff --git a/post.yml b/post.yml\n--- a/post.yml\n+++ b/post.yml\n@@ -1,4 +1,5 @@- title: Hello\n+ title: Hello, world\n author: Alice\n- status: draft\n+ status: published\n+ tags: greeting\n \n",
"merge::line-diff.php": "= alpha\n- beta\n+ BETA\n= gamma\n+ delta\n= \n",
"merge::sync-folder-vs-db.php": "=== hello.md ===\n(conflict \u2014 needs review)\n# Hello\n\n<<<<<<< HEAD\nDraft body, expanded on disk.\n\n=======\nNew section from the editor.\n\n>>>>>>> incoming \n\n\n=== about.md ===\n(conflict \u2014 needs review)\n# About\n\n<<<<<<< HEAD\nWho *they* are.\n\n=======\nWho we really are.\n\n>>>>>>> incoming \n\n\n",
"merge::three-way.php": "clean merge:\nintro updated\nbody\noutro\nappendix\n\n",
"polyfill::filter-chain.php": "my-post-title\n",
"polyfill::library-hooks.php": "user@example.com\nother@example.com\n",
"polyfill::php8-strings.php": "bool(true)\nbool(true)\nbool(true)\nfirst key: alpha\n",
"polyfill::priority-args.php": "<strong>19.99</strong> EUR (EUR markup)\n",
"polyfill::wp-stubs.php": "Hello, world\n&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;\na &quot;quoted&quot; value\nhttps://example.com/?a=1&amp;b=2\n",
"xml::bump-prices.php": "<catalog><book sku=\"A1\" price=\"32.99\"><title>PHP Internals</title></book><book sku=\"A2\" price=\"15.95\"><title>WordPress at Scale</title></book></catalog>",
"xml::opml.php": "Hacker News\thttps://news.ycombinator.com/rss\nLWN\thttps://lwn.net/headlines/rss\nWordPress\thttps://wordpress.org/news/feed/\n",
"xml::rewrite-wxr-urls.php": "rewrote 3 text nodes\n\n<?xml version=\"1.0\"?><rss xmlns:wp=\"http://wordpress.org/export/1.2/\"><channel><wp:base_site_url>https://new.example.com</wp:base_site_url><item><link>https://new.example.com/2024/post-1</link><guid>https://new.example.com/?p=1</guid></item></channel></rss>",
"xml::wxr-namespaces.php": "title: Hello World\ndc/creator: admin\nwp/post_id: 42\nwp/status: publish\n",
"zip::epub.php": "mimetype: application/epub+zip\nsize on disk: 839 bytes\n",
"zip::repack.php": "new config.json: {\"debug\":true,\"version\":\"1.0.1\"}\nuntouched: <?php echo \"hello\";\n",
"zip::stream-large.php": "Inflated 205000 bytes in 8 KB chunks, parsed 15000 rows.\n",
"zip::teaser-read.php": "Hello from inside the zip.",
"zip::zip-slip.php": "../../etc/passwd => etc/passwd\n./safe/path.txt => ./safe/path.txt\na/../../b/secret => a/../b/secret\na//b///c.txt => a/b/c.txt\n../../../../root/.ssh/authorized_keys => root/.ssh/authorized_keys\n",
"zip::zip-to-memfs.php": "files now in memory:\n /app/README.md\n /app/VERSION\n /app/assets/style.css\n /app/index.php\n /app/lib/util.php\n"
}
23 changes: 23 additions & 0 deletions bin/build-docs-bundle.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# Rebuilds docs/assets/php-toolkit.zip and regenerates the docs HTML pages.
# Run this whenever components/ changes or the docs page generator (bin/build-docs.py)
# changes.
set -euo pipefail

cd "$(dirname "$0")/.."

echo "==> composer install --no-dev --optimize-autoloader"
composer install --no-dev --optimize-autoloader --quiet

echo "==> bundling docs/assets/php-toolkit.zip"
rm -f docs/assets/php-toolkit.zip
zip -qr docs/assets/php-toolkit.zip components vendor bootstrap.php composer.json \
-x "*/Tests/*" "*/tests/*" "*/.git/*" "*/.github/*" "*/node_modules/*"

echo "==> regenerating legacy docs/_legacy/*/index.html"
python3 bin/build-docs.py

echo "==> regenerating docs/reference/*.html"
python3 bin/build-reference.py

echo "Done. docs/assets/php-toolkit.zip = $(du -h docs/assets/php-toolkit.zip | cut -f1)"
Loading
Loading