@mezzanine-stack/create-mezzanine 0.1.0
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.md +27 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +573 -0
- package/dist/cli.js.map +1 -0
- package/package.json +21 -0
- package/src/cli.ts +678 -0
- package/tsconfig.json +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# create-mezzanine
|
|
2
|
+
|
|
3
|
+
Mezzanine Platform のプロジェクト生成・運用補助 CLI です。
|
|
4
|
+
|
|
5
|
+
## 提供コマンド
|
|
6
|
+
|
|
7
|
+
- `create-starter`
|
|
8
|
+
- `contact-env`
|
|
9
|
+
- `manifest-init`
|
|
10
|
+
- `tenant-add`
|
|
11
|
+
- `tenant-list`
|
|
12
|
+
|
|
13
|
+
## 例
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx create-mezzanine create-starter --output ../acme-site --hosting cloudflare
|
|
17
|
+
npx create-mezzanine contact-env --output ./contact-vars.example.toml
|
|
18
|
+
npx create-mezzanine manifest-init --tenant acme --output ./customer-manifest.yaml
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 用途
|
|
22
|
+
|
|
23
|
+
- `create-starter`: `apps/site-starter-astro` と必要最小限 package をコピーして standalone リポジトリを生成
|
|
24
|
+
- `contact-env`: contact service 向け `wrangler.toml [vars]` テンプレートを出力
|
|
25
|
+
- `manifest-init`: managed ops 用 `customer-manifest.yaml` テンプレートを出力
|
|
26
|
+
- `tenant-add` / `tenant-list`: マルチテナント運用向け KV 設定補助
|
|
27
|
+
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* create-mezzanine — Mezzanine Platform プロジェクト生成 CLI
|
|
4
|
+
*
|
|
5
|
+
* Phase 5: contact service の導入補助サブコマンドを提供します。
|
|
6
|
+
* Phase 6: managed ops 用の manifest-init サブコマンドを追加しました。
|
|
7
|
+
* Phase 6 (multi-tenant): KV テナント管理の tenant-add / tenant-list を追加しました。
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;GAMG"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* create-mezzanine — Mezzanine Platform プロジェクト生成 CLI
|
|
4
|
+
*
|
|
5
|
+
* Phase 5: contact service の導入補助サブコマンドを提供します。
|
|
6
|
+
* Phase 6: managed ops 用の manifest-init サブコマンドを追加しました。
|
|
7
|
+
* Phase 6 (multi-tenant): KV テナント管理の tenant-add / tenant-list を追加しました。
|
|
8
|
+
*/
|
|
9
|
+
const [, , command, ...args] = process.argv;
|
|
10
|
+
async function main() {
|
|
11
|
+
switch (command) {
|
|
12
|
+
case "create-starter":
|
|
13
|
+
await createStarterProject(args);
|
|
14
|
+
break;
|
|
15
|
+
case "contact-env":
|
|
16
|
+
await printContactEnvTemplate(args);
|
|
17
|
+
break;
|
|
18
|
+
case "manifest-init":
|
|
19
|
+
await printManifestTemplate(args);
|
|
20
|
+
break;
|
|
21
|
+
case "tenant-add":
|
|
22
|
+
await printTenantConfig(args);
|
|
23
|
+
break;
|
|
24
|
+
case "tenant-list":
|
|
25
|
+
await listTenants(args);
|
|
26
|
+
break;
|
|
27
|
+
case "help":
|
|
28
|
+
case "--help":
|
|
29
|
+
case "-h":
|
|
30
|
+
case undefined:
|
|
31
|
+
printHelp();
|
|
32
|
+
break;
|
|
33
|
+
default:
|
|
34
|
+
console.error(`Unknown command: ${command}`);
|
|
35
|
+
printHelp();
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
main().catch((err) => {
|
|
40
|
+
console.error(err);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
});
|
|
43
|
+
function printHelp() {
|
|
44
|
+
console.log(`
|
|
45
|
+
create-mezzanine — Mezzanine Platform CLI
|
|
46
|
+
|
|
47
|
+
Usage:
|
|
48
|
+
create-mezzanine <command> [options]
|
|
49
|
+
|
|
50
|
+
Commands:
|
|
51
|
+
create-starter [--output <dir>] [--hosting <cloudflare|vercel|netlify|static>] [--name <project-name>] [--smoke-test]
|
|
52
|
+
starter + 必要最小限 package をコピーして、単体で動く顧客サイト repo を生成する
|
|
53
|
+
--smoke-test 指定時は生成直後に pnpm install / pnpm build を実行して検証する
|
|
54
|
+
|
|
55
|
+
contact-env [--output <path>]
|
|
56
|
+
contact-service-cloudflare 用の wrangler.toml [vars] テンプレートを出力する
|
|
57
|
+
|
|
58
|
+
manifest-init [--tenant <id>] [--output <path>]
|
|
59
|
+
managed ops 用の customer manifest テンプレートを出力する
|
|
60
|
+
|
|
61
|
+
tenant-add --origin <url> --to <email> --from <email> --success <url>
|
|
62
|
+
[--tenant <id>] [--subject <prefix>] [--failure <url>]
|
|
63
|
+
[--output <path>]
|
|
64
|
+
KV (TENANT_CONFIG_KV) に投入する顧客設定 JSON を出力する
|
|
65
|
+
Auth KV (AUTH_ALLOWED_KV) への origin 登録コマンドも案内する
|
|
66
|
+
|
|
67
|
+
tenant-list --namespace-id <id>
|
|
68
|
+
wrangler CLI 経由で TENANT_CONFIG_KV のテナント一覧を表示する
|
|
69
|
+
|
|
70
|
+
help
|
|
71
|
+
このヘルプを表示する
|
|
72
|
+
|
|
73
|
+
Examples:
|
|
74
|
+
npx create-mezzanine create-starter --output ../acme-site --hosting cloudflare
|
|
75
|
+
npx create-mezzanine create-starter --output ../acme-site --hosting static --name acme-site
|
|
76
|
+
npx create-mezzanine create-starter --output ../acme-site --hosting cloudflare --smoke-test
|
|
77
|
+
npx create-mezzanine contact-env
|
|
78
|
+
npx create-mezzanine manifest-init --tenant my-client --output customers/my-client/manifest.yaml
|
|
79
|
+
npx create-mezzanine tenant-add \\
|
|
80
|
+
--origin https://acme.com \\
|
|
81
|
+
--to owner@acme.com \\
|
|
82
|
+
--from noreply@your-domain.com \\
|
|
83
|
+
--success https://acme.com/contact/thanks \\
|
|
84
|
+
--output tenant-acme.json
|
|
85
|
+
npx create-mezzanine tenant-list --namespace-id xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
86
|
+
`);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* create-starter:
|
|
90
|
+
* `apps/site-starter-astro` と依存 package の最小セットをコピーし、
|
|
91
|
+
* 顧客向けの standalone リポジトリを生成する。
|
|
92
|
+
*/
|
|
93
|
+
async function createStarterProject(args) {
|
|
94
|
+
const get = (flag) => {
|
|
95
|
+
const i = args.indexOf(flag);
|
|
96
|
+
return i !== -1 ? args[i + 1] : undefined;
|
|
97
|
+
};
|
|
98
|
+
const has = (flag) => args.includes(flag);
|
|
99
|
+
const outputArg = get("--output");
|
|
100
|
+
if (!outputArg) {
|
|
101
|
+
console.error("Error: --output は必須です。");
|
|
102
|
+
console.error(" 例: npx create-mezzanine create-starter --output ../acme-site --hosting cloudflare");
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
const hosting = (get("--hosting") ?? "cloudflare").toLowerCase();
|
|
106
|
+
if (!["cloudflare", "vercel", "netlify", "static"].includes(hosting)) {
|
|
107
|
+
console.error(`Error: --hosting は cloudflare|vercel|netlify|static のいずれかを指定してください。受け取った値: ${hosting}`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
const projectName = get("--name") ?? "mezzanine-customer-site";
|
|
111
|
+
const smokeTest = has("--smoke-test");
|
|
112
|
+
const fs = await import("node:fs");
|
|
113
|
+
const path = await import("node:path");
|
|
114
|
+
const { fileURLToPath } = await import("node:url");
|
|
115
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
116
|
+
const repoRoot = path.resolve(path.dirname(thisFile), "../../..");
|
|
117
|
+
const outDir = path.resolve(process.cwd(), outputArg);
|
|
118
|
+
if (fs.existsSync(outDir) && fs.readdirSync(outDir).length > 0) {
|
|
119
|
+
console.error(`Error: 出力先ディレクトリが空ではありません: ${outDir}`);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
123
|
+
const appSrc = path.join(repoRoot, "apps/site-starter-astro");
|
|
124
|
+
const packageNames = [
|
|
125
|
+
"tokens",
|
|
126
|
+
"css",
|
|
127
|
+
"ui",
|
|
128
|
+
"content-schema",
|
|
129
|
+
"astro-renderer",
|
|
130
|
+
"cms-core",
|
|
131
|
+
"git-provider-github",
|
|
132
|
+
];
|
|
133
|
+
const copyFiltered = (src, dest) => {
|
|
134
|
+
fs.cpSync(src, dest, {
|
|
135
|
+
recursive: true,
|
|
136
|
+
filter: (entry) => {
|
|
137
|
+
const normalized = entry.replace(/\\/g, "/");
|
|
138
|
+
if (normalized.endsWith("/node_modules") ||
|
|
139
|
+
normalized.includes("/node_modules/") ||
|
|
140
|
+
normalized.endsWith("/dist") ||
|
|
141
|
+
normalized.includes("/dist/") ||
|
|
142
|
+
normalized.endsWith("/.astro") ||
|
|
143
|
+
normalized.includes("/.astro/") ||
|
|
144
|
+
normalized.endsWith("/.git") ||
|
|
145
|
+
normalized.includes("/.git/")) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
// apps/site-starter-astro
|
|
153
|
+
const appDest = path.join(outDir, "apps/site-starter-astro");
|
|
154
|
+
fs.mkdirSync(path.dirname(appDest), { recursive: true });
|
|
155
|
+
copyFiltered(appSrc, appDest);
|
|
156
|
+
// 必要 package のみコピー
|
|
157
|
+
for (const name of packageNames) {
|
|
158
|
+
const src = path.join(repoRoot, "packages", name);
|
|
159
|
+
const dest = path.join(outDir, "packages", name);
|
|
160
|
+
copyFiltered(src, dest);
|
|
161
|
+
}
|
|
162
|
+
// ルート設定ファイルを生成
|
|
163
|
+
fs.writeFileSync(path.join(outDir, "package.json"), JSON.stringify({
|
|
164
|
+
name: projectName,
|
|
165
|
+
private: true,
|
|
166
|
+
version: "0.1.0",
|
|
167
|
+
packageManager: "pnpm@10.28.2",
|
|
168
|
+
scripts: {
|
|
169
|
+
dev: "pnpm --filter @mezzanine/site-starter-astro dev",
|
|
170
|
+
build: "pnpm --filter @mezzanine/site-starter-astro build",
|
|
171
|
+
preview: "pnpm --filter @mezzanine/site-starter-astro preview",
|
|
172
|
+
typecheck: "pnpm -r typecheck",
|
|
173
|
+
},
|
|
174
|
+
}, null, 2) + "\n", "utf-8");
|
|
175
|
+
fs.writeFileSync(path.join(outDir, "pnpm-workspace.yaml"), 'packages:\n - "apps/*"\n - "packages/*"\n', "utf-8");
|
|
176
|
+
const tsconfigBaseSrc = path.join(repoRoot, "tsconfig.base.json");
|
|
177
|
+
if (fs.existsSync(tsconfigBaseSrc)) {
|
|
178
|
+
fs.copyFileSync(tsconfigBaseSrc, path.join(outDir, "tsconfig.base.json"));
|
|
179
|
+
}
|
|
180
|
+
fs.writeFileSync(path.join(outDir, ".gitignore"), "node_modules\n.pnpm-store\n.astro\ndist\n.DS_Store\n", "utf-8");
|
|
181
|
+
writeHostingFiles(fs, path, outDir, hosting);
|
|
182
|
+
writeStarterReadme(fs, path, outDir, hosting);
|
|
183
|
+
if (smokeTest) {
|
|
184
|
+
await runStarterSmokeTest(outDir);
|
|
185
|
+
}
|
|
186
|
+
console.log(`Created starter project: ${outDir}`);
|
|
187
|
+
console.log(`Hosting preset: ${hosting}`);
|
|
188
|
+
console.log("");
|
|
189
|
+
console.log("Next steps:");
|
|
190
|
+
console.log(` cd ${outDir}`);
|
|
191
|
+
console.log(" pnpm install");
|
|
192
|
+
console.log(" pnpm dev");
|
|
193
|
+
}
|
|
194
|
+
async function runStarterSmokeTest(outDir) {
|
|
195
|
+
const { spawnSync } = await import("node:child_process");
|
|
196
|
+
console.log("Running smoke test...");
|
|
197
|
+
console.log(" 1) pnpm install");
|
|
198
|
+
const install = spawnSync("pnpm", ["install"], {
|
|
199
|
+
cwd: outDir,
|
|
200
|
+
stdio: "inherit",
|
|
201
|
+
env: process.env,
|
|
202
|
+
});
|
|
203
|
+
if (install.error) {
|
|
204
|
+
console.error("Smoke test failed: pnpm install を実行できませんでした。");
|
|
205
|
+
console.error(install.error.message);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
if (install.status !== 0) {
|
|
209
|
+
console.error(`Smoke test failed: pnpm install が失敗しました (exit=${install.status ?? 1})`);
|
|
210
|
+
process.exit(install.status ?? 1);
|
|
211
|
+
}
|
|
212
|
+
console.log(" 2) pnpm build");
|
|
213
|
+
const build = spawnSync("pnpm", ["build"], {
|
|
214
|
+
cwd: outDir,
|
|
215
|
+
stdio: "inherit",
|
|
216
|
+
env: process.env,
|
|
217
|
+
});
|
|
218
|
+
if (build.error) {
|
|
219
|
+
console.error("Smoke test failed: pnpm build を実行できませんでした。");
|
|
220
|
+
console.error(build.error.message);
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
if (build.status !== 0) {
|
|
224
|
+
console.error(`Smoke test failed: pnpm build が失敗しました (exit=${build.status ?? 1})`);
|
|
225
|
+
process.exit(build.status ?? 1);
|
|
226
|
+
}
|
|
227
|
+
console.log("Smoke test passed.");
|
|
228
|
+
}
|
|
229
|
+
function writeHostingFiles(fs, path, outDir, hosting) {
|
|
230
|
+
const appDir = path.join(outDir, "apps/site-starter-astro");
|
|
231
|
+
if (hosting === "vercel") {
|
|
232
|
+
fs.writeFileSync(path.join(appDir, "vercel.json"), JSON.stringify({
|
|
233
|
+
framework: "astro",
|
|
234
|
+
buildCommand: "pnpm --filter @mezzanine/site-starter-astro build",
|
|
235
|
+
outputDirectory: "dist",
|
|
236
|
+
}, null, 2) + "\n", "utf-8");
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (hosting === "netlify") {
|
|
240
|
+
fs.writeFileSync(path.join(appDir, "netlify.toml"), `[build]
|
|
241
|
+
base = "apps/site-starter-astro"
|
|
242
|
+
publish = "dist"
|
|
243
|
+
command = "pnpm build"
|
|
244
|
+
`, "utf-8");
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (hosting === "cloudflare") {
|
|
248
|
+
const publicDir = path.join(appDir, "public");
|
|
249
|
+
fs.mkdirSync(publicDir, { recursive: true });
|
|
250
|
+
fs.writeFileSync(path.join(publicDir, "_headers"), `/*
|
|
251
|
+
X-Frame-Options: SAMEORIGIN
|
|
252
|
+
X-Content-Type-Options: nosniff
|
|
253
|
+
`, "utf-8");
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (hosting === "static") {
|
|
257
|
+
fs.writeFileSync(path.join(outDir, "HOSTING.md"), `# Hosting Guide (Static)
|
|
258
|
+
|
|
259
|
+
## Build
|
|
260
|
+
|
|
261
|
+
\`\`\`bash
|
|
262
|
+
pnpm install
|
|
263
|
+
pnpm build
|
|
264
|
+
\`\`\`
|
|
265
|
+
|
|
266
|
+
Static output is generated under:
|
|
267
|
+
|
|
268
|
+
\`apps/site-starter-astro/dist\`
|
|
269
|
+
|
|
270
|
+
Upload this directory to your static hosting platform (S3 + CDN, Nginx, etc).
|
|
271
|
+
`, "utf-8");
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function writeStarterReadme(fs, path, outDir, hosting) {
|
|
275
|
+
fs.writeFileSync(path.join(outDir, "README.md"), `# Mezzanine Starter (Generated)
|
|
276
|
+
|
|
277
|
+
このプロジェクトは \`create-mezzanine create-starter\` で生成されました。
|
|
278
|
+
|
|
279
|
+
## Hosting preset
|
|
280
|
+
|
|
281
|
+
\`${hosting}\`
|
|
282
|
+
|
|
283
|
+
## Quick start
|
|
284
|
+
|
|
285
|
+
\`\`\`bash
|
|
286
|
+
pnpm install
|
|
287
|
+
pnpm dev
|
|
288
|
+
\`\`\`
|
|
289
|
+
|
|
290
|
+
## CMS
|
|
291
|
+
|
|
292
|
+
- サイトと同一ドメインで \`/admin\` を提供します
|
|
293
|
+
- 認証は \`PUBLIC_AUTH_BROKER_URL\` を設定して利用します
|
|
294
|
+
|
|
295
|
+
## Commands
|
|
296
|
+
|
|
297
|
+
- \`pnpm dev\`
|
|
298
|
+
- \`pnpm build\`
|
|
299
|
+
- \`pnpm preview\`
|
|
300
|
+
- \`pnpm typecheck\`
|
|
301
|
+
`, "utf-8");
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* tenant-add: KV に投入する顧客設定 JSON を生成して出力する。
|
|
305
|
+
*
|
|
306
|
+
* 使い方:
|
|
307
|
+
* npx create-mezzanine tenant-add \
|
|
308
|
+
* --origin https://acme.com \
|
|
309
|
+
* --to owner@acme.com \
|
|
310
|
+
* --from noreply@your-domain.com \
|
|
311
|
+
* --success https://acme.com/contact/thanks \
|
|
312
|
+
* [--tenant acme] [--subject "[acme お問い合わせ]"] [--failure https://acme.com/contact?error=1] \
|
|
313
|
+
* [--output tenant-acme.json]
|
|
314
|
+
*
|
|
315
|
+
* 出力後は以下のコマンドで KV に投入する:
|
|
316
|
+
* wrangler kv key put --binding=TENANT_CONFIG_KV "tenant:<origin>" "$(cat <output>)"
|
|
317
|
+
* wrangler kv key put --binding=AUTH_ALLOWED_KV "allowed-origin:<origin>" "1"
|
|
318
|
+
*/
|
|
319
|
+
async function printTenantConfig(args) {
|
|
320
|
+
const get = (flag) => {
|
|
321
|
+
const i = args.indexOf(flag);
|
|
322
|
+
return i !== -1 ? args[i + 1] : undefined;
|
|
323
|
+
};
|
|
324
|
+
const origin = get("--origin");
|
|
325
|
+
const toEmail = get("--to");
|
|
326
|
+
const fromEmail = get("--from");
|
|
327
|
+
const successUrl = get("--success");
|
|
328
|
+
if (!origin || !toEmail || !fromEmail || !successUrl) {
|
|
329
|
+
console.error("Error: --origin, --to, --from, --success はすべて必須です。");
|
|
330
|
+
console.error(" 例: npx create-mezzanine tenant-add --origin https://acme.com --to owner@acme.com --from noreply@your-domain.com --success https://acme.com/contact/thanks");
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
const tenantId = get("--tenant") ?? origin.replace(/https?:\/\//, "").replace(/[^a-z0-9-]/gi, "-");
|
|
334
|
+
const subjectPrefix = get("--subject");
|
|
335
|
+
const failureUrl = get("--failure");
|
|
336
|
+
const outputPath = get("--output");
|
|
337
|
+
const config = {
|
|
338
|
+
tenantId,
|
|
339
|
+
toEmails: [toEmail],
|
|
340
|
+
fromEmail,
|
|
341
|
+
...(subjectPrefix ? { subjectPrefix } : {}),
|
|
342
|
+
successRedirectUrl: successUrl,
|
|
343
|
+
...(failureUrl ? { failureRedirectUrl: failureUrl } : {}),
|
|
344
|
+
uniformResponse: false,
|
|
345
|
+
enableHoneypot: true,
|
|
346
|
+
captchaRequired: true,
|
|
347
|
+
ratePerMinute: 5,
|
|
348
|
+
ratePer10Minutes: 20,
|
|
349
|
+
};
|
|
350
|
+
const json = JSON.stringify(config, null, 2);
|
|
351
|
+
if (outputPath) {
|
|
352
|
+
const fs = await import("node:fs");
|
|
353
|
+
fs.writeFileSync(outputPath, json, "utf-8");
|
|
354
|
+
console.log(`Written: ${outputPath}`);
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
process.stdout.write(json + "\n");
|
|
358
|
+
}
|
|
359
|
+
const kvKey = `tenant:${origin}`;
|
|
360
|
+
const authKey = `allowed-origin:${origin}`;
|
|
361
|
+
const src = outputPath ? `"$(cat ${outputPath})"` : `'${json.replace(/'/g, "'\\''")}'`;
|
|
362
|
+
console.error(`
|
|
363
|
+
Next steps — TENANT_CONFIG_KV に登録する:
|
|
364
|
+
wrangler kv key put --binding=TENANT_CONFIG_KV "${kvKey}" ${src}
|
|
365
|
+
|
|
366
|
+
Next steps — AUTH_ALLOWED_KV に登録する:
|
|
367
|
+
wrangler kv key put --binding=AUTH_ALLOWED_KV "${authKey}" "1"
|
|
368
|
+
`);
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* tenant-list: wrangler CLI 経由で TENANT_CONFIG_KV のテナント一覧を表示する。
|
|
372
|
+
*
|
|
373
|
+
* 使い方:
|
|
374
|
+
* npx create-mezzanine tenant-list --namespace-id <KV_NAMESPACE_ID>
|
|
375
|
+
*
|
|
376
|
+
* wrangler CLI が PATH に存在する必要があります。
|
|
377
|
+
*/
|
|
378
|
+
async function listTenants(args) {
|
|
379
|
+
const nsIndex = args.indexOf("--namespace-id");
|
|
380
|
+
const namespaceId = nsIndex !== -1 ? args[nsIndex + 1] : undefined;
|
|
381
|
+
if (!namespaceId) {
|
|
382
|
+
console.error("Error: --namespace-id は必須です。");
|
|
383
|
+
console.error(" 例: npx create-mezzanine tenant-list --namespace-id xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
|
|
384
|
+
console.error("\n namespace ID は以下のコマンドで確認できます:");
|
|
385
|
+
console.error(" wrangler kv namespace list");
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
const { spawnSync } = await import("node:child_process");
|
|
389
|
+
const result = spawnSync("wrangler", ["kv", "key", "list", "--namespace-id", namespaceId, "--prefix", "tenant:"], { encoding: "utf-8" });
|
|
390
|
+
if (result.error) {
|
|
391
|
+
console.error("wrangler の実行に失敗しました。wrangler がインストールされているか確認してください。");
|
|
392
|
+
console.error(result.error.message);
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
if (result.status !== 0) {
|
|
396
|
+
console.error(result.stderr ?? "wrangler がエラーを返しました。");
|
|
397
|
+
process.exit(result.status ?? 1);
|
|
398
|
+
}
|
|
399
|
+
let keys = [];
|
|
400
|
+
try {
|
|
401
|
+
keys = JSON.parse(result.stdout);
|
|
402
|
+
}
|
|
403
|
+
catch {
|
|
404
|
+
process.stdout.write(result.stdout);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (keys.length === 0) {
|
|
408
|
+
console.log("登録済みのテナントはありません。");
|
|
409
|
+
console.log(" npx create-mezzanine tenant-add でテナントを追加してください。");
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
console.log(`登録済みテナント (${keys.length} 件):`);
|
|
413
|
+
for (const key of keys) {
|
|
414
|
+
const origin = key.name.replace(/^tenant:/, "");
|
|
415
|
+
console.log(` ${origin}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
async function printManifestTemplate(args) {
|
|
419
|
+
const tenantIndex = args.indexOf("--tenant");
|
|
420
|
+
const tenantId = tenantIndex !== -1 ? args[tenantIndex + 1] : "REPLACE_WITH_TENANT_ID";
|
|
421
|
+
const outputIndex = args.indexOf("--output");
|
|
422
|
+
const outputPath = outputIndex !== -1 ? args[outputIndex + 1] : undefined;
|
|
423
|
+
const template = `# Mezzanine Managed Ops — Customer Manifest
|
|
424
|
+
# このファイルは private 運用 repo の customers/<tenant-id>/manifest.yaml として管理します。
|
|
425
|
+
# 顧客固有のシークレット実体はここに置かず、secretRefs にキー名のみ記録してください。
|
|
426
|
+
#
|
|
427
|
+
# 生成コマンド: npx create-mezzanine manifest-init --tenant ${tenantId}
|
|
428
|
+
# 参照: docs/adr/0004-phase6-managed-ops-design.md
|
|
429
|
+
|
|
430
|
+
# --- 識別情報 ---
|
|
431
|
+
tenantId: "${tenantId}"
|
|
432
|
+
schemaVersion: "1"
|
|
433
|
+
|
|
434
|
+
# --- 顧客サイト ---
|
|
435
|
+
site:
|
|
436
|
+
repository: "https://github.com/YOUR_ORG/${tenantId}-site"
|
|
437
|
+
defaultBranch: "main"
|
|
438
|
+
hosting: "cloudflare-pages"
|
|
439
|
+
domains:
|
|
440
|
+
production: "https://your-site.com"
|
|
441
|
+
staging: "https://staging.your-site.com"
|
|
442
|
+
|
|
443
|
+
# --- 利用モジュール ---
|
|
444
|
+
modules:
|
|
445
|
+
cmsAdmin: true
|
|
446
|
+
authBroker: true
|
|
447
|
+
contactService: true
|
|
448
|
+
|
|
449
|
+
# --- Platform バージョン ---
|
|
450
|
+
platform:
|
|
451
|
+
# @mezzanine/* packages の使用バージョン (semver)
|
|
452
|
+
packageVersion: "0.1.0"
|
|
453
|
+
# site-starter-astro の起点バージョン (git tag または commit SHA)
|
|
454
|
+
starterBaseline: "v0.1.0"
|
|
455
|
+
# このファイルの manifest schema バージョン
|
|
456
|
+
manifestSchemaVersion: "1"
|
|
457
|
+
|
|
458
|
+
# --- リリースポリシー ---
|
|
459
|
+
# stable : smoke test 通過後、順次適用候補にする
|
|
460
|
+
# pilot : 先行検証環境で確認してから stable 顧客へ展開する
|
|
461
|
+
# pinned : 明示 opt-in なしに更新しない
|
|
462
|
+
releasePolicy: "stable"
|
|
463
|
+
|
|
464
|
+
# --- シークレット参照 (実体は secret manager に登録し、ここにはキー名のみ記録) ---
|
|
465
|
+
secretRefs:
|
|
466
|
+
# Cloudflare Pages / Workers の secret 名
|
|
467
|
+
resendApiKey: "RESEND_API_KEY"
|
|
468
|
+
turnstileSecretKey: "TURNSTILE_SECRET_KEY"
|
|
469
|
+
# GitHub OAuth broker の secret 名
|
|
470
|
+
githubClientId: "GITHUB_CLIENT_ID"
|
|
471
|
+
githubClientSecret: "GITHUB_CLIENT_SECRET"
|
|
472
|
+
|
|
473
|
+
# --- 監視・通知 ---
|
|
474
|
+
observability:
|
|
475
|
+
uptimeChecks:
|
|
476
|
+
- url: "https://your-site.com"
|
|
477
|
+
name: "site-top"
|
|
478
|
+
- url: "https://your-site.com/admin"
|
|
479
|
+
name: "cms-admin"
|
|
480
|
+
- url: "https://auth.your-domain.com/api/auth"
|
|
481
|
+
name: "auth-broker"
|
|
482
|
+
- url: "https://contact.your-domain.com"
|
|
483
|
+
name: "contact-service"
|
|
484
|
+
notifyTo: "ops@your-domain.com"
|
|
485
|
+
alertThresholdMs: 3000
|
|
486
|
+
|
|
487
|
+
# --- 運用メモ ---
|
|
488
|
+
notes: |
|
|
489
|
+
初回導入日: YYYY-MM-DD
|
|
490
|
+
担当者: YOUR_NAME
|
|
491
|
+
特記事項: なし
|
|
492
|
+
`;
|
|
493
|
+
if (outputPath) {
|
|
494
|
+
const fs = await import("node:fs");
|
|
495
|
+
const path = await import("node:path");
|
|
496
|
+
const dir = path.dirname(outputPath);
|
|
497
|
+
if (dir !== ".") {
|
|
498
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
499
|
+
}
|
|
500
|
+
fs.writeFileSync(outputPath, template, "utf-8");
|
|
501
|
+
console.log(`Written: ${outputPath}`);
|
|
502
|
+
console.log(`\nNext steps:`);
|
|
503
|
+
console.log(` 1. ${outputPath} の各値を実際の設定に置き換える`);
|
|
504
|
+
console.log(` 2. secretRefs のキー名を secret manager に登録する`);
|
|
505
|
+
console.log(` 3. customers/${tenantId}/environments/production.yaml を作成して環境差分を記録する`);
|
|
506
|
+
console.log(` 4. smoke test(公開 URL / /admin / auth popup / contact 送信)を実施する`);
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
process.stdout.write(template);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
async function printContactEnvTemplate(args) {
|
|
513
|
+
const outputIndex = args.indexOf("--output");
|
|
514
|
+
const outputPath = outputIndex !== -1 ? args[outputIndex + 1] : undefined;
|
|
515
|
+
const template = `# contact-service-cloudflare — wrangler.toml [vars] テンプレート
|
|
516
|
+
# 各値を実際の設定に置き換えてください。
|
|
517
|
+
|
|
518
|
+
name = "contact-service-cloudflare"
|
|
519
|
+
main = "src/worker.ts"
|
|
520
|
+
compatibility_date = "2024-09-23"
|
|
521
|
+
compatibility_flags = ["nodejs_compat"]
|
|
522
|
+
|
|
523
|
+
[[kv_namespaces]]
|
|
524
|
+
binding = "RATE_LIMIT_KV"
|
|
525
|
+
id = "REPLACE_WITH_YOUR_KV_NAMESPACE_ID"
|
|
526
|
+
|
|
527
|
+
[vars]
|
|
528
|
+
# 許可する Origin (カンマ区切り)
|
|
529
|
+
ALLOWED_ORIGINS = "https://your-site.com"
|
|
530
|
+
|
|
531
|
+
# 成功・失敗時のリダイレクト先
|
|
532
|
+
SUCCESS_REDIRECT_URL = "https://your-site.com/contact/thanks"
|
|
533
|
+
FAILURE_REDIRECT_URL = "https://your-site.com/contact?error=1"
|
|
534
|
+
|
|
535
|
+
# 通知メールの設定
|
|
536
|
+
TO_EMAIL = "you@example.com"
|
|
537
|
+
FROM_EMAIL = "noreply@your-domain.com"
|
|
538
|
+
SUBJECT_PREFIX = "[Contact]"
|
|
539
|
+
|
|
540
|
+
# レスポンス方針
|
|
541
|
+
# true: 失敗も SUCCESS_REDIRECT_URL にリダイレクト(失敗を隠す)
|
|
542
|
+
# false: FAILURE_REDIRECT_URL または JSON エラーを返す
|
|
543
|
+
UNIFORM_RESPONSE = "false"
|
|
544
|
+
|
|
545
|
+
# スパム防御
|
|
546
|
+
ENABLE_HONEYPOT = "true"
|
|
547
|
+
RATE_PER_MINUTE = "5"
|
|
548
|
+
RATE_PER_10_MINUTES = "20"
|
|
549
|
+
|
|
550
|
+
# JSON ボディを許可するか(通常 false で OK)
|
|
551
|
+
ALLOW_JSON_BODY = "false"
|
|
552
|
+
|
|
553
|
+
# シークレット: wrangler secret put で設定してください
|
|
554
|
+
# wrangler secret put RESEND_API_KEY
|
|
555
|
+
# wrangler secret put TURNSTILE_SECRET_KEY
|
|
556
|
+
`;
|
|
557
|
+
if (outputPath) {
|
|
558
|
+
const fs = await import("node:fs");
|
|
559
|
+
fs.writeFileSync(outputPath, template, "utf-8");
|
|
560
|
+
console.log(`Written: ${outputPath}`);
|
|
561
|
+
console.log(`Next steps:`);
|
|
562
|
+
console.log(` 1. Edit ${outputPath} with your actual values`);
|
|
563
|
+
console.log(` 2. wrangler kv namespace create "RATE_LIMIT_KV" — copy the id`);
|
|
564
|
+
console.log(` 3. wrangler secret put RESEND_API_KEY`);
|
|
565
|
+
console.log(` 4. wrangler secret put TURNSTILE_SECRET_KEY`);
|
|
566
|
+
console.log(` 5. wrangler deploy`);
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
process.stdout.write(template);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
export {};
|
|
573
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mezzanine-stack/create-mezzanine",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"bin": {
|
|
11
|
+
"create-mezzanine": "./dist/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@types/node": "^20.0.0",
|
|
15
|
+
"typescript": "^5.7.3"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc --noEmit false",
|
|
19
|
+
"typecheck": "tsc --noEmit"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* create-mezzanine — Mezzanine Platform プロジェクト生成 CLI
|
|
4
|
+
*
|
|
5
|
+
* Phase 5: contact service の導入補助サブコマンドを提供します。
|
|
6
|
+
* Phase 6: managed ops 用の manifest-init サブコマンドを追加しました。
|
|
7
|
+
* Phase 6 (multi-tenant): KV テナント管理の tenant-add / tenant-list を追加しました。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const [, , command, ...args] = process.argv;
|
|
11
|
+
|
|
12
|
+
async function main(): Promise<void> {
|
|
13
|
+
switch (command) {
|
|
14
|
+
case "create-starter":
|
|
15
|
+
await createStarterProject(args);
|
|
16
|
+
break;
|
|
17
|
+
case "contact-env":
|
|
18
|
+
await printContactEnvTemplate(args);
|
|
19
|
+
break;
|
|
20
|
+
case "manifest-init":
|
|
21
|
+
await printManifestTemplate(args);
|
|
22
|
+
break;
|
|
23
|
+
case "tenant-add":
|
|
24
|
+
await printTenantConfig(args);
|
|
25
|
+
break;
|
|
26
|
+
case "tenant-list":
|
|
27
|
+
await listTenants(args);
|
|
28
|
+
break;
|
|
29
|
+
case "help":
|
|
30
|
+
case "--help":
|
|
31
|
+
case "-h":
|
|
32
|
+
case undefined:
|
|
33
|
+
printHelp();
|
|
34
|
+
break;
|
|
35
|
+
default:
|
|
36
|
+
console.error(`Unknown command: ${command}`);
|
|
37
|
+
printHelp();
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
main().catch((err: unknown) => {
|
|
43
|
+
console.error(err);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
function printHelp(): void {
|
|
48
|
+
console.log(`
|
|
49
|
+
create-mezzanine — Mezzanine Platform CLI
|
|
50
|
+
|
|
51
|
+
Usage:
|
|
52
|
+
create-mezzanine <command> [options]
|
|
53
|
+
|
|
54
|
+
Commands:
|
|
55
|
+
create-starter [--output <dir>] [--hosting <cloudflare|vercel|netlify|static>] [--name <project-name>] [--smoke-test]
|
|
56
|
+
starter + 必要最小限 package をコピーして、単体で動く顧客サイト repo を生成する
|
|
57
|
+
--smoke-test 指定時は生成直後に pnpm install / pnpm build を実行して検証する
|
|
58
|
+
|
|
59
|
+
contact-env [--output <path>]
|
|
60
|
+
contact-service-cloudflare 用の wrangler.toml [vars] テンプレートを出力する
|
|
61
|
+
|
|
62
|
+
manifest-init [--tenant <id>] [--output <path>]
|
|
63
|
+
managed ops 用の customer manifest テンプレートを出力する
|
|
64
|
+
|
|
65
|
+
tenant-add --origin <url> --to <email> --from <email> --success <url>
|
|
66
|
+
[--tenant <id>] [--subject <prefix>] [--failure <url>]
|
|
67
|
+
[--output <path>]
|
|
68
|
+
KV (TENANT_CONFIG_KV) に投入する顧客設定 JSON を出力する
|
|
69
|
+
Auth KV (AUTH_ALLOWED_KV) への origin 登録コマンドも案内する
|
|
70
|
+
|
|
71
|
+
tenant-list --namespace-id <id>
|
|
72
|
+
wrangler CLI 経由で TENANT_CONFIG_KV のテナント一覧を表示する
|
|
73
|
+
|
|
74
|
+
help
|
|
75
|
+
このヘルプを表示する
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
npx create-mezzanine create-starter --output ../acme-site --hosting cloudflare
|
|
79
|
+
npx create-mezzanine create-starter --output ../acme-site --hosting static --name acme-site
|
|
80
|
+
npx create-mezzanine create-starter --output ../acme-site --hosting cloudflare --smoke-test
|
|
81
|
+
npx create-mezzanine contact-env
|
|
82
|
+
npx create-mezzanine manifest-init --tenant my-client --output customers/my-client/manifest.yaml
|
|
83
|
+
npx create-mezzanine tenant-add \\
|
|
84
|
+
--origin https://acme.com \\
|
|
85
|
+
--to owner@acme.com \\
|
|
86
|
+
--from noreply@your-domain.com \\
|
|
87
|
+
--success https://acme.com/contact/thanks \\
|
|
88
|
+
--output tenant-acme.json
|
|
89
|
+
npx create-mezzanine tenant-list --namespace-id xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
90
|
+
`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* create-starter:
|
|
95
|
+
* `apps/site-starter-astro` と依存 package の最小セットをコピーし、
|
|
96
|
+
* 顧客向けの standalone リポジトリを生成する。
|
|
97
|
+
*/
|
|
98
|
+
async function createStarterProject(args: string[]): Promise<void> {
|
|
99
|
+
const get = (flag: string): string | undefined => {
|
|
100
|
+
const i = args.indexOf(flag);
|
|
101
|
+
return i !== -1 ? args[i + 1] : undefined;
|
|
102
|
+
};
|
|
103
|
+
const has = (flag: string): boolean => args.includes(flag);
|
|
104
|
+
|
|
105
|
+
const outputArg = get("--output");
|
|
106
|
+
if (!outputArg) {
|
|
107
|
+
console.error("Error: --output は必須です。");
|
|
108
|
+
console.error(" 例: npx create-mezzanine create-starter --output ../acme-site --hosting cloudflare");
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const hosting = (get("--hosting") ?? "cloudflare").toLowerCase();
|
|
113
|
+
if (!["cloudflare", "vercel", "netlify", "static"].includes(hosting)) {
|
|
114
|
+
console.error(`Error: --hosting は cloudflare|vercel|netlify|static のいずれかを指定してください。受け取った値: ${hosting}`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const projectName = get("--name") ?? "mezzanine-customer-site";
|
|
119
|
+
const smokeTest = has("--smoke-test");
|
|
120
|
+
|
|
121
|
+
const fs = await import("node:fs");
|
|
122
|
+
const path = await import("node:path");
|
|
123
|
+
const { fileURLToPath } = await import("node:url");
|
|
124
|
+
|
|
125
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
126
|
+
const repoRoot = path.resolve(path.dirname(thisFile), "../../..");
|
|
127
|
+
const outDir = path.resolve(process.cwd(), outputArg);
|
|
128
|
+
|
|
129
|
+
if (fs.existsSync(outDir) && fs.readdirSync(outDir).length > 0) {
|
|
130
|
+
console.error(`Error: 出力先ディレクトリが空ではありません: ${outDir}`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
134
|
+
|
|
135
|
+
const appSrc = path.join(repoRoot, "apps/site-starter-astro");
|
|
136
|
+
const packageNames = [
|
|
137
|
+
"tokens",
|
|
138
|
+
"css",
|
|
139
|
+
"ui",
|
|
140
|
+
"content-schema",
|
|
141
|
+
"astro-renderer",
|
|
142
|
+
"cms-core",
|
|
143
|
+
"git-provider-github",
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
const copyFiltered = (src: string, dest: string): void => {
|
|
147
|
+
fs.cpSync(src, dest, {
|
|
148
|
+
recursive: true,
|
|
149
|
+
filter: (entry) => {
|
|
150
|
+
const normalized = entry.replace(/\\/g, "/");
|
|
151
|
+
if (
|
|
152
|
+
normalized.endsWith("/node_modules") ||
|
|
153
|
+
normalized.includes("/node_modules/") ||
|
|
154
|
+
normalized.endsWith("/dist") ||
|
|
155
|
+
normalized.includes("/dist/") ||
|
|
156
|
+
normalized.endsWith("/.astro") ||
|
|
157
|
+
normalized.includes("/.astro/") ||
|
|
158
|
+
normalized.endsWith("/.git") ||
|
|
159
|
+
normalized.includes("/.git/")
|
|
160
|
+
) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
return true;
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// apps/site-starter-astro
|
|
169
|
+
const appDest = path.join(outDir, "apps/site-starter-astro");
|
|
170
|
+
fs.mkdirSync(path.dirname(appDest), { recursive: true });
|
|
171
|
+
copyFiltered(appSrc, appDest);
|
|
172
|
+
|
|
173
|
+
// 必要 package のみコピー
|
|
174
|
+
for (const name of packageNames) {
|
|
175
|
+
const src = path.join(repoRoot, "packages", name);
|
|
176
|
+
const dest = path.join(outDir, "packages", name);
|
|
177
|
+
copyFiltered(src, dest);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ルート設定ファイルを生成
|
|
181
|
+
fs.writeFileSync(
|
|
182
|
+
path.join(outDir, "package.json"),
|
|
183
|
+
JSON.stringify(
|
|
184
|
+
{
|
|
185
|
+
name: projectName,
|
|
186
|
+
private: true,
|
|
187
|
+
version: "0.1.0",
|
|
188
|
+
packageManager: "pnpm@10.28.2",
|
|
189
|
+
scripts: {
|
|
190
|
+
dev: "pnpm --filter @mezzanine-stack/site-starter-astro dev",
|
|
191
|
+
build: "pnpm --filter @mezzanine-stack/site-starter-astro build",
|
|
192
|
+
preview: "pnpm --filter @mezzanine-stack/site-starter-astro preview",
|
|
193
|
+
typecheck: "pnpm -r typecheck",
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
null,
|
|
197
|
+
2,
|
|
198
|
+
) + "\n",
|
|
199
|
+
"utf-8",
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
fs.writeFileSync(
|
|
203
|
+
path.join(outDir, "pnpm-workspace.yaml"),
|
|
204
|
+
'packages:\n - "apps/*"\n - "packages/*"\n',
|
|
205
|
+
"utf-8",
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const tsconfigBaseSrc = path.join(repoRoot, "tsconfig.base.json");
|
|
209
|
+
if (fs.existsSync(tsconfigBaseSrc)) {
|
|
210
|
+
fs.copyFileSync(tsconfigBaseSrc, path.join(outDir, "tsconfig.base.json"));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
fs.writeFileSync(
|
|
214
|
+
path.join(outDir, ".gitignore"),
|
|
215
|
+
"node_modules\n.pnpm-store\n.astro\ndist\n.DS_Store\n",
|
|
216
|
+
"utf-8",
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
writeHostingFiles(fs, path, outDir, hosting);
|
|
220
|
+
writeStarterReadme(fs, path, outDir, hosting);
|
|
221
|
+
|
|
222
|
+
if (smokeTest) {
|
|
223
|
+
await runStarterSmokeTest(outDir);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.log(`Created starter project: ${outDir}`);
|
|
227
|
+
console.log(`Hosting preset: ${hosting}`);
|
|
228
|
+
console.log("");
|
|
229
|
+
console.log("Next steps:");
|
|
230
|
+
console.log(` cd ${outDir}`);
|
|
231
|
+
console.log(" pnpm install");
|
|
232
|
+
console.log(" pnpm dev");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function runStarterSmokeTest(outDir: string): Promise<void> {
|
|
236
|
+
const { spawnSync } = await import("node:child_process");
|
|
237
|
+
|
|
238
|
+
console.log("Running smoke test...");
|
|
239
|
+
console.log(" 1) pnpm install");
|
|
240
|
+
const install = spawnSync("pnpm", ["install"], {
|
|
241
|
+
cwd: outDir,
|
|
242
|
+
stdio: "inherit",
|
|
243
|
+
env: process.env,
|
|
244
|
+
});
|
|
245
|
+
if (install.error) {
|
|
246
|
+
console.error("Smoke test failed: pnpm install を実行できませんでした。");
|
|
247
|
+
console.error(install.error.message);
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
if (install.status !== 0) {
|
|
251
|
+
console.error(`Smoke test failed: pnpm install が失敗しました (exit=${install.status ?? 1})`);
|
|
252
|
+
process.exit(install.status ?? 1);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
console.log(" 2) pnpm build");
|
|
256
|
+
const build = spawnSync("pnpm", ["build"], {
|
|
257
|
+
cwd: outDir,
|
|
258
|
+
stdio: "inherit",
|
|
259
|
+
env: process.env,
|
|
260
|
+
});
|
|
261
|
+
if (build.error) {
|
|
262
|
+
console.error("Smoke test failed: pnpm build を実行できませんでした。");
|
|
263
|
+
console.error(build.error.message);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
if (build.status !== 0) {
|
|
267
|
+
console.error(`Smoke test failed: pnpm build が失敗しました (exit=${build.status ?? 1})`);
|
|
268
|
+
process.exit(build.status ?? 1);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
console.log("Smoke test passed.");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function writeHostingFiles(
|
|
275
|
+
fs: typeof import("node:fs"),
|
|
276
|
+
path: typeof import("node:path"),
|
|
277
|
+
outDir: string,
|
|
278
|
+
hosting: string,
|
|
279
|
+
): void {
|
|
280
|
+
const appDir = path.join(outDir, "apps/site-starter-astro");
|
|
281
|
+
|
|
282
|
+
if (hosting === "vercel") {
|
|
283
|
+
fs.writeFileSync(
|
|
284
|
+
path.join(appDir, "vercel.json"),
|
|
285
|
+
JSON.stringify(
|
|
286
|
+
{
|
|
287
|
+
framework: "astro",
|
|
288
|
+
buildCommand: "pnpm --filter @mezzanine-stack/site-starter-astro build",
|
|
289
|
+
outputDirectory: "dist",
|
|
290
|
+
},
|
|
291
|
+
null,
|
|
292
|
+
2,
|
|
293
|
+
) + "\n",
|
|
294
|
+
"utf-8",
|
|
295
|
+
);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (hosting === "netlify") {
|
|
300
|
+
fs.writeFileSync(
|
|
301
|
+
path.join(appDir, "netlify.toml"),
|
|
302
|
+
`[build]
|
|
303
|
+
base = "apps/site-starter-astro"
|
|
304
|
+
publish = "dist"
|
|
305
|
+
command = "pnpm build"
|
|
306
|
+
`,
|
|
307
|
+
"utf-8",
|
|
308
|
+
);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (hosting === "cloudflare") {
|
|
313
|
+
const publicDir = path.join(appDir, "public");
|
|
314
|
+
fs.mkdirSync(publicDir, { recursive: true });
|
|
315
|
+
fs.writeFileSync(
|
|
316
|
+
path.join(publicDir, "_headers"),
|
|
317
|
+
`/*
|
|
318
|
+
X-Frame-Options: SAMEORIGIN
|
|
319
|
+
X-Content-Type-Options: nosniff
|
|
320
|
+
`,
|
|
321
|
+
"utf-8",
|
|
322
|
+
);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (hosting === "static") {
|
|
327
|
+
fs.writeFileSync(
|
|
328
|
+
path.join(outDir, "HOSTING.md"),
|
|
329
|
+
`# Hosting Guide (Static)
|
|
330
|
+
|
|
331
|
+
## Build
|
|
332
|
+
|
|
333
|
+
\`\`\`bash
|
|
334
|
+
pnpm install
|
|
335
|
+
pnpm build
|
|
336
|
+
\`\`\`
|
|
337
|
+
|
|
338
|
+
Static output is generated under:
|
|
339
|
+
|
|
340
|
+
\`apps/site-starter-astro/dist\`
|
|
341
|
+
|
|
342
|
+
Upload this directory to your static hosting platform (S3 + CDN, Nginx, etc).
|
|
343
|
+
`,
|
|
344
|
+
"utf-8",
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function writeStarterReadme(
|
|
350
|
+
fs: typeof import("node:fs"),
|
|
351
|
+
path: typeof import("node:path"),
|
|
352
|
+
outDir: string,
|
|
353
|
+
hosting: string,
|
|
354
|
+
): void {
|
|
355
|
+
fs.writeFileSync(
|
|
356
|
+
path.join(outDir, "README.md"),
|
|
357
|
+
`# Mezzanine Starter (Generated)
|
|
358
|
+
|
|
359
|
+
このプロジェクトは \`create-mezzanine create-starter\` で生成されました。
|
|
360
|
+
|
|
361
|
+
## Hosting preset
|
|
362
|
+
|
|
363
|
+
\`${hosting}\`
|
|
364
|
+
|
|
365
|
+
## Quick start
|
|
366
|
+
|
|
367
|
+
\`\`\`bash
|
|
368
|
+
pnpm install
|
|
369
|
+
pnpm dev
|
|
370
|
+
\`\`\`
|
|
371
|
+
|
|
372
|
+
## CMS
|
|
373
|
+
|
|
374
|
+
- サイトと同一ドメインで \`/admin\` を提供します
|
|
375
|
+
- 認証は \`PUBLIC_AUTH_BROKER_URL\` を設定して利用します
|
|
376
|
+
|
|
377
|
+
## Commands
|
|
378
|
+
|
|
379
|
+
- \`pnpm dev\`
|
|
380
|
+
- \`pnpm build\`
|
|
381
|
+
- \`pnpm preview\`
|
|
382
|
+
- \`pnpm typecheck\`
|
|
383
|
+
`,
|
|
384
|
+
"utf-8",
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* tenant-add: KV に投入する顧客設定 JSON を生成して出力する。
|
|
390
|
+
*
|
|
391
|
+
* 使い方:
|
|
392
|
+
* npx create-mezzanine tenant-add \
|
|
393
|
+
* --origin https://acme.com \
|
|
394
|
+
* --to owner@acme.com \
|
|
395
|
+
* --from noreply@your-domain.com \
|
|
396
|
+
* --success https://acme.com/contact/thanks \
|
|
397
|
+
* [--tenant acme] [--subject "[acme お問い合わせ]"] [--failure https://acme.com/contact?error=1] \
|
|
398
|
+
* [--output tenant-acme.json]
|
|
399
|
+
*
|
|
400
|
+
* 出力後は以下のコマンドで KV に投入する:
|
|
401
|
+
* wrangler kv key put --binding=TENANT_CONFIG_KV "tenant:<origin>" "$(cat <output>)"
|
|
402
|
+
* wrangler kv key put --binding=AUTH_ALLOWED_KV "allowed-origin:<origin>" "1"
|
|
403
|
+
*/
|
|
404
|
+
async function printTenantConfig(args: string[]): Promise<void> {
|
|
405
|
+
const get = (flag: string): string | undefined => {
|
|
406
|
+
const i = args.indexOf(flag);
|
|
407
|
+
return i !== -1 ? args[i + 1] : undefined;
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const origin = get("--origin");
|
|
411
|
+
const toEmail = get("--to");
|
|
412
|
+
const fromEmail = get("--from");
|
|
413
|
+
const successUrl = get("--success");
|
|
414
|
+
|
|
415
|
+
if (!origin || !toEmail || !fromEmail || !successUrl) {
|
|
416
|
+
console.error("Error: --origin, --to, --from, --success はすべて必須です。");
|
|
417
|
+
console.error(" 例: npx create-mezzanine tenant-add --origin https://acme.com --to owner@acme.com --from noreply@your-domain.com --success https://acme.com/contact/thanks");
|
|
418
|
+
process.exit(1);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const tenantId = get("--tenant") ?? origin.replace(/https?:\/\//, "").replace(/[^a-z0-9-]/gi, "-");
|
|
422
|
+
const subjectPrefix = get("--subject");
|
|
423
|
+
const failureUrl = get("--failure");
|
|
424
|
+
const outputPath = get("--output");
|
|
425
|
+
|
|
426
|
+
const config = {
|
|
427
|
+
tenantId,
|
|
428
|
+
toEmails: [toEmail],
|
|
429
|
+
fromEmail,
|
|
430
|
+
...(subjectPrefix ? { subjectPrefix } : {}),
|
|
431
|
+
successRedirectUrl: successUrl,
|
|
432
|
+
...(failureUrl ? { failureRedirectUrl: failureUrl } : {}),
|
|
433
|
+
uniformResponse: false,
|
|
434
|
+
enableHoneypot: true,
|
|
435
|
+
captchaRequired: true,
|
|
436
|
+
ratePerMinute: 5,
|
|
437
|
+
ratePer10Minutes: 20,
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
const json = JSON.stringify(config, null, 2);
|
|
441
|
+
|
|
442
|
+
if (outputPath) {
|
|
443
|
+
const fs = await import("node:fs");
|
|
444
|
+
fs.writeFileSync(outputPath, json, "utf-8");
|
|
445
|
+
console.log(`Written: ${outputPath}`);
|
|
446
|
+
} else {
|
|
447
|
+
process.stdout.write(json + "\n");
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const kvKey = `tenant:${origin}`;
|
|
451
|
+
const authKey = `allowed-origin:${origin}`;
|
|
452
|
+
const src = outputPath ? `"$(cat ${outputPath})"` : `'${json.replace(/'/g, "'\\''")}'`;
|
|
453
|
+
|
|
454
|
+
console.error(`
|
|
455
|
+
Next steps — TENANT_CONFIG_KV に登録する:
|
|
456
|
+
wrangler kv key put --binding=TENANT_CONFIG_KV "${kvKey}" ${src}
|
|
457
|
+
|
|
458
|
+
Next steps — AUTH_ALLOWED_KV に登録する:
|
|
459
|
+
wrangler kv key put --binding=AUTH_ALLOWED_KV "${authKey}" "1"
|
|
460
|
+
`);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* tenant-list: wrangler CLI 経由で TENANT_CONFIG_KV のテナント一覧を表示する。
|
|
465
|
+
*
|
|
466
|
+
* 使い方:
|
|
467
|
+
* npx create-mezzanine tenant-list --namespace-id <KV_NAMESPACE_ID>
|
|
468
|
+
*
|
|
469
|
+
* wrangler CLI が PATH に存在する必要があります。
|
|
470
|
+
*/
|
|
471
|
+
async function listTenants(args: string[]): Promise<void> {
|
|
472
|
+
const nsIndex = args.indexOf("--namespace-id");
|
|
473
|
+
const namespaceId = nsIndex !== -1 ? args[nsIndex + 1] : undefined;
|
|
474
|
+
|
|
475
|
+
if (!namespaceId) {
|
|
476
|
+
console.error("Error: --namespace-id は必須です。");
|
|
477
|
+
console.error(" 例: npx create-mezzanine tenant-list --namespace-id xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
|
|
478
|
+
console.error("\n namespace ID は以下のコマンドで確認できます:");
|
|
479
|
+
console.error(" wrangler kv namespace list");
|
|
480
|
+
process.exit(1);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const { spawnSync } = await import("node:child_process");
|
|
484
|
+
const result = spawnSync(
|
|
485
|
+
"wrangler",
|
|
486
|
+
["kv", "key", "list", "--namespace-id", namespaceId, "--prefix", "tenant:"],
|
|
487
|
+
{ encoding: "utf-8" },
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
if (result.error) {
|
|
491
|
+
console.error("wrangler の実行に失敗しました。wrangler がインストールされているか確認してください。");
|
|
492
|
+
console.error(result.error.message);
|
|
493
|
+
process.exit(1);
|
|
494
|
+
}
|
|
495
|
+
if (result.status !== 0) {
|
|
496
|
+
console.error(result.stderr ?? "wrangler がエラーを返しました。");
|
|
497
|
+
process.exit(result.status ?? 1);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
let keys: Array<{ name: string }> = [];
|
|
501
|
+
try {
|
|
502
|
+
keys = JSON.parse(result.stdout) as Array<{ name: string }>;
|
|
503
|
+
} catch {
|
|
504
|
+
process.stdout.write(result.stdout);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (keys.length === 0) {
|
|
509
|
+
console.log("登録済みのテナントはありません。");
|
|
510
|
+
console.log(" npx create-mezzanine tenant-add でテナントを追加してください。");
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
console.log(`登録済みテナント (${keys.length} 件):`);
|
|
515
|
+
for (const key of keys) {
|
|
516
|
+
const origin = key.name.replace(/^tenant:/, "");
|
|
517
|
+
console.log(` ${origin}`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async function printManifestTemplate(args: string[]): Promise<void> {
|
|
522
|
+
const tenantIndex = args.indexOf("--tenant");
|
|
523
|
+
const tenantId = tenantIndex !== -1 ? args[tenantIndex + 1] : "REPLACE_WITH_TENANT_ID";
|
|
524
|
+
|
|
525
|
+
const outputIndex = args.indexOf("--output");
|
|
526
|
+
const outputPath = outputIndex !== -1 ? args[outputIndex + 1] : undefined;
|
|
527
|
+
|
|
528
|
+
const template = `# Mezzanine Managed Ops — Customer Manifest
|
|
529
|
+
# このファイルは private 運用 repo の customers/<tenant-id>/manifest.yaml として管理します。
|
|
530
|
+
# 顧客固有のシークレット実体はここに置かず、secretRefs にキー名のみ記録してください。
|
|
531
|
+
#
|
|
532
|
+
# 生成コマンド: npx create-mezzanine manifest-init --tenant ${tenantId}
|
|
533
|
+
# 参照: docs/adr/0004-phase6-managed-ops-design.md
|
|
534
|
+
|
|
535
|
+
# --- 識別情報 ---
|
|
536
|
+
tenantId: "${tenantId}"
|
|
537
|
+
schemaVersion: "1"
|
|
538
|
+
|
|
539
|
+
# --- 顧客サイト ---
|
|
540
|
+
site:
|
|
541
|
+
repository: "https://github.com/YOUR_ORG/${tenantId}-site"
|
|
542
|
+
defaultBranch: "main"
|
|
543
|
+
hosting: "cloudflare-pages"
|
|
544
|
+
domains:
|
|
545
|
+
production: "https://your-site.com"
|
|
546
|
+
staging: "https://staging.your-site.com"
|
|
547
|
+
|
|
548
|
+
# --- 利用モジュール ---
|
|
549
|
+
modules:
|
|
550
|
+
cmsAdmin: true
|
|
551
|
+
authBroker: true
|
|
552
|
+
contactService: true
|
|
553
|
+
|
|
554
|
+
# --- Platform バージョン ---
|
|
555
|
+
platform:
|
|
556
|
+
# @mezzanine-stack/* packages の使用バージョン (semver)
|
|
557
|
+
packageVersion: "0.1.0"
|
|
558
|
+
# site-starter-astro の起点バージョン (git tag または commit SHA)
|
|
559
|
+
starterBaseline: "v0.1.0"
|
|
560
|
+
# このファイルの manifest schema バージョン
|
|
561
|
+
manifestSchemaVersion: "1"
|
|
562
|
+
|
|
563
|
+
# --- リリースポリシー ---
|
|
564
|
+
# stable : smoke test 通過後、順次適用候補にする
|
|
565
|
+
# pilot : 先行検証環境で確認してから stable 顧客へ展開する
|
|
566
|
+
# pinned : 明示 opt-in なしに更新しない
|
|
567
|
+
releasePolicy: "stable"
|
|
568
|
+
|
|
569
|
+
# --- シークレット参照 (実体は secret manager に登録し、ここにはキー名のみ記録) ---
|
|
570
|
+
secretRefs:
|
|
571
|
+
# Cloudflare Pages / Workers の secret 名
|
|
572
|
+
resendApiKey: "RESEND_API_KEY"
|
|
573
|
+
turnstileSecretKey: "TURNSTILE_SECRET_KEY"
|
|
574
|
+
# GitHub OAuth broker の secret 名
|
|
575
|
+
githubClientId: "GITHUB_CLIENT_ID"
|
|
576
|
+
githubClientSecret: "GITHUB_CLIENT_SECRET"
|
|
577
|
+
|
|
578
|
+
# --- 監視・通知 ---
|
|
579
|
+
observability:
|
|
580
|
+
uptimeChecks:
|
|
581
|
+
- url: "https://your-site.com"
|
|
582
|
+
name: "site-top"
|
|
583
|
+
- url: "https://your-site.com/admin"
|
|
584
|
+
name: "cms-admin"
|
|
585
|
+
- url: "https://auth.your-domain.com/api/auth"
|
|
586
|
+
name: "auth-broker"
|
|
587
|
+
- url: "https://contact.your-domain.com"
|
|
588
|
+
name: "contact-service"
|
|
589
|
+
notifyTo: "ops@your-domain.com"
|
|
590
|
+
alertThresholdMs: 3000
|
|
591
|
+
|
|
592
|
+
# --- 運用メモ ---
|
|
593
|
+
notes: |
|
|
594
|
+
初回導入日: YYYY-MM-DD
|
|
595
|
+
担当者: YOUR_NAME
|
|
596
|
+
特記事項: なし
|
|
597
|
+
`;
|
|
598
|
+
|
|
599
|
+
if (outputPath) {
|
|
600
|
+
const fs = await import("node:fs");
|
|
601
|
+
const path = await import("node:path");
|
|
602
|
+
const dir = path.dirname(outputPath);
|
|
603
|
+
if (dir !== ".") {
|
|
604
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
605
|
+
}
|
|
606
|
+
fs.writeFileSync(outputPath, template, "utf-8");
|
|
607
|
+
console.log(`Written: ${outputPath}`);
|
|
608
|
+
console.log(`\nNext steps:`);
|
|
609
|
+
console.log(` 1. ${outputPath} の各値を実際の設定に置き換える`);
|
|
610
|
+
console.log(` 2. secretRefs のキー名を secret manager に登録する`);
|
|
611
|
+
console.log(` 3. customers/${tenantId}/environments/production.yaml を作成して環境差分を記録する`);
|
|
612
|
+
console.log(` 4. smoke test(公開 URL / /admin / auth popup / contact 送信)を実施する`);
|
|
613
|
+
} else {
|
|
614
|
+
process.stdout.write(template);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
async function printContactEnvTemplate(args: string[]): Promise<void> {
|
|
619
|
+
const outputIndex = args.indexOf("--output");
|
|
620
|
+
const outputPath = outputIndex !== -1 ? args[outputIndex + 1] : undefined;
|
|
621
|
+
|
|
622
|
+
const template = `# contact-service-cloudflare — wrangler.toml [vars] テンプレート
|
|
623
|
+
# 各値を実際の設定に置き換えてください。
|
|
624
|
+
|
|
625
|
+
name = "contact-service-cloudflare"
|
|
626
|
+
main = "src/worker.ts"
|
|
627
|
+
compatibility_date = "2024-09-23"
|
|
628
|
+
compatibility_flags = ["nodejs_compat"]
|
|
629
|
+
|
|
630
|
+
[[kv_namespaces]]
|
|
631
|
+
binding = "RATE_LIMIT_KV"
|
|
632
|
+
id = "REPLACE_WITH_YOUR_KV_NAMESPACE_ID"
|
|
633
|
+
|
|
634
|
+
[vars]
|
|
635
|
+
# 許可する Origin (カンマ区切り)
|
|
636
|
+
ALLOWED_ORIGINS = "https://your-site.com"
|
|
637
|
+
|
|
638
|
+
# 成功・失敗時のリダイレクト先
|
|
639
|
+
SUCCESS_REDIRECT_URL = "https://your-site.com/contact/thanks"
|
|
640
|
+
FAILURE_REDIRECT_URL = "https://your-site.com/contact?error=1"
|
|
641
|
+
|
|
642
|
+
# 通知メールの設定
|
|
643
|
+
TO_EMAIL = "you@example.com"
|
|
644
|
+
FROM_EMAIL = "noreply@your-domain.com"
|
|
645
|
+
SUBJECT_PREFIX = "[Contact]"
|
|
646
|
+
|
|
647
|
+
# レスポンス方針
|
|
648
|
+
# true: 失敗も SUCCESS_REDIRECT_URL にリダイレクト(失敗を隠す)
|
|
649
|
+
# false: FAILURE_REDIRECT_URL または JSON エラーを返す
|
|
650
|
+
UNIFORM_RESPONSE = "false"
|
|
651
|
+
|
|
652
|
+
# スパム防御
|
|
653
|
+
ENABLE_HONEYPOT = "true"
|
|
654
|
+
RATE_PER_MINUTE = "5"
|
|
655
|
+
RATE_PER_10_MINUTES = "20"
|
|
656
|
+
|
|
657
|
+
# JSON ボディを許可するか(通常 false で OK)
|
|
658
|
+
ALLOW_JSON_BODY = "false"
|
|
659
|
+
|
|
660
|
+
# シークレット: wrangler secret put で設定してください
|
|
661
|
+
# wrangler secret put RESEND_API_KEY
|
|
662
|
+
# wrangler secret put TURNSTILE_SECRET_KEY
|
|
663
|
+
`;
|
|
664
|
+
|
|
665
|
+
if (outputPath) {
|
|
666
|
+
const fs = await import("node:fs");
|
|
667
|
+
fs.writeFileSync(outputPath, template, "utf-8");
|
|
668
|
+
console.log(`Written: ${outputPath}`);
|
|
669
|
+
console.log(`Next steps:`);
|
|
670
|
+
console.log(` 1. Edit ${outputPath} with your actual values`);
|
|
671
|
+
console.log(` 2. wrangler kv namespace create "RATE_LIMIT_KV" — copy the id`);
|
|
672
|
+
console.log(` 3. wrangler secret put RESEND_API_KEY`);
|
|
673
|
+
console.log(` 4. wrangler secret put TURNSTILE_SECRET_KEY`);
|
|
674
|
+
console.log(` 5. wrangler deploy`);
|
|
675
|
+
} else {
|
|
676
|
+
process.stdout.write(template);
|
|
677
|
+
}
|
|
678
|
+
}
|