Skip to content

Latest commit

 

History

16 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

aiseed-builder — WordPress の Flet アプリ版

サーバーも MySQL も無い。サイトは手元のフォルダ(記事=シリーズ単位の AsciiDoc、履歴=git)で、公開は静的HTML(Cloudflare Pages 等、月0円)。 WordPress の管理画面の操作感のまま、壊れない・攻撃されない・保守しない サイト運用をするためのデスクトップアプリ(Python/Flet)。

WordPress aiseed-builder
サーバー+PHP+MySQL 手元のフォルダ+git(無し)
管理画面(投稿一覧・編集・下書き/公開・メディア) 同じ操作感の Flet アプリ
テーマ サイト側の tools/templates/ + site.json
プラグイン forms(問い合わせフォーム)/ audit(サイト監査)
更新・脆弱性対応 無い(静的HTMLに攻撃面がない)
リビジョン git(「変更を記録」ボタン)

入手・起動

配布は PyPI ではなくデスクトップアプリ(各OSのバンドル)。 Releases から自分のOSのものを取って実行する。ビルドは GitHub Actions (.github/workflows/build.yml)が linux / macOS / Windows 分を作る。

# 自分でビルドする(Flutter SDK が要る)
uv sync && uv run flet build linux      # → build/linux/aiseed-builder

# ソースから動かす(開発時)
uv run aiseed-builder ~/dev/website   # サイトフォルダを指定
uv run aiseed-builder                      # 前回のサイト(初回は選択画面)

サイト側には Python と、そのサイトのビルドエンジン(tools/)が要る ——アプリはエンジンを持たないため(下記の設計原則)。

設計原則

  • エンジンは持たない。 ビルド・パースは対象サイトが持つ tools/(build_article.py / build/series.py / serve.py)を使う。 パーサの正はサイト側に一つだけ。aiseed-builder は UI とファイル操作。
  • サイト固有の知識は site.json へ。 シリーズ一覧・URL規則・ Cloudflare プロジェクト名などはコードに書かない。シリーズは site.json で自由に定義できる(組み込みの名前に縛られない):
{
  "builder": {
    "cf_project": "aiseed-dev",
    "publish_dir": "html",
    "preview_port": 8000,
    "series": [
      {"file": "blog.adoc", "label": "Blog", "url_base": "/blog"},
      {"file": "claude-debian.adoc", "label": "Debian入門",
       "url_base": "/claude-debian", "slug_strip": "claude-debian-"},
      {"file": "pages.adoc", "label": "固定ページ", "url_base": "/pages",
       "subtitle": "日付のない、独立したページ"}
    ]
  }
}
  • 公開の手段はアプリが持つ。 「サイトを公開」は cf-publish(PyPI・このアプリの 依存)で publish_dir を Cloudflare Pages へ直接アップロードする—— wrangler も npm も要らない。サイト側にデプロイスクリプトが無くても公開 できるので、ビルドだけできるサイトなら公開できる(公開先は cf_project、既定の公開ディレクトリは html/)。認証は環境変数か ~/.config/cloudflare/pages.env(CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID)。
  • 壊さない。 すべての保存は本文/フロントマターの行単位差し替え+ 保存後の再パース検証つき。壊れる編集は拒否して自動復元する。
  • 起動時にプレビューサーバー(ライブリロード付き)を自動起動。 「更新」→数秒でビルド→開いているプレビューが自動で最新になる。

プラグイン機構

site.jsonbuilder.plugins(例 ["forms", "audit"])に名前を入れた ものだけが、その順でサイドバーに出る。各プラグインは plugins/<name>/plugin.pyPLUGIN(name/label/icon/screen)を宣言し、 plugins/__init__.py の登録表に1行足すだけ。main.py はプラグインごとの 分岐を持たない。

プラグイン: audit(サイト監査)

"audit" を入れると、サイドバーに サイト監査 が出る。WordPress の 「サイトヘルス」に当たるが、静的サイトなので全ページ・全参照をローカルで 完全走査できる(実測: 390ページ・13,579参照を約1秒)。結果は JSON で 書き出せる。

タブ「サイト内」(ネットワーク不要)

ビルド出力 html/ を丸ごと走査する:

  • リンク切れ …… 内部リンク・画像・PDF が実在するか(最重要)
  • メタデータ …… title / description / OGP の欠落、説明文の重複
  • 代替テキスト …… alt の無い画像
  • 孤立ページ …… どこからもリンクされていないページ
  • 重い画像 …… 表示を遅くする大きなファイル

verify_migration.py(新旧の一致を見る道具)とは目的が違う。あちらは 「移行が正しいか」、こちらは「サイトが今正しいか」——両方で壊れている ものは前者では原理的に検出できない(実際、この監査で「本文のリンク名と実 ファイル名の食い違いで移行前からPDFが読めていない」箇所が見つかった)。

タブ「公開面(DNS)」

~/dev/test/dns-inventory の CLI を移植。 自分のドメインを入れて棚卸しすると:

  • 基本レコード(A/AAAA/MX/NS/TXT/SOA/CAA)
  • メール認証(SPF/DMARC/DKIM)を良好/要改善で色分け(DMARC の p=none も警告)
  • 逆引き(IP → ホスト名)
  • AXFR ゾーン転送テスト(開いていたら重大)
  • crt.sh でサブドメイン補完(外部通信。チェックで on/off、既定 on)+ ダングリング CNAME 検出(サブドメイン乗っ取り候補)

WordPress をやめて攻撃面を減らした次の一手——「公開面を自分で点検する」。 dnspython に依存(未インストールなら画面にその旨を表示)。

プラグイン: forms(FormRescue 統合)

site.jsonbuilder.plugins"forms" を入れると、サイドバーに フォーム受信箱が出る(FormRescueforms/flet を移植):

  • 受信箱(未確認/確認済み/対応済み)——Cloudflare Worker + D1 からプル型で 取得。「確認」でリモートから削除(データは端末に残る)、「対応済み」で整理
  • 接続設定(Worker URL / PULL_TOKEN——cf-publish の deploy.py が発行)
  • 「フォーム定義をコピー」——記事本文に貼る [.form] の雛形
  • データは <site>/.builder/forms/inbox.db(gitignore 推奨・日次 バックアップ30日保持)

公開ページ側の [.form] 描画はサイトのビルドエンジン側で対応済み (website の場合: pywashi の form プラグインを adoc レンダラに合成し、 form-render.js / form.css / Turnstile をフォームのあるページだけに読み込む)。 これで一気通貫: 記事に [.form](雛形コピー)→ 公開ページでフォーム描画 → 送信は Worker+D1 → aiseed-builder の受信箱で管理。 未移植: QRスキャン(モバイル専用機能)。

新しいサイトを作る

サイドバーの「新しいサイトを作る」で、雛形を選んでサイトを増やせる:

雛形 中身
default 素のサイト(記事2本・テンプレート・エンジン一式)
contact 上記 + 固定ページ(pages.adoc: このサイトについて / お問い合わせ)+ 問い合わせフォーム([.form]、フォーム編集ツール tools/form-builder/plugins:["forms"])

雛形とその展開は今開いているサイトのエンジン(tools/init_site.py)が 行う。アプリは雛形を持たない——エンジンと雛形が一緒に育つようにするため。 作られたサイトにはエンジン一式が入るので、単体でビルドでき、このアプリでも 開ける(そのサイトからさらに新しいサイトも作れる)。

ロードマップ

  1. フェーズ1 — site-editor の汎用化(済。website(旧website-adoc)が最初の利用者)
  2. サイトの新規作成(雛形)(済) / テーマ切替UI
  3. forms プラグイン(済。[.form] のサイト側描画も対応済み)
  4. audit プラグイン(サイト監査 + 公開面DNS)(済)
  5. flet-printing 連携(記事を印刷)、外部リンクの生存確認、他プラグイン

テスト

uv run pytest -q

Flet MCP(AI に最新の API を引かせる)

Flet 1.0 系は API の変更が続いており、LLM の記憶は古い(実際この開発でも Tabs の構造・Dropdown.on_selectft.Padding.symmetric で三度踏んだ)。 公式の Flet MCP サーバーを繋ぐと、インストール済みバージョンの コントロール・プロパティ・イベント・アイコン・実例を、その場で引ける。

pip install flet-mcp        # flet と同じバージョンが入る

プロジェクト直下に .mcp.json を置く(flet は絶対パスで指定—— MCP サーバーは venv を有効化していない環境で起動されるため):

{
  "mcpServers": {
    "flet": {
      "command": "/path/to/.venv/bin/flet",
      "args": ["mcp"],
      "env": { "FLET_MCP_ENABLE_EXAMPLES": "1" }
    }
  }
}

既定で有効なのは API とアイコン。FLET_MCP_ENABLE_EXAMPLES=1 で実例検索も 入る。パスが環境ごとに違うので .mcp.json は git に入れない (.gitignore 済み)。設定を変えたら Claude Code を再起動する。

アプリとして配る上での約束事(実装メモ)

flet build のバンドルは自前の Python ランタイムを抱える。そのため:

  • [tool.flet.app] pathパッケージの親(src)を指す。パッケージ本体を 指すと中身がフラットに展開され、実行時に ModuleNotFoundError になる
  • パッケージ内は絶対 import(from aiseed_builder... import)
  • サイトの python / git を起動するときは LD_LIBRARY_PATH PYTHONHOME PYTHONPATH外した環境を渡す(残すとサイト側の python がアプリの ライブラリを掴んで起動に失敗する)
  • サイトのエンジンから import するのは依存ゼロの層だけ (tools/build/series.pyfrontmatter.py)。レンダリング(markdown-it / jinja2 / Pillow)を引きずる層に触れると、アプリのバンドルにその依存が 無いため落ちる。ビルド・デプロイは import ではなく subprocess で行う

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages