create-ampless 1.0.0-alpha.98 → 1.0.0-beta.148
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/README.ja.md +4 -4
- package/README.md +4 -4
- package/dist/index.js +198 -29
- package/dist/templates/_shared/README.ja.md +4 -4
- package/dist/templates/_shared/README.md +4 -4
- package/dist/templates/_shared/amplify/backend.ts +1 -1
- package/dist/templates/_shared/amplify/data/get-published-post.js +13 -1
- package/dist/templates/_shared/amplify/data/list-posts-by-tag.js +15 -3
- package/dist/templates/_shared/amplify/data/list-published-posts.js +26 -15
- package/dist/templates/_shared/amplify/events/dispatcher/handler.ts +11 -1
- package/dist/templates/_shared/amplify/secrets/encryption-key.ts +2 -2
- package/dist/templates/_shared/app/(admin)/admin/_editor-bootstrap.tsx +13 -0
- package/dist/templates/_shared/app/(admin)/admin/layout.tsx +2 -1
- package/dist/templates/_shared/app/(admin)/admin/preview/route.tsx +4 -0
- package/dist/templates/_shared/cms.config.ts +3 -0
- package/dist/templates/_shared/docs/plugin-author-guide.ja.md +841 -31
- package/dist/templates/_shared/docs/plugin-author-guide.md +1147 -80
- package/dist/templates/_shared/package.json +17 -16
- package/dist/templates/_shared/plugins/README.ja.md +1 -1
- package/dist/templates/_shared/plugins/README.md +1 -1
- package/dist/templates/blog/pages/home.tsx +5 -5
- package/dist/templates/blog/pages/post.tsx +5 -3
- package/dist/templates/corporate/pages/home.tsx +5 -5
- package/dist/templates/corporate/pages/post.tsx +5 -3
- package/dist/templates/dads/pages/home.tsx +5 -5
- package/dist/templates/dads/pages/post.tsx +5 -3
- package/dist/templates/docs/pages/post.tsx +5 -3
- package/dist/templates/landing/pages/home.tsx +5 -5
- package/dist/templates/landing/pages/post.tsx +5 -3
- package/dist/templates/minimal/pages/post.tsx +5 -3
- package/dist/templates/plugin-local/README.md +1 -1
- package/dist/templates/plugin-standalone/README.ja.md +3 -3
- package/dist/templates/plugin-standalone/README.md +3 -3
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
[ampless](https://github.com/heavymoons/ampless) プロジェクト向け CLI スキャフォールディングツール。
|
|
7
7
|
|
|
8
|
-
> **プレリリース /
|
|
8
|
+
> **プレリリース / ベータ版。** v1.0 まではマイナーバージョンでも破壊的変更が入る可能性があります。`@beta` タグを使用してください(`@latest` タグは v1.0 まで存在しません)。
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
npx create-ampless@
|
|
11
|
+
npx create-ampless@beta
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
ウィザードが以下を順に案内します:
|
|
@@ -36,7 +36,7 @@ npm run dev # http://localhost:3000
|
|
|
36
36
|
ウィザードは `npx` から Amplify Hosting の URL まで一気に進めることもできます:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
npx create-ampless@
|
|
39
|
+
npx create-ampless@beta my-site --deploy
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
このフラグを追加すると、スキャフォールディング後に以下が実行されます:
|
|
@@ -51,7 +51,7 @@ npx create-ampless@alpha my-site --deploy
|
|
|
51
51
|
コマンドラインに指定が不足している場合はインタラクティブに確認します。CI 向けのフル指定の例:
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
npx create-ampless@
|
|
54
|
+
npx create-ampless@beta my-site --deploy \
|
|
55
55
|
--github-owner my-org \
|
|
56
56
|
--github-private \
|
|
57
57
|
--aws-region us-east-1 \
|
package/README.md
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
CLI scaffolding tool for [ampless](https://github.com/heavymoons/ampless) projects.
|
|
7
7
|
|
|
8
|
-
> **Pre-release /
|
|
8
|
+
> **Pre-release / beta.** Breaking changes possible in any minor version until v1.0. Use the `@beta` tag (the `@latest` tag won't exist until v1.0).
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
npx create-ampless@
|
|
11
|
+
npx create-ampless@beta
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
The wizard walks you through:
|
|
@@ -36,7 +36,7 @@ Sign up at `/login` — the first registered user is automatically promoted to t
|
|
|
36
36
|
The wizard can also take you all the way from `npx` to a live Amplify Hosting URL:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
npx create-ampless@
|
|
39
|
+
npx create-ampless@beta my-site --deploy
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
That extra flag, after scaffolding, runs:
|
|
@@ -51,7 +51,7 @@ That extra flag, after scaffolding, runs:
|
|
|
51
51
|
Anything missing on the command line gets asked interactively. For CI-friendly fully-flagged usage:
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
npx create-ampless@
|
|
54
|
+
npx create-ampless@beta my-site --deploy \
|
|
55
55
|
--github-owner my-org \
|
|
56
56
|
--github-private \
|
|
57
57
|
--aws-region us-east-1 \
|
package/dist/index.js
CHANGED
|
@@ -516,12 +516,12 @@ function parseDeployArgs(argv) {
|
|
|
516
516
|
var HELP_TEXT = `create-ampless \u2014 scaffold an ampless project
|
|
517
517
|
|
|
518
518
|
Usage:
|
|
519
|
-
npx create-ampless@
|
|
520
|
-
npx create-ampless@
|
|
521
|
-
npx create-ampless@
|
|
522
|
-
npx create-ampless@
|
|
523
|
-
npx create-ampless@
|
|
524
|
-
npx create-ampless@
|
|
519
|
+
npx create-ampless@beta <project-name> [options]
|
|
520
|
+
npx create-ampless@beta --mount [options] # in an existing project dir
|
|
521
|
+
npx create-ampless@beta upgrade [options] # in an existing project dir
|
|
522
|
+
npx create-ampless@beta copy-theme <src> <dst> # in an existing project dir
|
|
523
|
+
npx create-ampless@beta plugin <name> [options] # scaffold a plugin (Phase 5)
|
|
524
|
+
npx create-ampless@beta setup-encryption-key [--gitignore] # generate encryption key file
|
|
525
525
|
|
|
526
526
|
Options:
|
|
527
527
|
--site-name <name> Site display name (default: "My Blog")
|
|
@@ -558,7 +558,7 @@ Options:
|
|
|
558
558
|
flag values (for CI / automation)
|
|
559
559
|
-h, --help Show this message
|
|
560
560
|
|
|
561
|
-
upgrade Sync ampless package files / deps to latest
|
|
561
|
+
upgrade Sync ampless package files / deps to latest beta.
|
|
562
562
|
Run inside an existing ampless project.
|
|
563
563
|
|
|
564
564
|
--dry-run Show what would change without writing any files
|
|
@@ -571,7 +571,7 @@ copy-theme <source> <target>
|
|
|
571
571
|
(the convention that flags it as user-owned, so upgrade leaves
|
|
572
572
|
it alone). Run inside an existing ampless project.
|
|
573
573
|
|
|
574
|
-
Example: npx create-ampless@
|
|
574
|
+
Example: npx create-ampless@beta copy-theme blog my-blog
|
|
575
575
|
|
|
576
576
|
plugin <name>
|
|
577
577
|
Scaffold an ampless plugin. Two modes:
|
|
@@ -596,8 +596,8 @@ plugin <name>
|
|
|
596
596
|
--description "<text>" Optional one-line description
|
|
597
597
|
|
|
598
598
|
Examples:
|
|
599
|
-
npx create-ampless@
|
|
600
|
-
npx create-ampless@
|
|
599
|
+
npx create-ampless@beta plugin site-verification
|
|
600
|
+
npx create-ampless@beta plugin @ishinao/ampless-plugin-foo --standalone
|
|
601
601
|
|
|
602
602
|
setup-encryption-key
|
|
603
603
|
Generate the AES-256-GCM encryption key for plugin secret storage
|
|
@@ -624,8 +624,8 @@ setup-encryption-key
|
|
|
624
624
|
Rotation: re-run with confirm overwrite. Existing ciphertext
|
|
625
625
|
becomes unreadable; re-save each secret via /admin/plugins.
|
|
626
626
|
|
|
627
|
-
Example: npx create-ampless@
|
|
628
|
-
Example: npx create-ampless@
|
|
627
|
+
Example: npx create-ampless@beta setup-encryption-key
|
|
628
|
+
Example: npx create-ampless@beta setup-encryption-key --gitignore
|
|
629
629
|
`;
|
|
630
630
|
|
|
631
631
|
// src/deploy-prompts.ts
|
|
@@ -1885,23 +1885,41 @@ var AMPLESS_MANAGED_APP_PATHS = [
|
|
|
1885
1885
|
"app/login",
|
|
1886
1886
|
"app/site"
|
|
1887
1887
|
];
|
|
1888
|
-
var AMPLESS_RETIRED_PATHS = [
|
|
1888
|
+
var AMPLESS_RETIRED_PATHS = [
|
|
1889
|
+
// Phase 7 preview pipeline migrated from a `'use server'` action to a
|
|
1890
|
+
// Route Handler at `app/(admin)/admin/preview/route.tsx` (default
|
|
1891
|
+
// endpoint: `/admin/preview`). The old action made Next.js 15+ refuse
|
|
1892
|
+
// to compile the edit-post page
|
|
1893
|
+
// because the import graph traced `react-dom/server` from Client
|
|
1894
|
+
// Components through Server Action modules. Sites scaffolded before
|
|
1895
|
+
// this change pick the new endpoint up on their next `update-ampless`
|
|
1896
|
+
// — the new route file is seeded as part of the normal template
|
|
1897
|
+
// sync, and the old action file is retired by this entry.
|
|
1898
|
+
"app/(admin)/admin/_actions/render-preview.tsx"
|
|
1899
|
+
];
|
|
1889
1900
|
var AMPLESS_PACKAGES = /* @__PURE__ */ new Set([
|
|
1890
1901
|
"ampless",
|
|
1891
1902
|
"@ampless/admin",
|
|
1892
1903
|
"@ampless/backend",
|
|
1893
|
-
"@ampless/runtime",
|
|
1894
|
-
"@ampless/plugin-seo",
|
|
1895
|
-
"@ampless/plugin-rss",
|
|
1896
|
-
"@ampless/plugin-schema-jsonld",
|
|
1897
|
-
"@ampless/plugin-webhook",
|
|
1898
|
-
"@ampless/plugin-og-image",
|
|
1899
1904
|
"@ampless/plugin-analytics-ga4",
|
|
1905
|
+
"@ampless/plugin-cookie-consent",
|
|
1900
1906
|
"@ampless/plugin-gtm",
|
|
1907
|
+
"@ampless/plugin-og-image",
|
|
1901
1908
|
"@ampless/plugin-plausible",
|
|
1902
|
-
"@ampless/plugin-
|
|
1903
|
-
"@ampless/plugin-
|
|
1909
|
+
"@ampless/plugin-reading-time",
|
|
1910
|
+
"@ampless/plugin-rss",
|
|
1911
|
+
"@ampless/plugin-schema-jsonld",
|
|
1912
|
+
"@ampless/plugin-seo",
|
|
1913
|
+
"@ampless/plugin-webhook",
|
|
1914
|
+
"@ampless/plugin-x-embed",
|
|
1915
|
+
"@ampless/plugin-youtube",
|
|
1916
|
+
"@ampless/runtime"
|
|
1904
1917
|
]);
|
|
1918
|
+
var AMPLESS_MANAGED_TRANSITIVE_PREFIXES = ["@tiptap/"];
|
|
1919
|
+
function isManagedDep(name) {
|
|
1920
|
+
if (AMPLESS_PACKAGES.has(name)) return true;
|
|
1921
|
+
return AMPLESS_MANAGED_TRANSITIVE_PREFIXES.some((p3) => name.startsWith(p3));
|
|
1922
|
+
}
|
|
1905
1923
|
var AMPLESS_MANAGED_SCRIPTS = /* @__PURE__ */ new Set([
|
|
1906
1924
|
"sandbox",
|
|
1907
1925
|
"sandbox:dev",
|
|
@@ -2138,6 +2156,120 @@ function detectIndent(jsonStr) {
|
|
|
2138
2156
|
if (!m) return 2;
|
|
2139
2157
|
return m[1].length;
|
|
2140
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}@beta`, "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 @beta 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
|
+
}
|
|
2191
|
+
function djb2Hash(s) {
|
|
2192
|
+
let h = 5381;
|
|
2193
|
+
for (let i = 0; i < s.length; i++) {
|
|
2194
|
+
h = h * 33 ^ s.charCodeAt(i) | 0;
|
|
2195
|
+
}
|
|
2196
|
+
return (h >>> 0).toString(16);
|
|
2197
|
+
}
|
|
2198
|
+
async function regenerateEditorBootstrap(destDir, projectPkg) {
|
|
2199
|
+
const targetPath = join3(destDir, "app", "(admin)", "admin", "_editor-bootstrap.tsx");
|
|
2200
|
+
const installedPlugins = [];
|
|
2201
|
+
const warnings = [];
|
|
2202
|
+
const deps = projectPkg.dependencies ?? {};
|
|
2203
|
+
for (const pkgName of Object.keys(deps)) {
|
|
2204
|
+
const pkgJsonPath = join3(destDir, "node_modules", pkgName, "package.json");
|
|
2205
|
+
if (!existsSync5(pkgJsonPath)) continue;
|
|
2206
|
+
let mf;
|
|
2207
|
+
try {
|
|
2208
|
+
mf = JSON.parse(await readFile3(pkgJsonPath, "utf-8"));
|
|
2209
|
+
} catch (e) {
|
|
2210
|
+
warnings.push(`${pkgName}: failed to read package.json (${e instanceof Error ? e.message : String(e)}). Skipping editor wiring.`);
|
|
2211
|
+
continue;
|
|
2212
|
+
}
|
|
2213
|
+
const ep = mf?.amplessPlugin?.editorExports;
|
|
2214
|
+
if (typeof ep !== "string") continue;
|
|
2215
|
+
if (!ep.startsWith("./") || ep.includes("..") || ep.startsWith("/") || /[?#]/.test(ep)) {
|
|
2216
|
+
warnings.push(`${pkgName}: amplessPlugin.editorExports="${ep}" is invalid (must be a ./-prefixed relative subpath without .. or query/fragment). Skipping editor wiring.`);
|
|
2217
|
+
continue;
|
|
2218
|
+
}
|
|
2219
|
+
if (!mf?.exports || typeof mf.exports[ep] === "undefined") {
|
|
2220
|
+
warnings.push(`${pkgName}: amplessPlugin.editorExports="${ep}" is not declared in package.json#exports. Skipping editor wiring.`);
|
|
2221
|
+
continue;
|
|
2222
|
+
}
|
|
2223
|
+
installedPlugins.push({ packageName: pkgName, editorExports: ep });
|
|
2224
|
+
}
|
|
2225
|
+
installedPlugins.sort((a, b) => a.packageName.localeCompare(b.packageName));
|
|
2226
|
+
const lines = [];
|
|
2227
|
+
lines.push("'use client'", "");
|
|
2228
|
+
lines.push("// AUTO-GENERATED by `npm run update-ampless`. Do not edit \u2014 your changes");
|
|
2229
|
+
lines.push("// will be overwritten on the next run. To enable / disable a plugin's");
|
|
2230
|
+
lines.push("// editor extension, add / remove the corresponding @ampless/plugin-...");
|
|
2231
|
+
lines.push("// entry in your project's package.json (and register it in cms.config.ts");
|
|
2232
|
+
lines.push("// for runtime).", "");
|
|
2233
|
+
lines.push("import { installAdminEditorExtensions, installAdminTiptapNodeMarkdown, installAdminTiptapNodeHtml } from '@ampless/admin/editor'");
|
|
2234
|
+
const identifiers = [];
|
|
2235
|
+
const seen = /* @__PURE__ */ new Map();
|
|
2236
|
+
for (const p3 of installedPlugins) {
|
|
2237
|
+
let ident = "__" + p3.packageName.replace(/^@/, "").replace(/[^a-zA-Z0-9]+/g, "_") + "_editor";
|
|
2238
|
+
if (seen.has(ident) && seen.get(ident) !== p3.packageName) {
|
|
2239
|
+
const suffix = djb2Hash(p3.packageName).slice(0, 8);
|
|
2240
|
+
const orig = ident;
|
|
2241
|
+
ident = `${ident}_${suffix}`;
|
|
2242
|
+
warnings.push(`editor-bootstrap codegen: sanitised identifier "${orig}" collides between "${seen.get(orig)}" and "${p3.packageName}". Using "${ident}" for the latter to disambiguate.`);
|
|
2243
|
+
}
|
|
2244
|
+
seen.set(ident, p3.packageName);
|
|
2245
|
+
const importSubpath = p3.editorExports.startsWith("./") ? `${p3.packageName}/${p3.editorExports.slice(2)}` : `${p3.packageName}/${p3.editorExports}`;
|
|
2246
|
+
lines.push(`import * as ${ident} from '${importSubpath}'`);
|
|
2247
|
+
identifiers.push(ident);
|
|
2248
|
+
}
|
|
2249
|
+
lines.push("");
|
|
2250
|
+
lines.push("export function EditorBootstrap({ children }: { children: React.ReactNode }) {");
|
|
2251
|
+
if (identifiers.length === 0) {
|
|
2252
|
+
lines.push(" installAdminEditorExtensions([])");
|
|
2253
|
+
lines.push(" installAdminTiptapNodeMarkdown([])");
|
|
2254
|
+
lines.push(" installAdminTiptapNodeHtml([])");
|
|
2255
|
+
} else {
|
|
2256
|
+
lines.push(" installAdminEditorExtensions([");
|
|
2257
|
+
for (const id of identifiers) lines.push(` ${id}.editorExtension,`);
|
|
2258
|
+
lines.push(" ])");
|
|
2259
|
+
lines.push(" installAdminTiptapNodeMarkdown([");
|
|
2260
|
+
for (const id of identifiers) lines.push(` ${id}.tiptapNodeToMarkdown ?? {},`);
|
|
2261
|
+
lines.push(" ])");
|
|
2262
|
+
lines.push(" installAdminTiptapNodeHtml([");
|
|
2263
|
+
for (const id of identifiers) lines.push(` ${id}.tiptapNodeToHtml ?? {},`);
|
|
2264
|
+
lines.push(" ])");
|
|
2265
|
+
}
|
|
2266
|
+
lines.push(" return <>{children}</>");
|
|
2267
|
+
lines.push("}");
|
|
2268
|
+
lines.push("");
|
|
2269
|
+
await mkdir2(dirname(targetPath), { recursive: true });
|
|
2270
|
+
await writeFile3(targetPath, lines.join("\n"), "utf-8");
|
|
2271
|
+
return { path: targetPath, pluginCount: installedPlugins.length, warnings };
|
|
2272
|
+
}
|
|
2141
2273
|
async function runUpgradeIn(destDir, sharedDir, opts = {}) {
|
|
2142
2274
|
const derivedRoot = opts.templatesRoot ?? join3(sharedDir, "..");
|
|
2143
2275
|
const themeSyncEnabled = existsSync5(join3(derivedRoot, "_shared"));
|
|
@@ -2231,7 +2363,7 @@ async function runUpgradeIn(destDir, sharedDir, opts = {}) {
|
|
|
2231
2363
|
const templateDeps = templatePkg[section] ?? {};
|
|
2232
2364
|
const projectDeps = projectPkg[section] ?? {};
|
|
2233
2365
|
for (const [name, version] of Object.entries(templateDeps)) {
|
|
2234
|
-
if (!
|
|
2366
|
+
if (!isManagedDep(name)) continue;
|
|
2235
2367
|
projectDeps[name] = version;
|
|
2236
2368
|
}
|
|
2237
2369
|
if (Object.keys(projectDeps).length > 0) {
|
|
@@ -2251,11 +2383,46 @@ async function runUpgradeIn(destDir, sharedDir, opts = {}) {
|
|
|
2251
2383
|
projectPkg["scripts"] = projectScripts;
|
|
2252
2384
|
}
|
|
2253
2385
|
await writeFile3(projectPkgPath, JSON.stringify(projectPkg, null, indent) + "\n", "utf-8");
|
|
2386
|
+
const templatePkgForBump = templatePkg;
|
|
2387
|
+
const projectPkgForBump = projectPkg;
|
|
2388
|
+
const userPluginBump = await bumpUserAmplessPlugins(destDir, projectPkgForBump, templatePkgForBump);
|
|
2389
|
+
if (userPluginBump.bumped.length > 0) {
|
|
2390
|
+
await writeFile3(projectPkgPath, JSON.stringify(projectPkg, null, indent) + "\n", "utf-8");
|
|
2391
|
+
}
|
|
2254
2392
|
if (!opts.noInstall) {
|
|
2255
2393
|
const usePnpm = existsSync5(join3(destDir, "pnpm-lock.yaml"));
|
|
2256
2394
|
const pm = usePnpm ? "pnpm" : "npm";
|
|
2257
2395
|
await execa4(pm, ["install"], { cwd: destDir, stdio: "inherit" });
|
|
2258
2396
|
}
|
|
2397
|
+
const codegenProjectPkg = JSON.parse(
|
|
2398
|
+
await readFile3(join3(destDir, "package.json"), "utf-8")
|
|
2399
|
+
);
|
|
2400
|
+
const bootstrapResult = await regenerateEditorBootstrap(destDir, codegenProjectPkg);
|
|
2401
|
+
if (opts.noInstall) {
|
|
2402
|
+
bootstrapResult.warnings.push(
|
|
2403
|
+
"_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."
|
|
2404
|
+
);
|
|
2405
|
+
if (userPluginBump.bumped.length > 0) {
|
|
2406
|
+
userPluginBump.warnings.push(
|
|
2407
|
+
`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.`
|
|
2408
|
+
);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
for (const w of bootstrapResult.warnings) {
|
|
2412
|
+
log3.warn(w);
|
|
2413
|
+
}
|
|
2414
|
+
log3.info(
|
|
2415
|
+
`editor: ${pc3.cyan(`auto-wired ${bootstrapResult.pluginCount} editor extension(s) in _editor-bootstrap.tsx`)}`
|
|
2416
|
+
);
|
|
2417
|
+
if (userPluginBump.bumped.length > 0) {
|
|
2418
|
+
log3.info(`plugins: ${pc3.cyan(`bumped ${userPluginBump.bumped.length} user-installed @ampless/* plugin(s) to latest @beta:`)}`);
|
|
2419
|
+
for (const { name, from, to } of userPluginBump.bumped) {
|
|
2420
|
+
log3.info(` ${name}: ${from} \u2192 ${to}`);
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
for (const w of userPluginBump.warnings) {
|
|
2424
|
+
log3.warn(w);
|
|
2425
|
+
}
|
|
2259
2426
|
return {
|
|
2260
2427
|
added: replaceNew,
|
|
2261
2428
|
updated: replaceUpdate,
|
|
@@ -2265,7 +2432,9 @@ async function runUpgradeIn(destDir, sharedDir, opts = {}) {
|
|
|
2265
2432
|
themesPreserved: themeResult.preserved,
|
|
2266
2433
|
themesQuarantined: themeResult.quarantined,
|
|
2267
2434
|
packageJsonMerged: true,
|
|
2268
|
-
obsoleteRemoved: obsoleteFiles
|
|
2435
|
+
obsoleteRemoved: obsoleteFiles,
|
|
2436
|
+
editorExtensionsWired: bootstrapResult.pluginCount,
|
|
2437
|
+
userPluginsBumped: userPluginBump.bumped.length
|
|
2269
2438
|
};
|
|
2270
2439
|
}
|
|
2271
2440
|
async function runUpgrade(args) {
|
|
@@ -2361,8 +2530,8 @@ async function runCopyTheme(args) {
|
|
|
2361
2530
|
const source = args.copyThemeSource;
|
|
2362
2531
|
const target = args.copyThemeTarget;
|
|
2363
2532
|
if (!source || !target) {
|
|
2364
|
-
log4.error("Usage: npx create-ampless@
|
|
2365
|
-
log4.info("Example: npx create-ampless@
|
|
2533
|
+
log4.error("Usage: npx create-ampless@beta copy-theme <source> <target>");
|
|
2534
|
+
log4.info("Example: npx create-ampless@beta copy-theme blog my-blog");
|
|
2366
2535
|
process.exit(1);
|
|
2367
2536
|
}
|
|
2368
2537
|
try {
|
|
@@ -2397,7 +2566,7 @@ import {
|
|
|
2397
2566
|
cancel as cancel3
|
|
2398
2567
|
} from "@clack/prompts";
|
|
2399
2568
|
import pc5 from "picocolors";
|
|
2400
|
-
var SCAFFOLD_AMPLESS_VERSION = "1.0.0-
|
|
2569
|
+
var SCAFFOLD_AMPLESS_VERSION = "1.0.0-beta.0";
|
|
2401
2570
|
var TEXT_EXTENSIONS3 = /* @__PURE__ */ new Set([
|
|
2402
2571
|
".json",
|
|
2403
2572
|
".md",
|
|
@@ -2648,7 +2817,7 @@ async function runCreatePlugin(args) {
|
|
|
2648
2817
|
${pc5.cyan("pnpm install")}
|
|
2649
2818
|
${pc5.cyan("pnpm test")}
|
|
2650
2819
|
${pc5.cyan("pnpm build")}
|
|
2651
|
-
${pc5.cyan("pnpm publish --access public --tag
|
|
2820
|
+
${pc5.cyan("pnpm publish --access public --tag beta")}
|
|
2652
2821
|
|
|
2653
2822
|
Then add the published package to your ampless site:
|
|
2654
2823
|
${pc5.cyan(`pnpm add ${result.packageName ?? result.pluginName}`)}`
|
|
@@ -2671,7 +2840,7 @@ var SECRETS_DIR = "amplify/secrets";
|
|
|
2671
2840
|
var KEY_FILE = "encryption-key.ts";
|
|
2672
2841
|
var KEY_FILE_PATH = `${SECRETS_DIR}/${KEY_FILE}`;
|
|
2673
2842
|
function buildFileContent(keyB64) {
|
|
2674
|
-
return `// Generated by \`npx create-ampless setup-encryption-key\`.
|
|
2843
|
+
return `// Generated by \`npx create-ampless@beta setup-encryption-key\`.
|
|
2675
2844
|
// AES-256-GCM key (32 bytes, base64) for plugin secret storage.
|
|
2676
2845
|
// Rotate via the same CLI command. Existing ciphertext becomes unreadable on rotation.
|
|
2677
2846
|
|
|
@@ -2815,7 +2984,7 @@ async function runMount(args) {
|
|
|
2815
2984
|
if (problem) {
|
|
2816
2985
|
log7.error(problem);
|
|
2817
2986
|
log7.info(
|
|
2818
|
-
"Run `npx create-ampless@
|
|
2987
|
+
"Run `npx create-ampless@beta <name>` first to scaffold, or `cd` into a scaffolded project before passing --mount."
|
|
2819
2988
|
);
|
|
2820
2989
|
process.exit(1);
|
|
2821
2990
|
}
|
|
@@ -133,7 +133,7 @@ export default defineConfig({
|
|
|
133
133
|
このプロジェクトディレクトリ内で:
|
|
134
134
|
|
|
135
135
|
```bash
|
|
136
|
-
npx create-ampless@
|
|
136
|
+
npx create-ampless@beta --mount \
|
|
137
137
|
--github-owner <your-user-or-org> \
|
|
138
138
|
--aws-region <region> \
|
|
139
139
|
--create-iam-role # 初回のみ。次回以降は `--iam-service-role <arn>` で使い回し
|
|
@@ -153,7 +153,7 @@ CLI が以下を一気に実行します:
|
|
|
153
153
|
- `--skip-confirm` — 非対話モード(CI / 再実行向け)
|
|
154
154
|
- `--aws-profile <name>` — 複数 AWS profile がある場合に明示
|
|
155
155
|
|
|
156
|
-
全フラグは `npx create-ampless@
|
|
156
|
+
全フラグは `npx create-ampless@beta --help` を参照。
|
|
157
157
|
|
|
158
158
|
**このフロー固有の事前準備([トップの必要なもの](#必要なもの) に加えて):**
|
|
159
159
|
|
|
@@ -216,13 +216,13 @@ MCP のセットアップとトークン管理の詳細については [docs/arc
|
|
|
216
216
|
|
|
217
217
|
## ampless の更新
|
|
218
218
|
|
|
219
|
-
ampless は `
|
|
219
|
+
public beta 中の ampless は `beta` dist-tag でリリースしています。新機能を取り込むには:
|
|
220
220
|
|
|
221
221
|
```bash
|
|
222
222
|
npm run update-ampless
|
|
223
223
|
```
|
|
224
224
|
|
|
225
|
-
これは `npx create-ampless@
|
|
225
|
+
これは `npx create-ampless@beta upgrade` を実行し、以下を行います:
|
|
226
226
|
|
|
227
227
|
- `package.json` の `@ampless/*` / `ampless` 依存をバージョンアップ
|
|
228
228
|
- 共有テンプレートファイル(admin アプリの土台、amplify バックエンド、lib/、middleware、テーマ)を再同期 — `cms.config.ts`、`theme.*` 管理画面設定、投稿、テーマ manifest 値などのユーザーカスタマイズは保持されます
|
|
@@ -133,7 +133,7 @@ The shipped [`amplify.yml`](./amplify.yml) runs `npx ampx pipeline-deploy` (Ampl
|
|
|
133
133
|
From inside this project directory:
|
|
134
134
|
|
|
135
135
|
```bash
|
|
136
|
-
npx create-ampless@
|
|
136
|
+
npx create-ampless@beta --mount \
|
|
137
137
|
--github-owner <your-user-or-org> \
|
|
138
138
|
--aws-region <region> \
|
|
139
139
|
--create-iam-role # first time only; reuse `--iam-service-role <arn>` on later mounts
|
|
@@ -153,7 +153,7 @@ Useful extra flags:
|
|
|
153
153
|
- `--skip-confirm` — non-interactive (good for CI / re-runs)
|
|
154
154
|
- `--aws-profile <name>` — explicit AWS profile when you have multiple
|
|
155
155
|
|
|
156
|
-
See `npx create-ampless@
|
|
156
|
+
See `npx create-ampless@beta --help` for the full list.
|
|
157
157
|
|
|
158
158
|
**Prerequisites for this flow (in addition to the [top-level Requirements](#requirements)):**
|
|
159
159
|
|
|
@@ -216,13 +216,13 @@ See [docs/architecture/04-access-layer-mcp.md](./docs/architecture/04-access-lay
|
|
|
216
216
|
|
|
217
217
|
## Updating ampless
|
|
218
218
|
|
|
219
|
-
ampless releases on the `
|
|
219
|
+
ampless releases on the `beta` dist-tag during the public beta. To pick up new features:
|
|
220
220
|
|
|
221
221
|
```bash
|
|
222
222
|
npm run update-ampless
|
|
223
223
|
```
|
|
224
224
|
|
|
225
|
-
This runs `npx create-ampless@
|
|
225
|
+
This runs `npx create-ampless@beta upgrade`, which:
|
|
226
226
|
|
|
227
227
|
- Bumps the `@ampless/*` and `ampless` dependencies in `package.json`
|
|
228
228
|
- 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.
|
|
@@ -13,7 +13,7 @@ import { userAdmin } from './functions/user-admin/resource.js'
|
|
|
13
13
|
import { pluginSecretHandler } from './functions/plugin-secret-handler/resource.js'
|
|
14
14
|
import { customizeBackend } from './backend.custom.js'
|
|
15
15
|
// Plugin secret encryption key (Phase 6a v2.2).
|
|
16
|
-
// Generate with: npx create-ampless setup-encryption-key
|
|
16
|
+
// Generate with: npx create-ampless@beta setup-encryption-key
|
|
17
17
|
// The key lives in amplify/secrets/encryption-key.ts (gitignore it for
|
|
18
18
|
// public repos; safe to commit for private repos).
|
|
19
19
|
import { PLUGIN_SECRET_ENCRYPTION_KEY } from './secrets/encryption-key.js'
|
|
@@ -8,6 +8,13 @@ import { util } from '@aws-appsync/utils'
|
|
|
8
8
|
//
|
|
9
9
|
// Drafts are dropped in the response handler. If draft + published
|
|
10
10
|
// somehow share a slug we prefer the published row.
|
|
11
|
+
//
|
|
12
|
+
// Scheduled-publish: a `published` post is visible only when its
|
|
13
|
+
// publishedAt is absent (= immediate) OR <= now (= already live).
|
|
14
|
+
// When publishedAt is present and in the future the post is treated as
|
|
15
|
+
// "not yet live" and is hidden from the public site. This check is
|
|
16
|
+
// server-authoritative: the AppSync resolver enforces it on every read
|
|
17
|
+
// without relying on any client-side filtering.
|
|
11
18
|
export function request(ctx) {
|
|
12
19
|
const slug = ctx.args.slug
|
|
13
20
|
return {
|
|
@@ -25,6 +32,11 @@ export function request(ctx) {
|
|
|
25
32
|
export function response(ctx) {
|
|
26
33
|
if (ctx.error) util.error(ctx.error.message, ctx.error.type)
|
|
27
34
|
const items = ctx.result.items ?? []
|
|
28
|
-
const
|
|
35
|
+
const now = util.time.nowISO8601()
|
|
36
|
+
// Hide a published post only when publishedAt exists AND is in the future
|
|
37
|
+
// (scheduled). Missing publishedAt = immediate publish.
|
|
38
|
+
const published = items.find(
|
|
39
|
+
(i) => i.status === 'published' && (!i.publishedAt || i.publishedAt <= now)
|
|
40
|
+
)
|
|
29
41
|
return published ?? null
|
|
30
42
|
}
|
|
@@ -9,14 +9,26 @@ import { util } from '@aws-appsync/utils'
|
|
|
9
9
|
// the tag partition condition. Drafts never appear here because the
|
|
10
10
|
// admin client only writes PostTag rows for posts whose status is
|
|
11
11
|
// 'published'.
|
|
12
|
+
//
|
|
13
|
+
// Scheduled-publish: the SK is a composite of `${publishedAt}#${postId}`.
|
|
14
|
+
// ISO 8601 timestamps are fixed-width to millisecond precision, so lexical
|
|
15
|
+
// order == chronological order. We add an upper-bound SK condition
|
|
16
|
+
// `<= now + '#' + ''` to exclude PostTag rows whose publishedAt is in
|
|
17
|
+
// the future. U+FFFF sorts above every character that can appear in a real
|
|
18
|
+
// postId, so `now#` is strictly greater than `now#<any-postId>`:
|
|
19
|
+
// every postId suffix sharing the current-millisecond ISO prefix is
|
|
20
|
+
// included, while any SK whose timestamp prefix is later is excluded.
|
|
21
|
+
// (Written as an escape so this source file stays ASCII.)
|
|
12
22
|
export function request(ctx) {
|
|
13
23
|
const { tag, limit, nextToken } = ctx.args
|
|
24
|
+
const now = util.time.nowISO8601()
|
|
25
|
+
const upper = now + '#'
|
|
14
26
|
return {
|
|
15
27
|
operation: 'Query',
|
|
16
28
|
query: {
|
|
17
|
-
expression: '#tag = :tag',
|
|
18
|
-
expressionNames: { '#tag': 'tag' },
|
|
19
|
-
expressionValues: util.dynamodb.toMapValues({ ':tag': tag }),
|
|
29
|
+
expression: '#tag = :tag AND #pap <= :upper',
|
|
30
|
+
expressionNames: { '#tag': 'tag', '#pap': 'publishedAtPostId' },
|
|
31
|
+
expressionValues: util.dynamodb.toMapValues({ ':tag': tag, ':upper': upper }),
|
|
20
32
|
},
|
|
21
33
|
scanIndexForward: false, // newest first (SK descends)
|
|
22
34
|
limit: limit ?? 20,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { util } from '@aws-appsync/utils'
|
|
1
|
+
import { util, runtime } from '@aws-appsync/utils'
|
|
2
2
|
|
|
3
3
|
// AppSync JS resolver: list published posts, newest first.
|
|
4
4
|
//
|
|
@@ -8,29 +8,40 @@ import { util } from '@aws-appsync/utils'
|
|
|
8
8
|
// so a single Query reads only the `published` partition. Drafts never
|
|
9
9
|
// appear because the PK condition pins status='published'.
|
|
10
10
|
//
|
|
11
|
+
// Scheduled-publish: the upper bound of the SK range is always clamped
|
|
12
|
+
// to `now`. This ensures future-dated published posts (scheduled but not
|
|
13
|
+
// yet live) never appear in public listings. The upper bound is the lesser
|
|
14
|
+
// of `now` and any caller-supplied `to` argument.
|
|
15
|
+
//
|
|
16
|
+
// Reverse-range guard: if `from` is supplied and ends up > upper (e.g.
|
|
17
|
+
// the caller asks for a range entirely in the future), we return an empty
|
|
18
|
+
// result early via `runtime.earlyReturn` instead of issuing an inverted
|
|
19
|
+
// BETWEEN to DynamoDB (which DynamoDB rejects with a ValidationException).
|
|
20
|
+
//
|
|
11
21
|
// Date-range filtering (`from`, `to`) is pushed into the SK condition,
|
|
12
22
|
// so DynamoDB only reads the matching range. `nextToken` paginates
|
|
13
23
|
// without re-issuing a fresh query.
|
|
14
24
|
export function request(ctx) {
|
|
15
25
|
const { from, to, limit, nextToken } = ctx.args
|
|
26
|
+
const now = util.time.nowISO8601()
|
|
27
|
+
// Always clamp the upper bound to now so future-dated posts are excluded.
|
|
28
|
+
const upper = to && to < now ? to : now
|
|
29
|
+
|
|
30
|
+
// If `from` is later than `upper` the range is empty — skip the query
|
|
31
|
+
// rather than letting DynamoDB see an inverted BETWEEN / >= condition.
|
|
32
|
+
if (from && from > upper) {
|
|
33
|
+
return runtime.earlyReturn({ items: [], nextToken: null })
|
|
34
|
+
}
|
|
16
35
|
|
|
17
36
|
let keyExpression = '#status = :status'
|
|
18
|
-
const expressionNames = { '#status': 'status' }
|
|
19
|
-
const expressionValueMap = { ':status': 'published' }
|
|
37
|
+
const expressionNames = { '#status': 'status', '#publishedAt': 'publishedAt' }
|
|
38
|
+
const expressionValueMap = { ':status': 'published', ':upper': upper }
|
|
20
39
|
|
|
21
|
-
if (from
|
|
22
|
-
keyExpression += ' AND #publishedAt BETWEEN :from AND :
|
|
23
|
-
expressionNames['#publishedAt'] = 'publishedAt'
|
|
24
|
-
expressionValueMap[':from'] = from
|
|
25
|
-
expressionValueMap[':to'] = to
|
|
26
|
-
} else if (from) {
|
|
27
|
-
keyExpression += ' AND #publishedAt >= :from'
|
|
28
|
-
expressionNames['#publishedAt'] = 'publishedAt'
|
|
40
|
+
if (from) {
|
|
41
|
+
keyExpression += ' AND #publishedAt BETWEEN :from AND :upper'
|
|
29
42
|
expressionValueMap[':from'] = from
|
|
30
|
-
} else
|
|
31
|
-
keyExpression += ' AND #publishedAt <= :
|
|
32
|
-
expressionNames['#publishedAt'] = 'publishedAt'
|
|
33
|
-
expressionValueMap[':to'] = to
|
|
43
|
+
} else {
|
|
44
|
+
keyExpression += ' AND #publishedAt <= :upper'
|
|
34
45
|
}
|
|
35
46
|
|
|
36
47
|
return {
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
// Re-exported from @ampless/backend so the package can ship Lambda
|
|
2
2
|
// handler updates via `npm update`. Amplify's esbuild follows this
|
|
3
3
|
// import and bundles the real handler into the Lambda artifact.
|
|
4
|
-
|
|
4
|
+
//
|
|
5
|
+
// The dispatcher now also captures per-post revision history. The
|
|
6
|
+
// retention window comes from the user-side `cms.config` — the package
|
|
7
|
+
// handler never imports `cms.config` directly (it can't know the project
|
|
8
|
+
// layout), so this thin shell wires the value in via the factory.
|
|
9
|
+
import { createDispatcherHandler } from '@ampless/backend/events/dispatcher'
|
|
10
|
+
import config from '../../../cms.config'
|
|
11
|
+
|
|
12
|
+
export const handler = createDispatcherHandler({
|
|
13
|
+
historyRetentionDays: config.history?.retentionDays ?? 0,
|
|
14
|
+
})
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// Generated by `npx create-ampless setup-encryption-key`.
|
|
1
|
+
// Generated by `npx create-ampless@beta setup-encryption-key`.
|
|
2
2
|
// AES-256-GCM key (32 bytes, base64) for plugin secret storage.
|
|
3
3
|
// Rotate via the same CLI command. Existing ciphertext becomes unreadable on rotation.
|
|
4
4
|
//
|
|
5
5
|
// IMPORTANT: This is a placeholder. Replace the value below with a real key
|
|
6
|
-
// by running: npx create-ampless setup-encryption-key
|
|
6
|
+
// by running: npx create-ampless@beta setup-encryption-key
|
|
7
7
|
// (from your project root, after scaffolding).
|
|
8
8
|
|
|
9
9
|
export const PLUGIN_SECRET_ENCRYPTION_KEY = ''
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
// Placeholder for fresh scaffolds before `update-ampless` runs.
|
|
4
|
+
// `npm run update-ampless` rewrites this file with auto-wired plugin
|
|
5
|
+
// editor extensions based on the project's installed `@ampless/plugin-*`
|
|
6
|
+
// packages.
|
|
7
|
+
|
|
8
|
+
import { installAdminEditorExtensions } from '@ampless/admin/editor'
|
|
9
|
+
|
|
10
|
+
export function EditorBootstrap({ children }: { children: React.ReactNode }) {
|
|
11
|
+
installAdminEditorExtensions([])
|
|
12
|
+
return <>{children}</>
|
|
13
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { admin } from '@/lib/admin'
|
|
2
2
|
import { createAdminLayout } from '@ampless/admin/pages'
|
|
3
|
+
import { EditorBootstrap } from './_editor-bootstrap'
|
|
3
4
|
|
|
4
|
-
export default createAdminLayout(admin)
|
|
5
|
+
export default createAdminLayout(admin, { editorBootstrap: EditorBootstrap })
|