create-ampless 0.2.0-alpha.9 → 1.0.0-alpha.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.ja.md +77 -0
  2. package/README.md +4 -1
  3. package/dist/index.js +99 -47
  4. package/dist/templates/_shared/AGENTS.ja.md +104 -0
  5. package/dist/templates/_shared/AGENTS.md +104 -0
  6. package/dist/templates/_shared/README.ja.md +176 -0
  7. package/dist/templates/_shared/README.md +176 -0
  8. package/dist/templates/_shared/RUNBOOK.ja.md +120 -0
  9. package/dist/templates/_shared/RUNBOOK.md +24 -51
  10. package/dist/templates/_shared/amplify/auth/post-confirmation/resource.ts +6 -0
  11. package/dist/templates/_shared/amplify/backend.ts +2 -0
  12. package/dist/templates/_shared/amplify/data/get-published-post.js +9 -12
  13. package/dist/templates/_shared/amplify/data/list-posts-by-tag.js +6 -9
  14. package/dist/templates/_shared/amplify/data/list-published-posts.js +10 -11
  15. package/dist/templates/_shared/amplify/data/resource.custom.ts +1 -2
  16. package/dist/templates/_shared/amplify/data/resource.ts +22 -5
  17. package/dist/templates/_shared/amplify/events/processor-untrusted/resource.ts +4 -0
  18. package/dist/templates/_shared/amplify/functions/mcp-handler/handler.ts +1 -0
  19. package/dist/templates/_shared/amplify/functions/mcp-handler/resource.ts +11 -0
  20. package/dist/templates/_shared/amplify/functions/user-admin/resource.ts +6 -0
  21. package/dist/templates/_shared/app/(admin)/admin/mcp-tokens/page.tsx +5 -0
  22. package/dist/templates/_shared/app/globals.css +55 -39
  23. package/dist/templates/_shared/app/layout.tsx +24 -11
  24. package/dist/templates/_shared/app/r/[slug]/[[...path]]/route.ts +18 -0
  25. package/dist/templates/_shared/cms.config.ts +1 -23
  26. package/dist/templates/_shared/components/site-chrome/site-sidebar.tsx +2 -3
  27. package/dist/templates/_shared/lib/admin.ts +9 -7
  28. package/dist/templates/_shared/lib/posts-public.ts +1 -1
  29. package/dist/templates/_shared/package.json +11 -12
  30. package/dist/templates/_shared/proxy.ts +22 -8
  31. package/dist/templates/blog/README.ja.md +22 -0
  32. package/dist/templates/blog/README.md +17 -47
  33. package/dist/templates/blog/manifest.ts +1 -1
  34. package/dist/templates/blog/pages/feed.ts +3 -4
  35. package/dist/templates/blog/pages/home.tsx +5 -8
  36. package/dist/templates/blog/pages/post.tsx +7 -7
  37. package/dist/templates/blog/pages/sitemap.ts +3 -4
  38. package/dist/templates/blog/pages/tag.tsx +4 -4
  39. package/dist/templates/blog/tokens.css +26 -40
  40. package/dist/templates/corporate/README.ja.md +18 -0
  41. package/dist/templates/corporate/README.md +12 -14
  42. package/dist/templates/corporate/pages/feed.ts +2 -3
  43. package/dist/templates/corporate/pages/home.tsx +5 -8
  44. package/dist/templates/corporate/pages/post.tsx +7 -7
  45. package/dist/templates/corporate/pages/sitemap.ts +2 -3
  46. package/dist/templates/corporate/pages/tag.tsx +4 -4
  47. package/dist/templates/corporate/tokens.css +17 -39
  48. package/dist/templates/dads/README.ja.md +31 -0
  49. package/dist/templates/dads/README.md +13 -17
  50. package/dist/templates/dads/pages/feed.ts +2 -3
  51. package/dist/templates/dads/pages/home.tsx +5 -8
  52. package/dist/templates/dads/pages/post.tsx +7 -7
  53. package/dist/templates/dads/pages/sitemap.ts +2 -3
  54. package/dist/templates/dads/pages/tag.tsx +4 -4
  55. package/dist/templates/dads/tokens.css +22 -42
  56. package/dist/templates/docs/README.ja.md +24 -0
  57. package/dist/templates/docs/README.md +10 -13
  58. package/dist/templates/docs/pages/feed.ts +2 -3
  59. package/dist/templates/docs/pages/home.tsx +5 -6
  60. package/dist/templates/docs/pages/post.tsx +8 -8
  61. package/dist/templates/docs/pages/sitemap.ts +2 -3
  62. package/dist/templates/docs/pages/tag.tsx +5 -5
  63. package/dist/templates/docs/tokens.css +17 -39
  64. package/dist/templates/landing/README.ja.md +20 -0
  65. package/dist/templates/landing/README.md +14 -19
  66. package/dist/templates/landing/pages/feed.ts +3 -4
  67. package/dist/templates/landing/pages/home.tsx +5 -8
  68. package/dist/templates/landing/pages/post.tsx +7 -7
  69. package/dist/templates/landing/pages/sitemap.ts +2 -3
  70. package/dist/templates/landing/pages/tag.tsx +4 -4
  71. package/dist/templates/landing/tokens.css +17 -39
  72. package/dist/templates/minimal/README.ja.md +14 -0
  73. package/dist/templates/minimal/README.md +9 -47
  74. package/dist/templates/minimal/pages/feed.ts +3 -4
  75. package/dist/templates/minimal/pages/home.tsx +3 -4
  76. package/dist/templates/minimal/pages/post.tsx +6 -6
  77. package/dist/templates/minimal/pages/sitemap.ts +3 -4
  78. package/dist/templates/minimal/pages/tag.tsx +3 -3
  79. package/dist/templates/minimal/tokens.css +17 -39
  80. package/package.json +1 -1
  81. package/dist/templates/_shared/app/(admin)/admin/sites/page.tsx +0 -5
  82. package/dist/templates/_shared/app/site/[siteId]/raw/[slug]/route.ts +0 -5
  83. package/dist/templates/_shared/lib/admin-site-client.ts +0 -10
  84. package/dist/templates/_shared/lib/admin-site.ts +0 -12
  85. /package/dist/templates/_shared/app/{site/[siteId]/[slug] → [slug]}/page.tsx +0 -0
  86. /package/dist/templates/_shared/app/{site/[siteId]/feed.xml → feed.xml}/route.ts +0 -0
  87. /package/dist/templates/_shared/app/{site/[siteId]/og → og}/[slug]/route.ts +0 -0
  88. /package/dist/templates/_shared/app/{site/[siteId]/page.tsx → page.tsx} +0 -0
  89. /package/dist/templates/_shared/app/{site/[siteId]/sitemap.xml → sitemap.xml}/route.ts +0 -0
  90. /package/dist/templates/_shared/app/{site/[siteId]/tag → tag}/[tag]/page.tsx +0 -0
@@ -0,0 +1,176 @@
1
+ > English: [README.md](./README.md)
2
+ >
3
+ # {{siteName}}
4
+
5
+ このサイトは [ampless](https://github.com/heavymoons/ampless) で構築されています — AWS Amplify Gen 2(Cognito + DynamoDB + S3 + AppSync + Lambda)上で動くサーバーレス CMS、フロントエンドは Next.js 15。
6
+
7
+ この README は、サイト運営者として日常的に知っておくべき内容をまとめたものです。たまにやる運用手順(API キーのローテーション、バックアップ復元など)は [RUNBOOK.ja.md](./RUNBOOK.ja.md) に置いています。テーマごとのカスタマイズ詳細は `themes/<name>/README.ja.md` を参照してください。
8
+
9
+ このプロジェクトで AI コーディングエージェント(Claude Code, Cursor, Codex など)を使うなら [AGENTS.ja.md](./AGENTS.ja.md) を読ませてください。エージェントが触っていい場所・ダメな場所がそこに書いてあります。
10
+
11
+ ## 必要なもの
12
+
13
+ - **Node.js 20+** と **npm**
14
+ - **AWS アカウント**と CLI 認証情報(`aws configure`)— sandbox / 本番ともに実 AWS リソースをデプロイします
15
+ - 本番ホスティング(AWS Amplify Hosting)用の **GitHub アカウント**
16
+
17
+ ## コマンド
18
+
19
+ | コマンド | 内容 |
20
+ | --- | --- |
21
+ | `npm install` | 依存関係をインストール |
22
+ | `npm run sandbox` | 個人用 AWS サンドボックス(Cognito / DynamoDB / S3 など)をプロビジョニング、`amplify_outputs.json` を再生成し、`next dev` を `http://localhost:3000` で起動 |
23
+ | `npm run dev` | Next.js だけを起動(サンドボックスのプロビジョニングはスキップ — 一度 `sandbox` を実行済みなら使える) |
24
+ | `npm run build` | Next.js アプリの本番ビルド(Amplify Hosting のデプロイでも自動実行される) |
25
+ | `npm run start` | ビルド済みアプリをローカルで配信 |
26
+ | `npm run lint` | `next lint` で lint |
27
+ | `npm run update-ampless` | ampless テンプレートの最新ファイルを取り込む(設定やテーマは保持。下記「ampless の更新」参照) |
28
+ | `npm run copy-theme` | 公式テーマを後から追加する |
29
+
30
+ ## 初回セットアップ
31
+
32
+ ```bash
33
+ npm install
34
+ npm run sandbox
35
+ ```
36
+
37
+ 初回 sandbox は 5〜10 分かかります(AWS リソースのプロビジョニング)。`next dev` 起動前に毎回 `amplify_outputs.json` が再生成されます。
38
+
39
+ [http://localhost:3000/login](http://localhost:3000/login) を開き、**Create admin account** をクリック。最初に登録したユーザーが自動的に `ampless-admin` Cognito グループに追加されます。
40
+
41
+ ## 管理画面
42
+
43
+ サインイン後、管理画面は `/admin`:
44
+
45
+ | パス | 役割 |
46
+ | --- | --- |
47
+ | `/admin` | ダッシュボード |
48
+ | `/admin/posts` | 投稿の一覧 / 作成 / 編集(Tiptap、Markdown、生 HTML、または zip アップロードの静的バンドル) |
49
+ | `/admin/media` | 画像 / 動画 / ファイルを S3 にアップロード |
50
+ | `/admin/sites/<siteId>` | サイトレベル設定(名前、URL) |
51
+ | `/admin/sites/<siteId>/theme` | テーマの切り替え + フィールド調整(カラー、フォント、ナビ、ロゴなど) |
52
+ | `/admin/users` | ユーザー一覧と Cognito グループ所属の確認 |
53
+ | `/admin/mcp-tokens` | HTTP MCP エンドポイント用の Bearer トークンを発行 |
54
+
55
+ ユーザーロール(Cognito グループ):
56
+
57
+ - `ampless-admin` — フルアクセス(コンテンツ + 運用 + 破壊的操作)
58
+ - `ampless-editor` — コンテンツの CRUD(破壊的操作はなし)
59
+ - `ampless-reader` — 将来の REST/MCP API クライアント用に予約
60
+
61
+ ロールの付与 / 取り消しは AWS Cognito コンソールで行います — [RUNBOOK.ja.md → ユーザーの昇格 / 降格](./RUNBOOK.ja.md#promote--demote-a-user) を参照。
62
+
63
+ ## コンテンツの執筆
64
+
65
+ 投稿(Post)が唯一のコンテンツタイプです。各投稿には以下があります:
66
+
67
+ - **Format** — `tiptap`(リッチテキスト)/ `markdown` / `html`(生 HTML、サニタイズなし)/ `static`(HTML/CSS/JS の zip アップロード)
68
+ - **No layout** フラグ(`format: 'html'` のときのみ)— 本文をそのまま出力し、Next.js のレイアウトもテーマのクロームも適用しない。URL は `/<slug>` のままで、middleware がリクエストを内部のベア HTML ハンドラーに書き換える
69
+ - **キャッシュ戦略**(`metadata.cache`)— 投稿ごとに `Cache-Control` を上書き: `'auto'`(デフォルト、編集時刻ベースのクールダウン)、`'deep'`(常に長期キャッシュ)、`'hot'`(常に no-store)。詳細は `docs/CONTENT.ja.md`
70
+ - **Slug** — 公開 URL
71
+ - **Status** — `draft`(管理者のみ)または `published`
72
+
73
+ 詳細リファレンス: [docs/CONTENT.ja.md(GitHub)](https://github.com/heavymoons/ampless/blob/main/docs/CONTENT.ja.md) ([English](https://github.com/heavymoons/ampless/blob/main/docs/CONTENT.md))
74
+
75
+ ## テーマ
76
+
77
+ インストール済みのテーマはすべて `themes/<name>/` にバンドルされています。**アクティブな**テーマはサイトごとのランタイム設定 — テーマの切り替えに**再デプロイは不要**です。
78
+
79
+ アクティブテーマの切り替え: `/admin/sites/<siteId>/theme` → インストール済みリストから選択 → 保存。
80
+
81
+ アクティブテーマのカスタマイズ(カラー、フォント、ヘッダー / フッターナビなど): 同じ画面 — 各テーマが固有のカスタマイズフィールドを公開しています。各テーマで何が変えられるかは `themes/<name>/README.ja.md` を参照。
82
+
83
+ このプロジェクトに別の公式テーマを追加する:
84
+
85
+ ```bash
86
+ npm run copy-theme
87
+ ```
88
+
89
+ 独自テーマを作る場合: 既存テーマをコピーから始める(`cp -R themes/blog themes/your-theme`)→ `manifest.ts`、`tokens.css`、`pages/*.tsx` を編集 → `themes-registry.ts` に追加。フルガイド: [docs/THEMES.ja.md](https://github.com/heavymoons/ampless/blob/main/docs/THEMES.ja.md) ([English](https://github.com/heavymoons/ampless/blob/main/docs/THEMES.md))
90
+
91
+ ## プラグイン
92
+
93
+ プラグインは CMS にイベント駆動の副作用(SEO メタデータ、RSS フィード、外部 URL への webhook、OG 画像生成など)を追加する仕組みです。[`cms.config.ts`](./cms.config.ts) で宣言し、投稿の publish / update / delete 時に Lambda 上で実行されます。
94
+
95
+ `cms.config.ts` に書けば有効になる、同梱の公式プラグイン:
96
+
97
+ | パッケージ | 役割 |
98
+ | --- | --- |
99
+ | `@ampless/plugin-seo` | 投稿ごとの OGP / Twitter / canonical メタデータ + `sitemap.xml` |
100
+ | `@ampless/plugin-rss` | `/feed.xml` の RSS 2.0 フィード |
101
+ | `@ampless/plugin-webhook` | 外部 URL へのイベント POST(HMAC 署名付き) |
102
+ | `@ampless/plugin-og-image` | `/og/<slug>` での動的 OG 画像生成 |
103
+
104
+ プラグインを追加するには: install(`npm i @ampless/plugin-...`)→ `cms.config.ts` で import → `plugins` 配列に追加:
105
+
106
+ ```ts
107
+ import seoPlugin from '@ampless/plugin-seo'
108
+ import rssPlugin from '@ampless/plugin-rss'
109
+
110
+ export default defineConfig({
111
+ // ...
112
+ plugins: [
113
+ seoPlugin({ twitterSite: '@example' }),
114
+ rssPlugin({ language: 'ja', limit: 20 }),
115
+ ],
116
+ })
117
+ ```
118
+
119
+ プラグイン変更には再デプロイが必要です(プラグインコードは Lambda バンドルに含まれます)。
120
+
121
+ ## 本番デプロイ
122
+
123
+ 同梱の [`amplify.yml`](./amplify.yml) が、connect したブランチへの push のたびに `npx ampx pipeline-deploy`(Amplify バックエンド) + `npm run build`(Next.js)を実行します。
124
+
125
+ 1. **GitHub(または Amplify Hosting が対応する git ホスト)にこのプロジェクトを push**:
126
+ ```bash
127
+ git init && git add . && git commit -m "init"
128
+ git remote add origin <your-repo>
129
+ git push -u origin main
130
+ ```
131
+ 2. **AWS Amplify Hosting コンソール** → **Create new app** → **Host web app** → リポジトリを連携 → ブランチを選択 → 自動検出されたビルド設定(`amplify.yml` の内容になっているはず)を確認 → デプロイ
132
+ 3. 初回デプロイは 10〜20 分。以降は連携ブランチへの push で自動再デプロイ
133
+
134
+ ### 環境変数
135
+
136
+ 環境ごとの値は **Amplify Hosting コンソール → Hosting → Environment variables** で設定。よく使うもの:
137
+
138
+ | 変数 | 利用箇所 |
139
+ | --- | --- |
140
+ | `WEBHOOK_SECRET` | `@ampless/plugin-webhook` の HMAC 署名 |
141
+
142
+ env 変数追加 / 変更後は再デプロイをトリガーしてください。
143
+
144
+ ### カスタムドメイン
145
+
146
+ Amplify Hosting アプリの **Domain management** からドメインをバインドします — ACM 証明書と DNS レコードは Amplify が自動でプロビジョニングします。詳細手順: [RUNBOOK.ja.md → カスタムドメインを Amplify Hosting に追加](./RUNBOOK.ja.md#adding-a-custom-domain-to-amplify-hosting)
147
+
148
+ ## AI 連携(MCP)
149
+
150
+ ampless は MCP(Model Context Protocol)サーバーを同梱しているので、Claude Desktop / Cursor / Claude Code など MCP に対応した AI クライアントから投稿の読み書きができます。
151
+
152
+ - **ローカル / sandbox** — グローバルに 1 度入れる: `npx -y @ampless/mcp-server@alpha` に `amplify_outputs.json` のパスを渡す
153
+
154
+ ## ampless の更新
155
+
156
+ ampless は `alpha` dist-tag でリリースしています。新機能を取り込むには:
157
+
158
+ ```bash
159
+ npm run update-ampless
160
+ ```
161
+
162
+ これは `npx create-ampless@latest upgrade` を実行し、以下を行います:
163
+
164
+ - `package.json` の `@ampless/*` / `ampless` 依存をバージョンアップ
165
+ - 共有テンプレートファイル(admin アプリの土台、amplify バックエンド、lib/、middleware、テーマ)を再同期 — `cms.config.ts`、`theme.*` 管理画面設定、投稿、テーマ manifest 値などのユーザーカスタマイズは保持されます
166
+ - `update-ampless` / `copy-theme` スクリプトのコマンドが変わっていれば更新
167
+
168
+ commit 前に diff を確認できます。
169
+
170
+ ## 運用
171
+
172
+ 日常以外の運用レシピ — ユーザー昇格、パスワードリセット、バックアップ復元、カスタムドメイン配線、AppSync API キーローテーション — は [RUNBOOK.ja.md](./RUNBOOK.ja.md) にあります。
173
+
174
+ ## ライセンス
175
+
176
+ このプロジェクト自身のコードはあなたのものです。ampless 本体は MIT ライセンスです。詳細は [ampless リポジトリ](https://github.com/heavymoons/ampless) を参照。
@@ -0,0 +1,176 @@
1
+ > 日本語版: [README.ja.md](./README.ja.md)
2
+ >
3
+ # {{siteName}}
4
+
5
+ Your site is built with [ampless](https://github.com/heavymoons/ampless) — a serverless CMS that runs on AWS Amplify Gen 2 (Cognito + DynamoDB + S3 + AppSync + Lambda) with a Next.js 15 frontend.
6
+
7
+ This README covers what you, the site owner, need to know day-to-day. Operational recipes (rotating keys, restoring backups, etc.) live in [RUNBOOK.md](./RUNBOOK.md). Per-theme customization details live in `themes/<name>/README.md`.
8
+
9
+ If you use an AI coding agent (Claude Code, Cursor, Codex, etc.) on this project, point it at [AGENTS.md](./AGENTS.md) — that file tells the agent what it can and can't touch.
10
+
11
+ ## Requirements
12
+
13
+ - **Node.js 20+** and **npm**
14
+ - **AWS account** with CLI credentials (`aws configure`) — the sandbox + production both deploy real AWS resources
15
+ - **GitHub account** for production hosting via AWS Amplify Hosting
16
+
17
+ ## Commands
18
+
19
+ | Command | What it does |
20
+ | --- | --- |
21
+ | `npm install` | Install dependencies |
22
+ | `npm run sandbox` | Provision a personal AWS sandbox (Cognito / DynamoDB / S3 / etc.), regenerate `amplify_outputs.json`, then start `next dev` on `http://localhost:3000` |
23
+ | `npm run dev` | Start Next.js only (skip sandbox provisioning — use after `sandbox` has already run once) |
24
+ | `npm run build` | Production build of the Next.js app (also runs as part of Amplify Hosting deploy) |
25
+ | `npm run start` | Serve the production build locally |
26
+ | `npm run lint` | Lint with `next lint` |
27
+ | `npm run update-ampless` | Pull the latest ampless template files into this project, preserving your config and themes (see "Updating ampless" below) |
28
+ | `npm run copy-theme` | Add another official theme to this project after-the-fact |
29
+
30
+ ## First time setup
31
+
32
+ ```bash
33
+ npm install
34
+ npm run sandbox
35
+ ```
36
+
37
+ First sandbox run takes 5–10 minutes (AWS resource provisioning). `amplify_outputs.json` is regenerated each time before `next dev` starts.
38
+
39
+ Open [http://localhost:3000/login](http://localhost:3000/login) and click **Create admin account**. The first registered user is automatically added to the `ampless-admin` Cognito group.
40
+
41
+ ## Admin UI
42
+
43
+ After signing in, the admin lives at `/admin`:
44
+
45
+ | Path | Purpose |
46
+ | --- | --- |
47
+ | `/admin` | Dashboard |
48
+ | `/admin/posts` | List / create / edit posts (Tiptap editor, Markdown, raw HTML, or zip-uploaded static bundles) |
49
+ | `/admin/media` | Upload images / videos / files to S3 |
50
+ | `/admin/sites/<siteId>` | Site-level settings (name, URL) |
51
+ | `/admin/sites/<siteId>/theme` | Activate a theme + customize its fields (colors, fonts, nav, logo, etc.) |
52
+ | `/admin/users` | View users + Cognito group memberships |
53
+ | `/admin/mcp-tokens` | Issue Bearer tokens for the HTTP MCP endpoint |
54
+
55
+ User roles (Cognito groups):
56
+
57
+ - `ampless-admin` — full access (content + ops + destructive)
58
+ - `ampless-editor` — content CRUD (no destructive ops)
59
+ - `ampless-reader` — reserved for future REST/MCP API consumers
60
+
61
+ Promotion/demotion is done in the AWS Cognito console — see [RUNBOOK.md → Promote / demote a user](./RUNBOOK.md#promote--demote-a-user).
62
+
63
+ ## Authoring content
64
+
65
+ Posts are the single content type. Each post has:
66
+
67
+ - **Format** — `tiptap` (rich text) / `markdown` / `html` (raw, no sanitization) / `static` (zip-upload of HTML/CSS/JS)
68
+ - **No layout** flag (`format: 'html'` only) — render the body verbatim with no Next.js layout and no theme chrome. URL stays `/<slug>`; middleware rewrites the request to the internal bare-HTML handler.
69
+ - **Cache strategy** (`metadata.cache`) — override the per-post Cache-Control: `'auto'` (default; cooldown by edit time), `'deep'` (always long-cache), or `'hot'` (always no-store). See `docs/CONTENT.md` for details.
70
+ - **Slug** — the public URL
71
+ - **Status** — `draft` (admin only) or `published`
72
+
73
+ Full reference: [docs/CONTENT.md on GitHub](https://github.com/heavymoons/ampless/blob/main/docs/CONTENT.md) ([日本語](https://github.com/heavymoons/ampless/blob/main/docs/CONTENT.ja.md)).
74
+
75
+ ## Themes
76
+
77
+ Every installed theme is bundled in this project under `themes/<name>/`. The **active** theme per site is a runtime setting — switching themes does **not** require a redeploy.
78
+
79
+ To switch the active theme: `/admin/sites/<siteId>/theme` → pick from the installed list → save.
80
+
81
+ To customize the active theme (colors, fonts, header / footer nav, etc.): same page — each theme exposes its own customization fields. See `themes/<name>/README.md` for what's customizable per theme.
82
+
83
+ To install another official theme into this project:
84
+
85
+ ```bash
86
+ npm run copy-theme
87
+ ```
88
+
89
+ To author your own theme: copy an existing one as a starting point (`cp -R themes/blog themes/your-theme`), edit `manifest.ts`, `tokens.css`, and `pages/*.tsx`, then add it to `themes-registry.ts`. Full guide: [docs/THEMES.md](https://github.com/heavymoons/ampless/blob/main/docs/THEMES.md) ([日本語](https://github.com/heavymoons/ampless/blob/main/docs/THEMES.ja.md)).
90
+
91
+ ## Plugins
92
+
93
+ Plugins extend the CMS with event-driven side effects (SEO metadata, RSS feed, webhooks to external URLs, OG image generation, etc.). They're declared in [`cms.config.ts`](./cms.config.ts) and run on Lambda when posts are published / updated / deleted.
94
+
95
+ Bundled and ready to enable in `cms.config.ts`:
96
+
97
+ | Package | Purpose |
98
+ | --- | --- |
99
+ | `@ampless/plugin-seo` | Per-post OGP / Twitter / canonical metadata + `sitemap.xml` |
100
+ | `@ampless/plugin-rss` | RSS 2.0 feed at `/feed.xml` |
101
+ | `@ampless/plugin-webhook` | POST events to external URLs (HMAC-signed) |
102
+ | `@ampless/plugin-og-image` | Dynamic Open Graph image generation at `/og/<slug>` |
103
+
104
+ To add a plugin: install (`npm i @ampless/plugin-...`), import in `cms.config.ts`, and add to the `plugins` array:
105
+
106
+ ```ts
107
+ import seoPlugin from '@ampless/plugin-seo'
108
+ import rssPlugin from '@ampless/plugin-rss'
109
+
110
+ export default defineConfig({
111
+ // ...
112
+ plugins: [
113
+ seoPlugin({ twitterSite: '@example' }),
114
+ rssPlugin({ language: 'en', limit: 20 }),
115
+ ],
116
+ })
117
+ ```
118
+
119
+ A redeploy is required for plugin changes (the plugin code ships in the Lambda bundle).
120
+
121
+ ## Deploying to production
122
+
123
+ The shipped [`amplify.yml`](./amplify.yml) runs `npx ampx pipeline-deploy` (Amplify backend) + `npm run build` (Next.js) on every push to the branch you connect.
124
+
125
+ 1. **Push this project to GitHub** (or another git host Amplify Hosting supports):
126
+ ```bash
127
+ git init && git add . && git commit -m "init"
128
+ git remote add origin <your-repo>
129
+ git push -u origin main
130
+ ```
131
+ 2. **AWS Amplify Hosting console** → **Create new app** → **Host web app** → connect the repo → pick the branch → review the auto-detected build settings (they should match `amplify.yml`) → deploy.
132
+ 3. First deploy takes 10–20 minutes. Subsequent pushes to the connected branch redeploy automatically.
133
+
134
+ ### Environment variables
135
+
136
+ Set per-environment values in **Amplify Hosting console → Hosting → Environment variables**. Common ones:
137
+
138
+ | Variable | Used by |
139
+ | --- | --- |
140
+ | `WEBHOOK_SECRET` | `@ampless/plugin-webhook` HMAC signing |
141
+
142
+ Trigger a redeploy after adding/changing env vars.
143
+
144
+ ### Custom domains
145
+
146
+ Bind a domain to your Amplify Hosting app in **Domain management** — Amplify provisions an ACM certificate and DNS records automatically. Full step-by-step: [RUNBOOK.md → Adding a custom domain](./RUNBOOK.md#adding-a-custom-domain-to-amplify-hosting).
147
+
148
+ ## AI integration (MCP)
149
+
150
+ ampless ships an MCP (Model Context Protocol) server so Claude Desktop / Cursor / Claude Code / anything that speaks MCP can read and write your posts.
151
+
152
+ - **Local / sandbox** — install once globally: `npx -y @ampless/mcp-server@alpha` with the path to your `amplify_outputs.json`.
153
+
154
+ ## Updating ampless
155
+
156
+ ampless releases on the `alpha` dist-tag. To pick up new features:
157
+
158
+ ```bash
159
+ npm run update-ampless
160
+ ```
161
+
162
+ This runs `npx create-ampless@latest upgrade`, which:
163
+
164
+ - Bumps the `@ampless/*` and `ampless` dependencies in `package.json`
165
+ - Re-syncs the shared template files (admin app shell, amplify backend, lib/, middleware, themes) — your customizations to `cms.config.ts`, `theme.*` admin settings, posts, and theme manifest values are preserved.
166
+ - Updates `update-ampless` and `copy-theme` script entries if their commands change
167
+
168
+ You can review the diff before committing.
169
+
170
+ ## Operations
171
+
172
+ Day-to-day operational recipes — user promotion, password reset, backup restore, custom domain wiring, AppSync API key rotation — live in [RUNBOOK.md](./RUNBOOK.md).
173
+
174
+ ## License
175
+
176
+ This project's own code is yours. ampless itself is MIT-licensed; see the [ampless repository](https://github.com/heavymoons/ampless) for details.
@@ -0,0 +1,120 @@
1
+ > English: [RUNBOOK.md](./RUNBOOK.md)
2
+ >
3
+
4
+ # ランブック
5
+
6
+ ampless で構築したサイトで、ときどき必要になる運用作業の手順集です。毎日やるわけではないけれど、いざ必要になったときに迷わずに進めるためのものです。
7
+
8
+ 日常的な使い方(コマンド、管理画面、テーマ、プラグイン、デプロイなど)は [README.ja.md](./README.ja.md) を参照してください。
9
+
10
+ ## 目次
11
+
12
+ - [AppSync API キー(自動更新)](#appsync-api-キー自動更新)
13
+ - [よくある運用](#よくある運用)
14
+ - [ユーザーの昇格 / 降格](#promote--demote-a-user)
15
+ - [パスワードリセット(管理者上書き)](#reset-a-users-password-admin-override)
16
+ - [Post テーブルのバックアップから復元](#restore-from-a-post-table-backup)
17
+ - [失敗したプラグインイベントを確認する](#inspect-failed-plugin-events)
18
+ - [カスタムドメイン](#custom-domains)
19
+ - [カスタムドメインを Amplify Hosting に追加する](#adding-a-custom-domain-to-amplify-hosting)
20
+
21
+ ## AppSync API キー(自動更新)
22
+
23
+ パブリックブログの読み取り(`listPublishedPosts`、`getPublishedPost`、`listPostsByTag`)は AppSync API キーで保護されています。このキーは `amplify_outputs.json` に含まれているため、**公開サイトを訪問した誰もが確認できる**状態にあります。低信頼度のクレデンシャルとして扱ってください。このキーが持つ権限は上記 3 つのカスタムクエリを呼び出すことのみであり、それらのクエリは `status === 'published'` の行しか返しません。
24
+
25
+ ### なぜ API キーなのか(Identity Pool ゲストロールではなく)?
26
+
27
+ Amplify Gen 2 の `a.handler.custom` リゾルバーは `allow.guest()` や `allow.authenticated('identityPool')` をサポートしておらず、apiKey / userPool / lambda / group / owner のみが使用可能です。v0.1 では簡潔さを優先して API キーを選択しました。パブリック読み取りを Lambda 関数データソース(`a.handler.function`)に移行することは v0.2 の候補です。
28
+
29
+ ### 自動更新 — ローテーション手順は不要
30
+
31
+ `api-key-renewer` Lambda(`amplify/functions/api-key-renewer/` を参照)は、毎月 1 日の UTC 03:00 に EventBridge スケジュールで起動します。`AppSync.UpdateApiKey` を呼び出して既存キーの `expires` を「現時点 + 364 日」に延長するため、以下が保証されます:
32
+
33
+ - キー ID は変化しない
34
+ - `amplify_outputs.json` は引き続き有効
35
+ - Next.js アプリの再ビルドは不要
36
+ - 常に約 334 日以上の残有効期限が維持される
37
+
38
+ 手動で確認または実行したい場合:
39
+
40
+ ```bash
41
+ # 現在の有効期限を確認
42
+ aws appsync list-api-keys \
43
+ --region <amplify_outputs.json の data.aws_region> \
44
+ --api-id <amplify_outputs.json の data.url から導出した api-id>
45
+
46
+ # 手動実行(サンドボックスを長期間停止した後など)
47
+ aws lambda invoke \
48
+ --function-name $(aws lambda list-functions \
49
+ --query "Functions[?contains(FunctionName,'api-key-renewer')].FunctionName | [0]" \
50
+ --output text) \
51
+ /tmp/out.json && cat /tmp/out.json
52
+ ```
53
+
54
+ ### キーの漏洩が疑われる場合
55
+
56
+ 有効期限の延長ではなく、キーの値そのものをローテーションする即時対応を行います:
57
+
58
+ 1. `amplify/data/resource.ts` のコメントを編集して CFN 更新を強制する
59
+ 2. `npx ampx sandbox`(サンドボックス)または `npx ampx pipeline-deploy ...`(本番)を実行 — Amplify がキーの値を再生成する
60
+ 3. Next.js アプリを再デプロイして SSR が新しい `data.api_key` を参照するようにする
61
+
62
+ ## 一般的な操作
63
+
64
+ ### ユーザーのグループ昇格 / 降格
65
+
66
+ AWS Cognito コンソールから操作します:
67
+
68
+ 1. User Pool → Users → 対象ユーザーを選択
69
+ 2. Group memberships → グループに追加 / グループから削除
70
+ 3. ユーザーに一度サインアウトしてサインインし直してもらい、新しいクレームを適用させる
71
+
72
+ グループの種類: `ampless-admin`(フル CRUD + 運用操作)、`ampless-editor`(コンテンツ CRUD)、`ampless-reader`(将来の REST/MCP API 利用者向けに予約済み)。
73
+
74
+ ### ユーザーパスワードのリセット(管理者による上書き)
75
+
76
+ ロックアウトされており、メールによる復旧が利用できない場合:
77
+
78
+ ```bash
79
+ aws cognito-idp admin-set-user-password \
80
+ --user-pool-id <amplify_outputs.json の auth.user_pool_id> \
81
+ --region <リージョン> \
82
+ --username <メールアドレス> \
83
+ --password '<新しいパスワード>' --permanent
84
+ ```
85
+
86
+ `/login` ページにはセルフサービスの「パスワードを忘れた場合」フローもあります。
87
+
88
+ ### Post テーブルのバックアップからの復元
89
+
90
+ DynamoDB のポイントインタイムリカバリ(PITR)は v0.1 の `defineData` では有効になっていません。AWS コンソール → DynamoDB → Tables → `<投稿テーブル名>` → Backups → Edit PITR から手動で有効化してください。有効化後は `aws dynamodb restore-table-to-point-in-time` で新しいテーブルに復元し、その後アイテムをライブテーブルに移行する作業が必要です。
91
+
92
+ ### 失敗したプラグインイベントの確認
93
+
94
+ 処理に失敗したプロセッサー呼び出しは、`amplify/backend.ts` で作成された共有イベント DLQ(`EventsDlq`)に送られます。SQS コンソールまたは `aws sqs receive-message --queue-url <dlq-url> --max-number-of-messages 10` でメッセージを確認してください。v0.1 には自動アラームがないため、定期的な手動確認を推奨します。あるいは `ApproximateNumberOfMessagesVisible` に CloudWatch アラームを設定してください。
95
+
96
+ ## カスタムドメイン
97
+
98
+ ampless は 1 Amplify デプロイ = 1 サイト。複数サイトを別ドメインで配信したい場合は、サイトごとに Amplify 環境を分けてデプロイしてください。
99
+
100
+ ### Amplify Hosting へのカスタムドメイン追加
101
+
102
+ バインドしたいドメインごとに以下を実施します:
103
+
104
+ 1. **Amplify Hosting コンソール** → アプリ → **Domain management** → **Add domain** を選択。
105
+ 2. 頂点ドメイン(`example.com`)と接続したいサブドメインを入力します。Amplify が ACM 証明書と CloudFront SAN エントリを自動でプロビジョニングします。
106
+ 3. DNS を更新:
107
+ - **Route 53 / Amplify 管理の DNS プロバイダー**:Amplify が CNAME を作成してくれるので、確認するだけです。
108
+ - **外部 DNS**(Cloudflare、Squarespace など):Amplify が表示する CNAME / DNS 検証レコードをコピーします。ACM のメール検証もフォールバックとして使用できます。
109
+ 4. **Domain activation** が完了するまで待ちます(通常 15〜60 分。証明書の検証が最も時間がかかります)。
110
+ 5. `cms.config.ts` の `site.url` を新しい canonical URL に更新し、commit して push します:
111
+ ```bash
112
+ git add cms.config.ts && git commit -m "feat: bind docs.example.com"
113
+ git push # Amplify Hosting が自動検出します
114
+ ```
115
+
116
+ エンドツーエンドで確認:
117
+
118
+ ```bash
119
+ curl -I https://docs.example.com/ # 200 とサイトの HTML
120
+ ```
@@ -1,6 +1,22 @@
1
+ > 日本語版: [RUNBOOK.ja.md](./RUNBOOK.ja.md)
2
+ >
3
+
1
4
  # Runbook
2
5
 
3
- Operational tasks for an ampless-powered site.
6
+ Step-by-step recipes for occasional operations on an ampless-powered site — the things you don't do every day but want a reliable procedure for when you do.
7
+
8
+ For day-to-day usage (commands, admin UI tour, themes, plugins, deploying), start with [README.md](./README.md).
9
+
10
+ ## Contents
11
+
12
+ - [AppSync API key (auto-renewed)](#appsync-api-key-auto-renewed)
13
+ - [Common operations](#common-operations)
14
+ - [Promote / demote a user](#promote--demote-a-user)
15
+ - [Reset a user's password (admin override)](#reset-a-users-password-admin-override)
16
+ - [Restore from a Post-table backup](#restore-from-a-post-table-backup)
17
+ - [Inspect failed plugin events](#inspect-failed-plugin-events)
18
+ - [Custom domains](#custom-domains)
19
+ - [Adding a custom domain to Amplify Hosting](#adding-a-custom-domain-to-amplify-hosting)
4
20
 
5
21
  ## AppSync API key (auto-renewed)
6
22
 
@@ -99,49 +115,10 @@ or `aws sqs receive-message --queue-url <dlq-url> --max-number-of-messages 10`.
99
115
  There's no automated alarm in v0.1 — periodic manual checks recommended,
100
116
  or wire up a CloudWatch alarm on `ApproximateNumberOfMessagesVisible`.
101
117
 
102
- ## Multi-site / custom domains
103
-
104
- ampless can serve multiple sites from one Amplify Hosting deployment.
105
- Each site is identified by a `siteId` and bound to one or more
106
- hostnames via `cms.config.ts`:
107
-
108
- ```ts
109
- sites: {
110
- blog: {
111
- domains: ['blog.example.com', 'www.example.com'],
112
- name: 'My Blog',
113
- url: 'https://blog.example.com',
114
- },
115
- docs: {
116
- domains: ['docs.example.com'],
117
- name: 'Docs',
118
- url: 'https://docs.example.com',
119
- },
120
- },
121
- ```
122
-
123
- The middleware (`middleware.ts`) maps incoming `Host` to a `siteId` and
124
- internally rewrites the path to `/_sites/{siteId}/...`. Subdomains and
125
- fully separate domains are equivalent at the application layer — only
126
- the AWS-side wiring differs.
127
-
128
- ### Single domain operation
129
-
130
- If `sites` is undefined or has only one entry, ampless runs in
131
- single-site mode (`siteId='default'`). SSR responses follow each page's
132
- own caching directives (so you can opt into CloudFront caching with
133
- `Cache-Control: public, s-maxage=...` per route).
118
+ ## Custom domains
134
119
 
135
- ### Multi-site mode caveat: SSR caching is force-disabled
136
-
137
- When two or more sites are declared, the middleware adds
138
- `Cache-Control: private, no-store` to every public response. This is
139
- because Amplify Hosting's CloudFront does not include `Host` in its
140
- cache key — leaving caching on would let `https://site1/foo` and
141
- `https://site2/foo` cross-contaminate at the edge. The trade-off is
142
- that every public read hits Lambda. Lifting it requires moving off
143
- Amplify Hosting onto a self-managed CloudFront + Open Next stack
144
- (roadmap: post-v1.0).
120
+ ampless runs one site per Amplify deployment. To serve multiple sites
121
+ on different domains, deploy a separate Amplify environment per site.
145
122
 
146
123
  ### Adding a custom domain to Amplify Hosting
147
124
 
@@ -160,19 +137,15 @@ For each domain you want to bind:
160
137
  also works as a fallback.
161
138
  4. Wait for the **Domain activation** to finish (typically 15–60
162
139
  minutes; certificate validation is the slow step).
163
- 5. Add the new domain to the matching `sites.{id}.domains[]` in
164
- `cms.config.ts` and redeploy:
140
+ 5. Update `cms.config.ts` so `site.url` reflects the new canonical URL,
141
+ commit, and push to redeploy:
165
142
  ```bash
166
- git add cms.config.ts && git commit -m "feat: add docs.example.com"
143
+ git add cms.config.ts && git commit -m "feat: bind docs.example.com"
167
144
  git push # Amplify Hosting picks it up
168
145
  ```
169
146
 
170
147
  Verify end-to-end:
171
148
 
172
149
  ```bash
173
- curl -I https://docs.example.com/ # 200 with the docs site's HTML
174
- curl -sI https://docs.example.com/ | grep -i cache # Cache-Control: private, no-store
150
+ curl -I https://docs.example.com/ # 200 with your site's HTML
175
151
  ```
176
-
177
- If the request returns `404 Site not found` instead, the host is not
178
- listed in any `sites.*.domains[]` — fix the config and redeploy.
@@ -3,4 +3,10 @@ import { defineFunction } from '@aws-amplify/backend'
3
3
  export const postConfirmation = defineFunction({
4
4
  name: 'post-confirmation',
5
5
  entry: './handler.ts',
6
+ // Auth trigger — assign to the auth nested stack so CFN doesn't
7
+ // place it in a separate `function` stack that ends up in a
8
+ // circular dependency loop with auth (which references this
9
+ // function) and storage/data (which auth happens to reach via
10
+ // cross-stack refs the function adds).
11
+ resourceGroupName: 'auth',
6
12
  })
@@ -8,6 +8,7 @@ import { eventDispatcher } from './events/dispatcher/resource.js'
8
8
  import { processorTrusted } from './events/processor-trusted/resource.js'
9
9
  import { processorUntrusted } from './events/processor-untrusted/resource.js'
10
10
  import { apiKeyRenewer } from './functions/api-key-renewer/resource.js'
11
+ import { mcpHandler } from './functions/mcp-handler/resource.js'
11
12
  import { userAdmin } from './functions/user-admin/resource.js'
12
13
  import { customizeBackend } from './backend.custom.js'
13
14
 
@@ -27,6 +28,7 @@ const backend = defineAmplessBackend({
27
28
  processorTrusted,
28
29
  processorUntrusted,
29
30
  apiKeyRenewer,
31
+ mcpHandler,
30
32
  userAdmin,
31
33
  })
32
34
 
@@ -2,24 +2,21 @@ import { util } from '@aws-appsync/utils'
2
2
 
3
3
  // AppSync JS resolver: returns a single published post by slug.
4
4
  //
5
- // Reads the `bySiteIdSlug` GSI: PK = `${siteId}#${slug}`. A given
6
- // (site, slug) tuple identifies at most one row, so this is an O(1)
7
- // PK Query — no scan, no filter, no per-partition limit issues.
5
+ // Reads the `bySlug` GSI: PK = slug. A slug identifies at most one row
6
+ // (uniqueness enforced at the admin form level), so this is an O(1) PK
7
+ // Query — no scan, no filter, no per-partition limit issues.
8
8
  //
9
- // Drafts are dropped in the response handler. The admin form
10
- // enforces a unique slug per site at the application layer, but if
11
- // somehow draft + published share a slug we prefer the published row.
9
+ // Drafts are dropped in the response handler. If draft + published
10
+ // somehow share a slug we prefer the published row.
12
11
  export function request(ctx) {
13
- const siteId = ctx.args.siteId ?? 'default'
14
12
  const slug = ctx.args.slug
15
- const partition = `${siteId}#${slug}`
16
13
  return {
17
14
  operation: 'Query',
18
- index: 'bySiteIdSlug',
15
+ index: 'bySlug',
19
16
  query: {
20
- expression: '#siteIdSlug = :siteIdSlug',
21
- expressionNames: { '#siteIdSlug': 'siteIdSlug' },
22
- expressionValues: util.dynamodb.toMapValues({ ':siteIdSlug': partition }),
17
+ expression: '#slug = :slug',
18
+ expressionNames: { '#slug': 'slug' },
19
+ expressionValues: util.dynamodb.toMapValues({ ':slug': slug }),
23
20
  },
24
21
  limit: 5,
25
22
  }