Skip to content

feat!: export Markdown only; the site renders it (0.6.0) - #45

Merged
geoqiao merged 11 commits into
mainfrom
feat/content-export
Oct 6, 2026
Merged

geoqiao merged 11 commits into
mainfrom
feat/content-export

Conversation

@geoqiao

@geoqiao geoqiao commented Oct 6, 2026

Copy link
Copy Markdown
Owner

问题与改动

escaping 原来有两种产物:用 Jinja 主题生成的完整网站,以及给别的建站工具用的内容。这带来两套主题写法和两份契约,而内置主题从不使用导出,所以没有任何东西每天证明"导出足以建站"。

本 PR 之后 escaping 只做一件事:把已发布的 Issue 写成 Markdown 文件和一份 manifest。

  • escaping-site export 是唯一命令。build、Theme API 4、Quiet、GitHub 主题、路由、feed、sitemap、搜索、重定向、Projects、artifact validation、Action 和 starter 全部移除。
  • 默认外观搬到 escaping-template,它是一个读导出文件的站点,和用户自己写的站点一样只依赖 Content Export v1。
  • Config 只读 github、about、security 三段;文件里其余段落属于站点,不读也不检查。
  • 新增 front matter 字段 update_date;--output 可以是仓库里除 .git 之外的任何目录,同样的 Issue 得到逐字节相同的文件,所以 workflow 可以把导出提交进仓库。
  • 包发布到 PyPI,名字和命令是 escaping-site,import 包是 escaping_site;支持 Python 3.12+。

设计理由见 ADR-0013(取代 ADR 0007–0010 和 0012)。不在范围内:模板仓库的主题代码、任何站点的迁移和部署。

验证结果

  • Python 3.14,uv 0.12.21:CI=true uv run pytest -q 328 passed;ruff check、ruff format --check、ty check、uv lock --check 通过。
  • uv build 与 twine check 通过(sdist 和 wheel)。
  • 用 geoqiao.github.io 的真实 Issue 导出(41 篇 Blog、1 个 About):与移除渲染层之前的导出逐字节一致,只有 .escaping-output 标记文件的文字不同。
  • 文档本地链接与锚点检查:0 个失效。
  • 未验证:release.yml 从未在 GitHub 上运行过;本分支的 v0.6.0rc1 tag 用来验证它。Python 3.12/3.13 在移除渲染层之后只由本 PR 的 CI 验证,本地没有重跑。

风险与兼容性

破坏性变更(0.x 的 minor 版本)。

  • 仍在使用 geoqiao/escaping@v0.5.1 Action 的站点不受影响,可以一直留在该 tag。
  • 升级到 0.6.0 需要站点自己有读导出文件的代码;步骤见 CHANGELOG 的 "Upgrading from 0.5"。为 Theme API 4 写的主题不能用于 0.6.0。
  • 回滚:站点把版本号改回上一个版本即可。PyPI 上已发布的版本不能替换,只能 yank。

合并本仓库不等于发布 release、迁移站点或部署生产。

  • 已遵循维护指南与测试策略,说明适用检查的实际结果
  • 未弱化内容授权、sanitizer、路径 containment、导出目录归属检查或失败保旧导出
  • 公共行为变化已同步契约与迁移说明;远程发布或站点变更有单独授权(如适用)

geoqiao added 10 commits October 6, 2026 19:07
A site built with another tool can keep GitHub Issues as its content
source. The export runs the Content Compiler, so the same Issues are
published, skipped or refused as in a build, and writes one Markdown file
per Blog post, Idea and About Issue plus a manifest. The directory is
staged and marked like a built site. A second Action, export/, runs it
without requiring GitHub Pages.

See docs/contracts/content-export-v1.md and ADR-0011.
An export needs nothing from GitHub Pages, so the Action only wrapped one
command. A workflow now runs the package at a fixed version:
uvx --from 'escpe==X.Y.Z' escpe export.

Pushing a version tag builds the package and publishes it through PyPI
Trusted Publishing; the tag must equal the version in pyproject.toml.
The package metadata gains the license, project links and classifiers,
which needs setuptools 77 or later.
The distribution and the command are escpe, but the code was imported as
escaping, a name that belongs to another project on PyPI. One name now
serves all three. The published asset folder /assets/escaping/, the
.escaping-output marker and the project name are unchanged.

requires-python is >=3.12, the lowest version the full suite passes on,
with no upper bound; CI runs 3.12, 3.13 and 3.14. Six unparenthesized
multi-exception clauses, valid only on 3.14, are parenthesized.

README.md is shown on PyPI, which does not resolve relative addresses,
so its logo and links are absolute.
On GitHub Actions, escpe build reads the repository, its owner and its
Pages address itself when the Config leaves out github.repo or site.url,
and fails with the Pages instruction when Pages does not deploy from
Actions. An empty Config therefore builds with the package alone; a
Config with both fields is never asked about Pages.

The starter workflow runs uvx --from 'escpe==0.6.0' escpe build and no
longer uses any repository of this project. The Action still works for
sites that call it.

The version is 0.6.0. The CHANGELOG section is still "Unreleased"; date
it when the tag is made.
The product keeps its name, escaping. The distribution and the command
are escaping-site and the import package is escaping_site, replacing
escpe, which said nothing about the product and was one letter away from
another package on PyPI. A workflow runs uvx escaping-site@X.Y.Z build
(or export).

Released CHANGELOG sections keep the old command name.
The export was a branch of the build: it registered the site's routes,
validated the whole Site Config and imported the Theme code to write
Markdown. Issue Content is now compiled by issue_content, which knows no
URL, page switch or Theme. A build adds addresses and page HTML to its
entries; an export writes them as files. Neither imports the other.

The export reads only the github, about and security sections of the
Config. It no longer needs site.url or site.title, accepts sections of
the consuming site, exports Ideas and About whatever pages says, and on
GitHub Actions takes the workflow's repository when the Config names
none.

update_date is a new front matter field: the day the author last revised
the content, at least created_date and equal to it when left out. It is
on the post and Idea models for Themes and in the export.

Rendering and route rules moved from Issue Content v1 to Site Build v1.
A build behaves as before; the existing tests pass unchanged apart from
the model field.
--output of an export may be any folder inside the Config directory
except .git; a folder with files of its own is still refused. A workflow
can then commit the Markdown on an Issue event, and a host that builds on
push rebuilds the site without running escaping. A test keeps the export
byte-identical for the same Issues, so nothing is committed without a
change.

The release steps now end with syncing the template repository, after the
version is on PyPI: the template had stayed at 0.5.0.
escaping had two products: a finished site made with Jinja Themes, and the
content alone for a site built elsewhere. That gave two ways to write a theme
with two contracts, and the built-in Theme never used the export, so nothing
proved daily that the export is enough to build a site.

escaping-site export is now the only command. The Site Compiler, Theme API 4,
Quiet, GitHub Themes, routes, the feed, sitemap, search index, redirects,
Projects, artifact validation, the Action and the starter are removed. The
default look moves to the template repository as a site that reads the export,
like any site an author writes.

The Config is three sections (github, about, security); the rest of the file
is the site's and is not read. Site settings are no longer filled in from the
owner's GitHub profile or the Pages address. --output may be any folder except
inside .git.

An export of geoqiao.github.io's Issues is byte-identical to the one before
this change, apart from the text of the ownership marker.

Sites on 0.5 keep working on the v0.5.1 tag. See ADR-0013 and the CHANGELOG.
A release candidate checks what has never run: publishing to PyPI through
Trusted Publishing, and a site made from the template installing the package.
It does not use up the 0.6.0 version number.
On GitHub Actions the export tests read the workflow's GITHUB_REPOSITORY, so
the case "no repository is known" failed there and passed on a laptop.
0.6.0rc1 published to PyPI through Trusted Publishing, and a repository made
from the template built and deployed with it on GitHub: an empty repository,
Blog, Idea and About Issues under a sub-path, and one Issue with an error.
@geoqiao
geoqiao merged commit a97e11a into main Oct 6, 2026
3 checks passed
@geoqiao
geoqiao deleted the feat/content-export branch October 6, 2026 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant