create-ampless 0.2.0-alpha.20 → 0.2.0-alpha.22

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.
package/dist/index.js CHANGED
@@ -1761,6 +1761,14 @@ import { join as join3, relative, extname as extname2, dirname } from "path";
1761
1761
  import { log as log3, outro } from "@clack/prompts";
1762
1762
  import pc3 from "picocolors";
1763
1763
  import { execa as execa4 } from "execa";
1764
+ var AMPLESS_MANAGED_APP_PATHS = [
1765
+ "app/(admin)/admin",
1766
+ "app/api/admin",
1767
+ "app/api/media",
1768
+ "app/api/mcp",
1769
+ "app/login",
1770
+ "app/site"
1771
+ ];
1764
1772
  var AMPLESS_PACKAGES = /* @__PURE__ */ new Set([
1765
1773
  "ampless",
1766
1774
  "@ampless/admin",
@@ -1882,6 +1890,63 @@ async function walkDir(dir, base, out) {
1882
1890
  }
1883
1891
  }
1884
1892
  }
1893
+ async function listFilesRecursive(root) {
1894
+ const out = [];
1895
+ async function walk(current, relPrefix) {
1896
+ const entries = await readdir3(current, { withFileTypes: true });
1897
+ for (const entry of entries) {
1898
+ const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
1899
+ if (entry.isDirectory()) {
1900
+ await walk(join3(current, entry.name), rel);
1901
+ } else if (entry.isFile()) {
1902
+ out.push(rel);
1903
+ }
1904
+ }
1905
+ }
1906
+ if (!existsSync5(root)) return out;
1907
+ await walk(root, "");
1908
+ return out;
1909
+ }
1910
+ async function pruneEmptyDirs(root) {
1911
+ if (!existsSync5(root)) return;
1912
+ const entries = await readdir3(root, { withFileTypes: true });
1913
+ for (const entry of entries) {
1914
+ if (entry.isDirectory()) {
1915
+ await pruneEmptyDirs(join3(root, entry.name));
1916
+ }
1917
+ }
1918
+ const remaining = await readdir3(root);
1919
+ if (remaining.length === 0) {
1920
+ await rm2(root, { recursive: true, force: true });
1921
+ }
1922
+ }
1923
+ async function findObsoleteAppFiles(destDir, sharedDir) {
1924
+ const obsolete = [];
1925
+ for (const managedPath of AMPLESS_MANAGED_APP_PATHS) {
1926
+ const userPath = join3(destDir, managedPath);
1927
+ if (!existsSync5(userPath)) continue;
1928
+ const templatePath2 = join3(sharedDir, managedPath);
1929
+ const templateFiles = new Set(await listFilesRecursive(templatePath2));
1930
+ const userFiles = await listFilesRecursive(userPath);
1931
+ for (const f of userFiles) {
1932
+ if (!templateFiles.has(f)) {
1933
+ obsolete.push(`${managedPath}/${f}`);
1934
+ }
1935
+ }
1936
+ }
1937
+ return obsolete;
1938
+ }
1939
+ async function removeObsoleteAppFiles(destDir, paths) {
1940
+ for (const rel of paths) {
1941
+ const abs = join3(destDir, rel);
1942
+ if (existsSync5(abs)) {
1943
+ await rm2(abs, { force: true });
1944
+ }
1945
+ }
1946
+ for (const managedPath of AMPLESS_MANAGED_APP_PATHS) {
1947
+ await pruneEmptyDirs(join3(destDir, managedPath));
1948
+ }
1949
+ }
1885
1950
  function substituteVars(content, vars) {
1886
1951
  return content.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? `{{${key}}}`);
1887
1952
  }
@@ -1935,6 +2000,7 @@ async function runUpgradeIn(destDir, sharedDir, opts = {}) {
1935
2000
  const shippedThemes = themeSyncEnabled ? await listShippedThemes(templatesRoot) : [];
1936
2001
  const existingThemes = themeSyncEnabled ? await discoverInstalledThemes(destDir) : [];
1937
2002
  const preservedThemes = existingThemes.filter((t) => !shippedThemes.includes(t));
2003
+ const obsoleteFiles = await findObsoleteAppFiles(destDir, sharedDir);
1938
2004
  log3.info(
1939
2005
  `replace: ${pc3.green(`\u8FFD\u52A0 ${replaceNew.length}`)} / ${pc3.yellow(`\u66F4\u65B0 ${replaceUpdate.length}`)}`
1940
2006
  );
@@ -1949,6 +2015,9 @@ async function runUpgradeIn(destDir, sharedDir, opts = {}) {
1949
2015
  `themes: ${pc3.cyan(`\u30C7\u30D5\u30A9\u30EB\u30C8\u30C6\u30FC\u30DE\u3092\u540C\u671F: ${shippedThemes.length}`)} / ${pc3.dim(`\u30AB\u30B9\u30BF\u30E0\u30C6\u30FC\u30DE (my-*) \u3092\u4FDD\u6301: ${preservedThemes.length}`)}`
1950
2016
  );
1951
2017
  }
2018
+ if (obsoleteFiles.length > 0) {
2019
+ log3.info(`cleanup: ${pc3.yellow(`\u524A\u9664 ${obsoleteFiles.length}`)} (ampless-managed app/ \u914D\u4E0B\u3067\u30C6\u30F3\u30D7\u30EC\u306B\u7121\u3044\u30D5\u30A1\u30A4\u30EB)`);
2020
+ }
1952
2021
  log3.info(`protected: ${pc3.dim(`\u30C6\u30F3\u30D7\u30EC\u306B\u5B58\u5728\u3059\u308B\u304C\u89E6\u3089\u306A\u3044: ${classification.protected.length} \u500B`)}`);
1953
2022
  if (opts.dryRun) {
1954
2023
  return {
@@ -1958,7 +2027,8 @@ async function runUpgradeIn(destDir, sharedDir, opts = {}) {
1958
2027
  protected: classification.protected,
1959
2028
  themesSynced: shippedThemes,
1960
2029
  themesPreserved: preservedThemes,
1961
- packageJsonMerged: false
2030
+ packageJsonMerged: false,
2031
+ obsoleteRemoved: obsoleteFiles
1962
2032
  };
1963
2033
  }
1964
2034
  for (const rel of classification.replace) {
@@ -1972,6 +2042,7 @@ async function runUpgradeIn(destDir, sharedDir, opts = {}) {
1972
2042
  await copyWithSubstitution(src, dst, vars);
1973
2043
  }
1974
2044
  const themeResult = themeSyncEnabled ? await syncThemes(destDir, templatesRoot) : { synced: [], preserved: [] };
2045
+ await removeObsoleteAppFiles(destDir, obsoleteFiles);
1975
2046
  const templatePkgRaw = await readFile3(join3(sharedDir, "package.json"), "utf-8");
1976
2047
  const templatePkg = JSON.parse(templatePkgRaw);
1977
2048
  const indent = detectIndent(projectPkgRaw);
@@ -2011,7 +2082,8 @@ async function runUpgradeIn(destDir, sharedDir, opts = {}) {
2011
2082
  protected: classification.protected,
2012
2083
  themesSynced: themeResult.synced,
2013
2084
  themesPreserved: themeResult.preserved,
2014
- packageJsonMerged: true
2085
+ packageJsonMerged: true,
2086
+ obsoleteRemoved: obsoleteFiles
2015
2087
  };
2016
2088
  }
2017
2089
  async function runUpgrade(args) {
@@ -60,9 +60,7 @@
60
60
 
61
61
  エージェントが投稿コンテンツを直接クエリ・編集できる。公開ツール: `list_posts`、`get_post`、`create_post`、`update_post`、`delete_post`、`upload_media`、`get_schema`。
62
62
 
63
- 登録方法は 2 つ:
64
-
65
- **ローカル stdio(Claude Code / Cursor 向け、推奨):**
63
+ 登録方法:
66
64
 
67
65
  プロジェクトルートの `.mcp.json` に追加する:
68
66
  ```json
@@ -80,8 +78,6 @@
80
78
  }
81
79
  ```
82
80
 
83
- **管理者発行トークンによる HTTP**(ホスト型エージェント向け): `/admin/mcp-tokens` で Bearer トークンを発行し、エージェントクライアントを公開 MCP HTTP エンドポイントに向ける。エンドポイント URL は管理 UI で確認できる。
84
-
85
81
  投稿本文は `markdown`、`html`、`tiptap`(JSON ドキュメント)の 3 フォーマットに対応している。
86
82
 
87
83
  ## 動作確認の基準
@@ -60,9 +60,7 @@ This file is written for AI coding agents (Claude Code, Cursor, Codex, etc.). Fo
60
60
 
61
61
  Lets agents query and modify post content directly. Tools exposed: `list_posts`, `get_post`, `create_post`, `update_post`, `delete_post`, `upload_media`, `get_schema`.
62
62
 
63
- Two registration options:
64
-
65
- **Local stdio (recommended for Claude Code / Cursor):**
63
+ Registration:
66
64
 
67
65
  Add to `.mcp.json` at the project root:
68
66
  ```json
@@ -80,8 +78,6 @@ Add to `.mcp.json` at the project root:
80
78
  }
81
79
  ```
82
80
 
83
- **HTTP via admin-issued token** (for hosted agents): issue a Bearer token at `/admin/mcp-tokens` and point the agent client at the public MCP HTTP endpoint. See the admin UI for the exact endpoint URL.
84
-
85
81
  Post bodies accept three formats: `markdown`, `html`, or `tiptap` (JSON document).
86
82
 
87
83
  ## Verification expectations
@@ -137,8 +137,6 @@ export default defineConfig({
137
137
  | 変数 | 利用箇所 |
138
138
  | --- | --- |
139
139
  | `WEBHOOK_SECRET` | `@ampless/plugin-webhook` の HMAC 署名 |
140
- | `AMPLESS_MCP_SERVICE_EMAIL` | HTTP MCP サービスユーザーのメール — `/api/mcp` を有効化するのに必須 |
141
- | `AMPLESS_MCP_SERVICE_PASSWORD` | HTTP MCP サービスユーザーのパスワード |
142
140
 
143
141
  env 変数追加 / 変更後は再デプロイをトリガーしてください。
144
142
 
@@ -157,7 +155,6 @@ ampless は 1 つの Amplify Hosting デプロイで複数のサイトを配信
157
155
  ampless は MCP(Model Context Protocol)サーバーを同梱しているので、Claude Desktop / Cursor / Claude Code など MCP に対応した AI クライアントから投稿の読み書きができます。
158
156
 
159
157
  - **ローカル / sandbox** — グローバルに 1 度入れる: `npx -y @ampless/mcp-server@alpha` に `amplify_outputs.json` のパスを渡す
160
- - **本番(HTTP)** — `/admin/mcp-tokens` で Bearer トークンを発行し、MCP クライアントを `https://<your-domain>/api/mcp` に向ける。フルセットアップ: [docs/mcp-http-setup.ja.md](https://github.com/heavymoons/ampless/blob/main/docs/mcp-http-setup.ja.md) ([English](https://github.com/heavymoons/ampless/blob/main/docs/mcp-http-setup.md))
161
158
 
162
159
  ## ampless の更新
163
160
 
@@ -137,8 +137,6 @@ Set per-environment values in **Amplify Hosting console → Hosting → Environm
137
137
  | Variable | Used by |
138
138
  | --- | --- |
139
139
  | `WEBHOOK_SECRET` | `@ampless/plugin-webhook` HMAC signing |
140
- | `AMPLESS_MCP_SERVICE_EMAIL` | HTTP MCP service-user email — required to enable `/api/mcp` |
141
- | `AMPLESS_MCP_SERVICE_PASSWORD` | HTTP MCP service-user password |
142
140
 
143
141
  Trigger a redeploy after adding/changing env vars.
144
142
 
@@ -157,7 +155,6 @@ There's a caching trade-off in multi-site mode; details in [RUNBOOK.md → Multi
157
155
  ampless ships an MCP (Model Context Protocol) server so Claude Desktop / Cursor / Claude Code / anything that speaks MCP can read and write your posts.
158
156
 
159
157
  - **Local / sandbox** — install once globally: `npx -y @ampless/mcp-server@alpha` with the path to your `amplify_outputs.json`.
160
- - **Production (HTTP)** — issue a Bearer token at `/admin/mcp-tokens` and point your MCP client at `https://<your-domain>/api/mcp`. Full setup: [docs/mcp-http-setup.md](https://github.com/heavymoons/ampless/blob/main/docs/mcp-http-setup.md) ([日本語](https://github.com/heavymoons/ampless/blob/main/docs/mcp-http-setup.ja.md)).
161
158
 
162
159
  ## Updating ampless
163
160
 
@@ -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
 
@@ -0,0 +1 @@
1
+ export { handler } from '@ampless/backend/functions/mcp-handler'
@@ -0,0 +1,11 @@
1
+ import { defineFunction } from '@aws-amplify/backend'
2
+
3
+ export const mcpHandler = defineFunction({
4
+ name: 'mcp-handler',
5
+ entry: './handler.ts',
6
+ // Co-locate with the data stack so the function has the KvStore
7
+ // table ARN available without cross-stack references at synth time.
8
+ resourceGroupName: 'data',
9
+ memoryMB: 512,
10
+ timeoutSeconds: 30,
11
+ })
@@ -26,8 +26,8 @@
26
26
  "@ampless/plugin-rss": "^0.2.0-alpha.6",
27
27
  "@ampless/plugin-seo": "^0.2.0-alpha.6",
28
28
  "@ampless/plugin-webhook": "^0.2.0-alpha.6",
29
- "@ampless/admin": "^0.2.0-alpha.18",
30
- "@ampless/backend": "^0.2.0-alpha.8",
29
+ "@ampless/admin": "^0.2.0-alpha.19",
30
+ "@ampless/backend": "^0.2.0-alpha.9",
31
31
  "@ampless/runtime": "^0.2.0-alpha.9",
32
32
  "@digital-go-jp/tailwind-theme-plugin": "^0.3.4",
33
33
  "ampless": "^0.2.0-alpha.6",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ampless",
3
- "version": "0.2.0-alpha.20",
3
+ "version": "0.2.0-alpha.22",
4
4
  "description": "Create a new ampless project",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,6 +0,0 @@
1
- import { admin } from '@/lib/admin'
2
- import { createMcpTokensRoute } from '@ampless/admin/api'
3
-
4
- export const dynamic = 'force-dynamic'
5
- export const runtime = 'nodejs'
6
- export const { GET, POST, DELETE } = createMcpTokensRoute(admin)
@@ -1,9 +0,0 @@
1
- import { admin } from '@/lib/admin'
2
- import { createMcpRoute } from '@ampless/admin/api'
3
-
4
- // `force-dynamic` keeps CloudFront from caching MCP responses. Each
5
- // tool call must hit the SSR Lambda — caching would serve stale or
6
- // cross-token responses.
7
- export const dynamic = 'force-dynamic'
8
- export const runtime = 'nodejs'
9
- export const { POST } = createMcpRoute(admin)