create-ampless 1.0.0-alpha.133 → 1.0.0-alpha.137

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
@@ -2156,6 +2156,38 @@ function detectIndent(jsonStr) {
2156
2156
  if (!m) return 2;
2157
2157
  return m[1].length;
2158
2158
  }
2159
+ async function bumpUserAmplessPlugins(destDir, projectPkg, templatePkg) {
2160
+ const result = { bumped: [], warnings: [] };
2161
+ const deps = projectPkg.dependencies ?? {};
2162
+ const templateDeps = templatePkg.dependencies ?? {};
2163
+ for (const pkgName of Object.keys(deps)) {
2164
+ if (!pkgName.startsWith("@ampless/")) continue;
2165
+ if (pkgName in templateDeps) continue;
2166
+ let latest;
2167
+ try {
2168
+ const { stdout } = await execa4("npm", ["view", `${pkgName}@alpha`, "version"], {
2169
+ cwd: destDir,
2170
+ stdio: ["ignore", "pipe", "pipe"]
2171
+ });
2172
+ latest = stdout.trim();
2173
+ } catch (e) {
2174
+ result.warnings.push(
2175
+ `${pkgName}: failed to resolve @alpha version (${e instanceof Error ? e.message : String(e)}). Leaving the existing pin in package.json.`
2176
+ );
2177
+ continue;
2178
+ }
2179
+ if (!latest) {
2180
+ result.warnings.push(`${pkgName}: npm view returned empty version. Skipping bump.`);
2181
+ continue;
2182
+ }
2183
+ const target = `^${latest}`;
2184
+ const current = deps[pkgName];
2185
+ if (current === target) continue;
2186
+ deps[pkgName] = target;
2187
+ result.bumped.push({ name: pkgName, from: current, to: target });
2188
+ }
2189
+ return result;
2190
+ }
2159
2191
  function djb2Hash(s) {
2160
2192
  let h = 5381;
2161
2193
  for (let i = 0; i < s.length; i++) {
@@ -2198,7 +2230,7 @@ async function regenerateEditorBootstrap(destDir, projectPkg) {
2198
2230
  lines.push("// editor extension, add / remove the corresponding @ampless/plugin-...");
2199
2231
  lines.push("// entry in your project's package.json (and register it in cms.config.ts");
2200
2232
  lines.push("// for runtime).", "");
2201
- lines.push("import { installAdminEditorExtensions } from '@ampless/admin/editor'");
2233
+ lines.push("import { installAdminEditorExtensions, installAdminTiptapNodeMarkdown } from '@ampless/admin/editor'");
2202
2234
  const identifiers = [];
2203
2235
  const seen = /* @__PURE__ */ new Map();
2204
2236
  for (const p3 of installedPlugins) {
@@ -2211,16 +2243,20 @@ async function regenerateEditorBootstrap(destDir, projectPkg) {
2211
2243
  }
2212
2244
  seen.set(ident, p3.packageName);
2213
2245
  const importSubpath = p3.editorExports.startsWith("./") ? `${p3.packageName}/${p3.editorExports.slice(2)}` : `${p3.packageName}/${p3.editorExports}`;
2214
- lines.push(`import { editorExtension as ${ident} } from '${importSubpath}'`);
2246
+ lines.push(`import * as ${ident} from '${importSubpath}'`);
2215
2247
  identifiers.push(ident);
2216
2248
  }
2217
2249
  lines.push("");
2218
2250
  lines.push("export function EditorBootstrap({ children }: { children: React.ReactNode }) {");
2219
2251
  if (identifiers.length === 0) {
2220
2252
  lines.push(" installAdminEditorExtensions([])");
2253
+ lines.push(" installAdminTiptapNodeMarkdown([])");
2221
2254
  } else {
2222
2255
  lines.push(" installAdminEditorExtensions([");
2223
- for (const id of identifiers) lines.push(` ${id},`);
2256
+ for (const id of identifiers) lines.push(` ${id}.editorExtension,`);
2257
+ lines.push(" ])");
2258
+ lines.push(" installAdminTiptapNodeMarkdown([");
2259
+ for (const id of identifiers) lines.push(` ${id}.tiptapNodeToMarkdown ?? {},`);
2224
2260
  lines.push(" ])");
2225
2261
  }
2226
2262
  lines.push(" return <>{children}</>");
@@ -2343,6 +2379,12 @@ async function runUpgradeIn(destDir, sharedDir, opts = {}) {
2343
2379
  projectPkg["scripts"] = projectScripts;
2344
2380
  }
2345
2381
  await writeFile3(projectPkgPath, JSON.stringify(projectPkg, null, indent) + "\n", "utf-8");
2382
+ const templatePkgForBump = templatePkg;
2383
+ const projectPkgForBump = projectPkg;
2384
+ const userPluginBump = await bumpUserAmplessPlugins(destDir, projectPkgForBump, templatePkgForBump);
2385
+ if (userPluginBump.bumped.length > 0) {
2386
+ await writeFile3(projectPkgPath, JSON.stringify(projectPkg, null, indent) + "\n", "utf-8");
2387
+ }
2346
2388
  if (!opts.noInstall) {
2347
2389
  const usePnpm = existsSync5(join3(destDir, "pnpm-lock.yaml"));
2348
2390
  const pm = usePnpm ? "pnpm" : "npm";
@@ -2356,6 +2398,11 @@ async function runUpgradeIn(destDir, sharedDir, opts = {}) {
2356
2398
  bootstrapResult.warnings.push(
2357
2399
  "_editor-bootstrap.tsx was generated without running npm install \u2014 node_modules may be stale. Run `npm run update-ampless` (without --no-install) to pick up newly added plugin editor extensions."
2358
2400
  );
2401
+ if (userPluginBump.bumped.length > 0) {
2402
+ userPluginBump.warnings.push(
2403
+ `package.json was updated with ${userPluginBump.bumped.length} bumped @ampless/* plugin version(s) but npm install was skipped \u2014 run \`npm install\` (or \`npm run update-ampless\` without --no-install) to apply the new versions.`
2404
+ );
2405
+ }
2359
2406
  }
2360
2407
  for (const w of bootstrapResult.warnings) {
2361
2408
  log3.warn(w);
@@ -2363,6 +2410,15 @@ async function runUpgradeIn(destDir, sharedDir, opts = {}) {
2363
2410
  log3.info(
2364
2411
  `editor: ${pc3.cyan(`auto-wired ${bootstrapResult.pluginCount} editor extension(s) in _editor-bootstrap.tsx`)}`
2365
2412
  );
2413
+ if (userPluginBump.bumped.length > 0) {
2414
+ log3.info(`plugins: ${pc3.cyan(`bumped ${userPluginBump.bumped.length} user-installed @ampless/* plugin(s) to latest @alpha:`)}`);
2415
+ for (const { name, from, to } of userPluginBump.bumped) {
2416
+ log3.info(` ${name}: ${from} \u2192 ${to}`);
2417
+ }
2418
+ }
2419
+ for (const w of userPluginBump.warnings) {
2420
+ log3.warn(w);
2421
+ }
2366
2422
  return {
2367
2423
  added: replaceNew,
2368
2424
  updated: replaceUpdate,
@@ -2373,7 +2429,8 @@ async function runUpgradeIn(destDir, sharedDir, opts = {}) {
2373
2429
  themesQuarantined: themeResult.quarantined,
2374
2430
  packageJsonMerged: true,
2375
2431
  obsoleteRemoved: obsoleteFiles,
2376
- editorExtensionsWired: bootstrapResult.pluginCount
2432
+ editorExtensionsWired: bootstrapResult.pluginCount,
2433
+ userPluginsBumped: userPluginBump.bumped.length
2377
2434
  };
2378
2435
  }
2379
2436
  async function runUpgrade(args) {
@@ -6,7 +6,9 @@ import { admin } from '@/lib/admin'
6
6
  * POST a draft Post to this endpoint while the preview tab is open; we
7
7
  * render the body + page-level scripts via `ampless.renderBody` /
8
8
  * `publicPostScriptsForPage` and return a fully-rendered HTML string
9
- * that the admin shows in an iframe (`sandbox="allow-scripts"`).
9
+ * that the admin shows in an iframe (`sandbox="allow-scripts allow-same-origin"`,
10
+ * v1 trust boundary expansion — admin preview content / plugin script are
11
+ * explicitly treated as trusted; see the iframe comment in post-form.tsx).
10
12
  *
11
13
  * Why a Route Handler instead of a Server Action: Next.js 15+
12
14
  * refuses to compile Client Components that reach `react-dom/server`
@@ -848,6 +848,12 @@ export default createAdminLayout(admin, { editorBootstrap: EditorBootstrap })
848
848
 
849
849
  `installAdminEditorExtensions` は idempotent で、render 時に client component 内で呼ばれる。admin の `<TiptapEditor>` は毎回 render 時に登録済みリストを built-in extensions の末尾に spread する。
850
850
 
851
+ ### markdown → tiptap の復元
852
+
853
+ editor Node が markdown へ bare URL line として serialise される場合、逆方向の復元は paste rule ではなく `Node.parseHTML()` で扱う必要がある。admin の `markdown → tiptap` format switch は、まず markdown を HTML に変換する。GFM autolink により bare URL line は `<p><a href="https://...">https://...</a></p>` になり、その HTML を tiptap が document として parse する。paste rule は user paste / typing event 用なので、この HTML parse 経路では発火しない。
854
+
855
+ embed 系 Node は、上記 paragraph shape 用の high-priority parse rule を追加し、必要に応じて他の HTML-to-tiptap 経路向けに `a[href]` rule も追加する。paragraph rule は「paragraph が単一 link だけを含み、その link text が `href` と同じ」場合だけ match させる。これにより parser は paragraph 全体を block embed Node に置換でき、embed の前に空 paragraph が残らない。`getAttrs` は URL を検証し、match しない link では `false` を返して通常の Link mark にフォールバックさせる。
856
+
851
857
  ### active source と完全無効化
852
858
 
853
859
  **エディタ配線の active source は `package.json#dependencies`**(= `node_modules`)であり、`cms.config.ts` ではない。
@@ -857,7 +863,7 @@ export default createAdminLayout(admin, { editorBootstrap: EditorBootstrap })
857
863
 
858
864
  ### エディタプレビューのパイプライン
859
865
 
860
- admin の edit / new post フォームは preview ペインを `<iframe sandbox="allow-scripts">` で表示する。`srcDoc` はテンプレート側 preview Route Handler が返す HTML。テンプレート scaffold は handler を `app/(admin)/admin/preview/route.tsx` に同梱する:
866
+ admin の edit / new post フォームは preview ペインを `<iframe sandbox="allow-scripts allow-same-origin">` で表示する。`srcDoc` はテンプレート側 preview Route Handler が返す HTML。テンプレート scaffold は handler を `app/(admin)/admin/preview/route.tsx` に同梱する:
861
867
 
862
868
  ```tsx
863
869
  // templates/_shared/app/(admin)/admin/preview/route.tsx
@@ -911,7 +917,9 @@ export default createEditPostPage(admin, { previewEndpoint: '/cms/admin/preview'
911
917
 
912
918
  Server Action ではなく Route Handler を採用した理由: `'use server'` モジュール内部で `react-dom/server` 由来の rendering を行うと、Next.js 15+ が edit-post page の compile に失敗する。build 時に Client Component から Server Action モジュール経由で import graph を辿るため、その経路上で `react-dom/server` に到達した時点で「You're importing a component that imports react-dom/server」チェックが発火する。Route Handler に切り出すことで rendering を import graph から完全に切り離せる — `<PostForm>` は plain な HTTP endpoint を fetch するだけで、bundler は form から handler 側へ踏み込まない。さらに handler 自身が `admin.isEditor()` を明示的にチェックすることで、将来 `(admin)` route-group gate が誤設定された場合の安全側のフェールセーフになる。`react-dom/server` の import 自体を dynamic にしているのは Next.js 16 の Turbopack が、Route Handler を含む app router build 経路で reach できる top-level の `react-dom/server` static import を一律で flag するため。request 時に解決することで build-time import-graph walker から外しつつ、runtime では同じ Node.js subpath からロードする。
913
919
 
914
- iframe `sandbox="allow-scripts"` は `allow-same-origin` を含まないので、preview の中身は opaque origin で動作する → widget script は admin の DOM に触れない。トレードオフ: widget が自身の origin storage にアクセスできない場合に正しく動かない可能性がある。Phase 7 時点の dogfood では YouTube iframe / x.com widgets.js とも opaque origin で動作することを確認済み。将来 non-opaque origin が必要な widget が出てきた場合、escape hatch は別 subdomain preview route + 適切な CSP sandbox flag を緩めるのは取らない。
920
+ **Preview iframe sandbox — v1 trust boundary 拡張:** iframe は `sandbox="allow-scripts allow-same-origin"` を使う。`srcDoc` とともに使用すると iframe は admin の origin を継承し、サードパーティ embed widget(YouTube SDK、x.com `widgets.js`)が動作できるようになる opaque-origin(`allow-scripts` のみ)の iframe では動作を拒否する(非 HttpOnly storage / cache へのアクセスと real-origin requests が必要なため)。同時に same-origin により preview スクリプトは admin auth state / HttpOnly storage / DOM へのアクセスと authenticated same-origin XHR / fetch の発行が可能になる。
921
+
922
+ これは単なる sandbox 緩和ではなく、**v1 の明示的な設計判断**です: **ampless v1 は admin preview content / plugin script を全部 trusted とみなす**。エンジニアは npm install 前に plugin を審査するし(カスタマイズベース CMS モデル)、body content はこのサイトの trusted editor が作成する。`<PostHistoryPanel>` では別の editor が作成した過去 revision(revision author ≠ preview viewer)をプレビューするケースも通常発生するが、v1 ではそれも明示的に trust ring 内と位置づける。より安全な代替案(別 origin preview route + CSP / COEP / COOP)は v2.0+ でリアルなプラグインマーケットプレイスが必要になった場合に再検討する。
915
923
 
916
924
  ---
917
925
 
@@ -1104,6 +1104,24 @@ export default createAdminLayout(admin, { editorBootstrap: EditorBootstrap })
1104
1104
  inside a client component. The admin's `<TiptapEditor>` spreads the
1105
1105
  registered list onto its built-in extensions on every render.
1106
1106
 
1107
+ ### Markdown to tiptap restoration
1108
+
1109
+ If an editor Node serializes to markdown as a bare URL line, the reverse
1110
+ direction must be handled by `Node.parseHTML()`, not by paste rules. The
1111
+ admin's `markdown → tiptap` format switch first converts markdown to HTML;
1112
+ GFM autolinks emit a bare URL line as
1113
+ `<p><a href="https://...">https://...</a></p>`, then tiptap parses that
1114
+ HTML into a document. Paste rules only run for user paste / typing events,
1115
+ so they do not fire on this HTML parse path.
1116
+
1117
+ Embed-style Nodes should add a high-priority parse rule for the paragraph
1118
+ shape above, and may also add an `a[href]` rule for other HTML-to-tiptap
1119
+ paths. The paragraph rule should only match when the paragraph contains a
1120
+ single link whose text equals its `href`; that lets the parser replace the
1121
+ whole paragraph with the block embed Node instead of leaving an empty
1122
+ paragraph before the embed. `getAttrs` should validate the URL and return
1123
+ `false` for non-matching links so the normal Link mark remains in place.
1124
+
1107
1125
  ### Active source and full disable
1108
1126
 
1109
1127
  **The active source for editor wiring is `package.json#dependencies`**
@@ -1120,7 +1138,7 @@ registered list onto its built-in extensions on every render.
1120
1138
  ### Editor preview pipeline
1121
1139
 
1122
1140
  The admin's edit / new post forms render the preview pane in an
1123
- `<iframe sandbox="allow-scripts">` whose `srcDoc` is the HTML returned
1141
+ `<iframe sandbox="allow-scripts allow-same-origin">` whose `srcDoc` is the HTML returned
1124
1142
  by the template's preview Route Handler. The template scaffold ships
1125
1143
  the handler at `app/(admin)/admin/preview/route.tsx`:
1126
1144
 
@@ -1195,14 +1213,24 @@ included; deferring it to request time keeps the module outside the
1195
1213
  build-time import-graph walker while still loading it from the same
1196
1214
  Node.js subpath at runtime.
1197
1215
 
1198
- The iframe's `sandbox="allow-scripts"` (without `allow-same-origin`)
1199
- keeps preview content in an opaque origin so widget scripts cannot
1200
- reach the admin's DOM. The trade-off is that some widgets may misbehave
1201
- when they can't access their own origin's storage; the YouTube iframe
1202
- embed and x.com widgets.js both work under these constraints in
1203
- dogfood as of Phase 7. If a future widget needs a non-opaque preview
1204
- origin, the escape hatch is a separate preview route on a different
1205
- subdomain with appropriate CSP not relaxing the sandbox flag.
1216
+ **Preview iframe sandbox v1 trust boundary expansion:** The iframe uses
1217
+ `sandbox="allow-scripts allow-same-origin"`. With `srcDoc`, this gives
1218
+ the iframe the admin's origin, which 3rd-party embed widgets (YouTube SDK,
1219
+ x.com `widgets.js`) require they refuse to initialise in an opaque-origin
1220
+ (`allow-scripts`-only) iframe because they need access to non-HttpOnly
1221
+ storage / cache and real-origin requests. Same-origin also gives the
1222
+ preview script access to the admin's auth state / non-HttpOnly storage /
1223
+ DOM and lets it issue authenticated same-origin XHR / fetch.
1224
+
1225
+ This is an explicit v1 design decision, not a no-op sandbox relax:
1226
+ **ampless v1 treats admin preview content / plugin script as trusted**.
1227
+ The engineer audits plugins before npm-installing them (customization-based
1228
+ CMS model), and body content is produced by trusted editors of this site.
1229
+ `<PostHistoryPanel>` can also surface a past revision authored by a
1230
+ different editor (revision author ≠ preview viewer) — v1 explicitly puts
1231
+ both inside the trust ring. The safer alternative — separate-origin preview
1232
+ route + CSP / COEP / COOP — is parked for v2.0+ if/when a real plugin
1233
+ marketplace lands.
1206
1234
 
1207
1235
  ---
1208
1236
 
@@ -26,21 +26,21 @@
26
26
  "@tiptap/pm": "^3.23.6",
27
27
  "@tiptap/react": "^3.23.6",
28
28
  "@tiptap/starter-kit": "^3.23.6",
29
- "@ampless/plugin-analytics-ga4": "^0.2.0-alpha.28",
30
- "@ampless/plugin-cookie-consent": "^0.1.0-alpha.19",
31
- "@ampless/plugin-gtm": "^0.2.0-alpha.27",
32
- "@ampless/plugin-og-image": "^0.2.0-alpha.44",
33
- "@ampless/plugin-plausible": "^0.2.0-alpha.27",
34
- "@ampless/plugin-reading-time": "^0.1.0-alpha.17",
35
- "@ampless/plugin-rss": "^0.2.0-alpha.44",
36
- "@ampless/plugin-schema-jsonld": "^0.1.1-alpha.23",
37
- "@ampless/plugin-seo": "^0.2.0-alpha.44",
38
- "@ampless/plugin-webhook": "^0.2.0-alpha.45",
39
- "@ampless/admin": "^1.0.0-alpha.77",
40
- "@ampless/backend": "^1.0.0-alpha.66",
41
- "@ampless/runtime": "^1.0.0-alpha.53",
29
+ "@ampless/plugin-analytics-ga4": "^0.2.0-alpha.30",
30
+ "@ampless/plugin-cookie-consent": "^0.1.0-alpha.21",
31
+ "@ampless/plugin-gtm": "^0.2.0-alpha.29",
32
+ "@ampless/plugin-og-image": "^0.2.0-alpha.46",
33
+ "@ampless/plugin-plausible": "^0.2.0-alpha.29",
34
+ "@ampless/plugin-reading-time": "^0.1.0-alpha.19",
35
+ "@ampless/plugin-rss": "^0.2.0-alpha.46",
36
+ "@ampless/plugin-schema-jsonld": "^0.1.1-alpha.25",
37
+ "@ampless/plugin-seo": "^0.2.0-alpha.46",
38
+ "@ampless/plugin-webhook": "^0.2.0-alpha.47",
39
+ "@ampless/admin": "^1.0.0-alpha.79",
40
+ "@ampless/backend": "^1.0.0-alpha.68",
41
+ "@ampless/runtime": "^1.0.0-alpha.55",
42
42
  "@digital-go-jp/tailwind-theme-plugin": "^0.3.4",
43
- "ampless": "^1.0.0-alpha.44",
43
+ "ampless": "^1.0.0-alpha.46",
44
44
  "aws-amplify": "^6.17.0",
45
45
  "class-variance-authority": "^0.7.1",
46
46
  "clsx": "^2.1.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ampless",
3
- "version": "1.0.0-alpha.133",
3
+ "version": "1.0.0-alpha.137",
4
4
  "description": "Create a new ampless project",
5
5
  "license": "MIT",
6
6
  "type": "module",