create-ampless 1.0.0-alpha.133 → 1.0.0-alpha.135
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 +61 -4
- package/dist/templates/_shared/app/(admin)/admin/preview/route.tsx +3 -1
- package/dist/templates/_shared/docs/plugin-author-guide.ja.md +4 -2
- package/dist/templates/_shared/docs/plugin-author-guide.md +19 -9
- package/dist/templates/_shared/package.json +14 -14
- package/package.json +1 -1
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
|
|
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`
|
|
@@ -857,7 +857,7 @@ export default createAdminLayout(admin, { editorBootstrap: EditorBootstrap })
|
|
|
857
857
|
|
|
858
858
|
### エディタプレビューのパイプライン
|
|
859
859
|
|
|
860
|
-
admin の edit / new post フォームは preview ペインを `<iframe sandbox="allow-scripts">` で表示する。`srcDoc` はテンプレート側 preview Route Handler が返す HTML。テンプレート scaffold は handler を `app/(admin)/admin/preview/route.tsx` に同梱する:
|
|
860
|
+
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
861
|
|
|
862
862
|
```tsx
|
|
863
863
|
// templates/_shared/app/(admin)/admin/preview/route.tsx
|
|
@@ -911,7 +911,9 @@ export default createEditPostPage(admin, { previewEndpoint: '/cms/admin/preview'
|
|
|
911
911
|
|
|
912
912
|
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
913
|
|
|
914
|
-
iframe
|
|
914
|
+
**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 の発行が可能になる。
|
|
915
|
+
|
|
916
|
+
これは単なる 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
917
|
|
|
916
918
|
---
|
|
917
919
|
|
|
@@ -1120,7 +1120,7 @@ registered list onto its built-in extensions on every render.
|
|
|
1120
1120
|
### Editor preview pipeline
|
|
1121
1121
|
|
|
1122
1122
|
The admin's edit / new post forms render the preview pane in an
|
|
1123
|
-
`<iframe sandbox="allow-scripts">` whose `srcDoc` is the HTML returned
|
|
1123
|
+
`<iframe sandbox="allow-scripts allow-same-origin">` whose `srcDoc` is the HTML returned
|
|
1124
1124
|
by the template's preview Route Handler. The template scaffold ships
|
|
1125
1125
|
the handler at `app/(admin)/admin/preview/route.tsx`:
|
|
1126
1126
|
|
|
@@ -1195,14 +1195,24 @@ included; deferring it to request time keeps the module outside the
|
|
|
1195
1195
|
build-time import-graph walker while still loading it from the same
|
|
1196
1196
|
Node.js subpath at runtime.
|
|
1197
1197
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1198
|
+
**Preview iframe sandbox — v1 trust boundary expansion:** The iframe uses
|
|
1199
|
+
`sandbox="allow-scripts allow-same-origin"`. With `srcDoc`, this gives
|
|
1200
|
+
the iframe the admin's origin, which 3rd-party embed widgets (YouTube SDK,
|
|
1201
|
+
x.com `widgets.js`) require — they refuse to initialise in an opaque-origin
|
|
1202
|
+
(`allow-scripts`-only) iframe because they need access to non-HttpOnly
|
|
1203
|
+
storage / cache and real-origin requests. Same-origin also gives the
|
|
1204
|
+
preview script access to the admin's auth state / non-HttpOnly storage /
|
|
1205
|
+
DOM and lets it issue authenticated same-origin XHR / fetch.
|
|
1206
|
+
|
|
1207
|
+
This is an explicit v1 design decision, not a no-op sandbox relax:
|
|
1208
|
+
**ampless v1 treats admin preview content / plugin script as trusted**.
|
|
1209
|
+
The engineer audits plugins before npm-installing them (customization-based
|
|
1210
|
+
CMS model), and body content is produced by trusted editors of this site.
|
|
1211
|
+
`<PostHistoryPanel>` can also surface a past revision authored by a
|
|
1212
|
+
different editor (revision author ≠ preview viewer) — v1 explicitly puts
|
|
1213
|
+
both inside the trust ring. The safer alternative — separate-origin preview
|
|
1214
|
+
route + CSP / COEP / COOP — is parked for v2.0+ if/when a real plugin
|
|
1215
|
+
marketplace lands.
|
|
1206
1216
|
|
|
1207
1217
|
---
|
|
1208
1218
|
|
|
@@ -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.
|
|
30
|
-
"@ampless/plugin-cookie-consent": "^0.1.0-alpha.
|
|
31
|
-
"@ampless/plugin-gtm": "^0.2.0-alpha.
|
|
32
|
-
"@ampless/plugin-og-image": "^0.2.0-alpha.
|
|
33
|
-
"@ampless/plugin-plausible": "^0.2.0-alpha.
|
|
34
|
-
"@ampless/plugin-reading-time": "^0.1.0-alpha.
|
|
35
|
-
"@ampless/plugin-rss": "^0.2.0-alpha.
|
|
36
|
-
"@ampless/plugin-schema-jsonld": "^0.1.1-alpha.
|
|
37
|
-
"@ampless/plugin-seo": "^0.2.0-alpha.
|
|
38
|
-
"@ampless/plugin-webhook": "^0.2.0-alpha.
|
|
39
|
-
"@ampless/admin": "^1.0.0-alpha.
|
|
40
|
-
"@ampless/backend": "^1.0.0-alpha.
|
|
41
|
-
"@ampless/runtime": "^1.0.0-alpha.
|
|
29
|
+
"@ampless/plugin-analytics-ga4": "^0.2.0-alpha.29",
|
|
30
|
+
"@ampless/plugin-cookie-consent": "^0.1.0-alpha.20",
|
|
31
|
+
"@ampless/plugin-gtm": "^0.2.0-alpha.28",
|
|
32
|
+
"@ampless/plugin-og-image": "^0.2.0-alpha.45",
|
|
33
|
+
"@ampless/plugin-plausible": "^0.2.0-alpha.28",
|
|
34
|
+
"@ampless/plugin-reading-time": "^0.1.0-alpha.18",
|
|
35
|
+
"@ampless/plugin-rss": "^0.2.0-alpha.45",
|
|
36
|
+
"@ampless/plugin-schema-jsonld": "^0.1.1-alpha.24",
|
|
37
|
+
"@ampless/plugin-seo": "^0.2.0-alpha.45",
|
|
38
|
+
"@ampless/plugin-webhook": "^0.2.0-alpha.46",
|
|
39
|
+
"@ampless/admin": "^1.0.0-alpha.78",
|
|
40
|
+
"@ampless/backend": "^1.0.0-alpha.67",
|
|
41
|
+
"@ampless/runtime": "^1.0.0-alpha.54",
|
|
42
42
|
"@digital-go-jp/tailwind-theme-plugin": "^0.3.4",
|
|
43
|
-
"ampless": "^1.0.0-alpha.
|
|
43
|
+
"ampless": "^1.0.0-alpha.45",
|
|
44
44
|
"aws-amplify": "^6.17.0",
|
|
45
45
|
"class-variance-authority": "^0.7.1",
|
|
46
46
|
"clsx": "^2.1.1",
|