@c-time/frelio-cli 1.4.9 → 1.4.11
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 +179 -3
- package/dist/commands/doctor.d.ts +11 -0
- package/dist/commands/doctor.js +71 -0
- package/dist/commands/fleet.d.ts +16 -0
- package/dist/commands/fleet.js +110 -0
- package/dist/commands/init.js +1 -0
- package/dist/commands/set-mode.d.ts +10 -0
- package/dist/commands/set-mode.js +41 -0
- package/dist/commands/update.d.ts +7 -2
- package/dist/commands/update.js +72 -19
- package/dist/core/config.js +1 -0
- package/dist/core/doctor.d.ts +36 -0
- package/dist/core/doctor.js +86 -0
- package/dist/core/file-generators.d.ts +3 -0
- package/dist/core/file-generators.js +13 -3
- package/dist/core/fleet.d.ts +62 -0
- package/dist/core/fleet.js +172 -0
- package/dist/core/migrations/index.d.ts +3 -0
- package/dist/core/migrations/index.js +2 -0
- package/dist/core/migrations/node20-to-node24.d.ts +15 -0
- package/dist/core/migrations/node20-to-node24.js +35 -0
- package/dist/core/migrations/registry.d.ts +11 -0
- package/dist/core/migrations/registry.js +18 -0
- package/dist/core/migrations/types.d.ts +55 -0
- package/dist/core/migrations/types.js +11 -0
- package/dist/core/site-mode.d.ts +23 -0
- package/dist/core/site-mode.js +37 -0
- package/dist/core/template-refresh.d.ts +41 -0
- package/dist/core/template-refresh.js +148 -0
- package/dist/core/template-scaffold.d.ts +11 -0
- package/dist/core/template-scaffold.js +12 -2
- package/dist/core/types.d.ts +2 -0
- package/dist/core/version-check.d.ts +31 -0
- package/dist/core/version-check.js +114 -0
- package/dist/index.js +41 -3
- package/dist/lib/npm-registry.d.ts +12 -0
- package/dist/lib/npm-registry.js +30 -0
- package/dist/lib/template-renderer.js +6 -1
- package/dist/lib/templates.d.ts +36 -0
- package/dist/lib/templates.js +171 -0
- package/package.json +2 -2
package/dist/lib/templates.js
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
+
/** 本番ゲートのサイト公開状態(Issue #93) */
|
|
7
|
+
export const SITE_MODES = ['live', 'prelaunch', 'maintenance', 'closed'];
|
|
8
|
+
/** config.siteMode を正規化する(未知値・未指定は 'live') */
|
|
9
|
+
export function resolveSiteMode(config) {
|
|
10
|
+
const m = (config.siteMode ?? 'live');
|
|
11
|
+
return SITE_MODES.includes(m) ? m : 'live';
|
|
12
|
+
}
|
|
13
|
+
/** productionUrl から正規化先ホスト(独自ドメイン)を導出する。無効/空なら空文字。 */
|
|
14
|
+
export function deriveCanonicalHost(config) {
|
|
15
|
+
try {
|
|
16
|
+
return new URL(config.productionUrl).hostname;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** コンテンツ配信 Pages の本番 pages.dev ホストを導出する(例: mysite.pages.dev)。 */
|
|
23
|
+
export function deriveContentPagesDev(config) {
|
|
24
|
+
return config.pagesProjectName ? `${config.pagesProjectName}.pages.dev` : '';
|
|
25
|
+
}
|
|
6
26
|
/**
|
|
7
27
|
* ランダムな8文字のハッシュを生成(URL推測を困難にする)
|
|
8
28
|
*/
|
|
@@ -73,6 +93,10 @@ export function generateConfigJson(config) {
|
|
|
73
93
|
allowedOrigins: buildAllowedOrigins(config),
|
|
74
94
|
// デプロイ管理機能(Issue #27)の表示用。account_id は非シークレットのため config.json に保持する。
|
|
75
95
|
cloudflareAccountId: config.cloudflareAccountId ?? '',
|
|
96
|
+
// 保存タイムゾーン(Issue #92)。日時の保存オフセット。
|
|
97
|
+
saveTimezone: config.saveTimezone || '+09:00',
|
|
98
|
+
// 本番ゲートのサイト公開状態(Issue #93)。エッジ実体は wrangler [vars] の SITE_MODE。
|
|
99
|
+
siteMode: resolveSiteMode(config),
|
|
76
100
|
}, null, 2);
|
|
77
101
|
}
|
|
78
102
|
/**
|
|
@@ -123,6 +147,12 @@ CONTENT_REPO = "${config.contentRepo}"
|
|
|
123
147
|
# CLOUDFLARE_API_TOKEN はシークレットのため wrangler pages secret で管理画面プロジェクトに設定する(ここには置かない)。
|
|
124
148
|
CLOUDFLARE_ACCOUNT_ID = "${config.cloudflareAccountId ?? ''}"
|
|
125
149
|
PAGES_PROJECT_NAME = "${config.pagesProjectName}"
|
|
150
|
+
# 本番ゲート(Issue #93): コンテンツ配信の functions/_middleware.ts がエッジで参照する。
|
|
151
|
+
# SITE_MODE: live | prelaunch | maintenance | closed。
|
|
152
|
+
# CANONICAL_HOST / CONTENT_PAGES_DEV は派生値(productionUrl ホスト / <project>.pages.dev)。
|
|
153
|
+
SITE_MODE = "${resolveSiteMode(config)}"
|
|
154
|
+
CANONICAL_HOST = "${deriveCanonicalHost(config)}"
|
|
155
|
+
CONTENT_PAGES_DEV = "${deriveContentPagesDev(config)}"
|
|
126
156
|
`;
|
|
127
157
|
}
|
|
128
158
|
export function generateWorkerWranglerToml(config) {
|
|
@@ -156,6 +186,147 @@ export function generateRoutesJson() {
|
|
|
156
186
|
exclude: [],
|
|
157
187
|
}, null, 2);
|
|
158
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* コンテンツ配信側(public/)の _routes.json を生成する。
|
|
191
|
+
*
|
|
192
|
+
* 本番ゲート(Issue #93)の functions/_middleware.ts に全リクエストを通すため
|
|
193
|
+
* include を `/*` にする。これにより静的ページもエッジ middleware を経由する
|
|
194
|
+
* (live モードでは即 next() で素通り)。
|
|
195
|
+
*/
|
|
196
|
+
export function generatePublicRoutesJson() {
|
|
197
|
+
return JSON.stringify({
|
|
198
|
+
version: 1,
|
|
199
|
+
include: ['/*'],
|
|
200
|
+
exclude: [],
|
|
201
|
+
}, null, 2);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* 本番ゲート middleware(Issue #93)を生成する。
|
|
205
|
+
*
|
|
206
|
+
* 注意: この functions/ は管理画面 Pages にも配られる共有資産のため、
|
|
207
|
+
* ホスト判定で「コンテンツのホスト」のみを対象にし、admin / staging /
|
|
208
|
+
* localhost / 不明ホストは素通り(next)して CMS を保護する。
|
|
209
|
+
*
|
|
210
|
+
* テンプレート実ファイル
|
|
211
|
+
* (templates/content-repo/functions/_middleware.ts)と同一文字列を返す。
|
|
212
|
+
* 乖離は sync テストで検出する。
|
|
213
|
+
*/
|
|
214
|
+
export function generateGateMiddleware() {
|
|
215
|
+
return GATE_MIDDLEWARE_SOURCE;
|
|
216
|
+
}
|
|
217
|
+
const GATE_MIDDLEWARE_SOURCE = `/**
|
|
218
|
+
* 本番ゲート middleware(Issue #93)
|
|
219
|
+
*
|
|
220
|
+
* コンテンツ配信 Pages のエッジで動作し、サイトの公開状態(siteMode)と
|
|
221
|
+
* URL 正規化(pages.dev → 独自ドメイン 301)を制御する。
|
|
222
|
+
*
|
|
223
|
+
* 重要: この functions/ は管理画面 Pages にも配られる共有資産のため、
|
|
224
|
+
* ホスト判定で「コンテンツのホスト」のみを対象にし、admin / staging /
|
|
225
|
+
* localhost / 不明ホストは素通り(next)して CMS(/api/*)を保護する。
|
|
226
|
+
*
|
|
227
|
+
* 設定は wrangler.toml [vars](context.env)から読む:
|
|
228
|
+
* SITE_MODE live | prelaunch | maintenance | closed(既定 live)
|
|
229
|
+
* CANONICAL_HOST 正規化先ホスト(独自ドメイン。例 example.com)
|
|
230
|
+
* CONTENT_PAGES_DEV コンテンツ Pages の本番ホスト(例 mysite.pages.dev)
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
interface GateEnv {
|
|
234
|
+
SITE_MODE?: string
|
|
235
|
+
CANONICAL_HOST?: string
|
|
236
|
+
CONTENT_PAGES_DEV?: string
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
type GateDecision =
|
|
240
|
+
| { action: 'next' }
|
|
241
|
+
| { action: 'redirect'; location: string }
|
|
242
|
+
| { action: 'block'; status: number }
|
|
243
|
+
|
|
244
|
+
export function decideGate(
|
|
245
|
+
host: string,
|
|
246
|
+
pathname: string,
|
|
247
|
+
search: string,
|
|
248
|
+
env: GateEnv,
|
|
249
|
+
): GateDecision {
|
|
250
|
+
const canonical = (env.CANONICAL_HOST || '').trim()
|
|
251
|
+
const pagesDev = (env.CONTENT_PAGES_DEV || '').trim()
|
|
252
|
+
const mode = (env.SITE_MODE || 'live').trim()
|
|
253
|
+
|
|
254
|
+
const isCanonical = canonical !== '' && host === canonical
|
|
255
|
+
const isPagesDev = pagesDev !== '' && host === pagesDev
|
|
256
|
+
|
|
257
|
+
// コンテンツのホスト以外(admin / staging / localhost / 不明)は素通り = CMS 保護
|
|
258
|
+
if (!isCanonical && !isPagesDev) return { action: 'next' }
|
|
259
|
+
|
|
260
|
+
// pages.dev → 独自ドメインへ 301(canonical 設定時のみ)
|
|
261
|
+
if (isPagesDev && !isCanonical && canonical !== '') {
|
|
262
|
+
return { action: 'redirect', location: \`https://\${canonical}\${pathname}\${search}\` }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (mode === 'live') return { action: 'next' }
|
|
266
|
+
|
|
267
|
+
const isStorage = pathname === '/storage' || pathname.startsWith('/storage/')
|
|
268
|
+
|
|
269
|
+
// 閉鎖: storage 含め全遮断(キルスイッチ)
|
|
270
|
+
if (mode === 'closed') return { action: 'block', status: 503 }
|
|
271
|
+
// プレ公開: storage 以外を 403(storage は CMS のサムネ/プレビュー用に配信継続)
|
|
272
|
+
if (mode === 'prelaunch') {
|
|
273
|
+
return isStorage ? { action: 'next' } : { action: 'block', status: 403 }
|
|
274
|
+
}
|
|
275
|
+
// メンテナンス: storage 以外を 503
|
|
276
|
+
if (mode === 'maintenance') {
|
|
277
|
+
return isStorage ? { action: 'next' } : { action: 'block', status: 503 }
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 未知の値は安全側(素通り)
|
|
281
|
+
return { action: 'next' }
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function holdingPage(status: number): string {
|
|
285
|
+
const title = status === 403 ? '公開準備中' : 'メンテナンス中'
|
|
286
|
+
const message =
|
|
287
|
+
status === 403
|
|
288
|
+
? 'このサイトは現在準備中です。公開までしばらくお待ちください。'
|
|
289
|
+
: 'ただいまメンテナンス中です。しばらく経ってから再度アクセスしてください。'
|
|
290
|
+
return \`<!doctype html>
|
|
291
|
+
<html lang="ja">
|
|
292
|
+
<head>
|
|
293
|
+
<meta charset="utf-8">
|
|
294
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
295
|
+
<meta name="robots" content="noindex">
|
|
296
|
+
<title>\${title}</title>
|
|
297
|
+
<style>
|
|
298
|
+
body { font-family: system-ui, sans-serif; display: flex; min-height: 100vh; margin: 0; align-items: center; justify-content: center; background: #f5f5f5; color: #333; }
|
|
299
|
+
.box { text-align: center; padding: 2rem; }
|
|
300
|
+
h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
|
|
301
|
+
p { color: #666; }
|
|
302
|
+
</style>
|
|
303
|
+
</head>
|
|
304
|
+
<body>
|
|
305
|
+
<div class="box"><h1>\${title}</h1><p>\${message}</p></div>
|
|
306
|
+
</body>
|
|
307
|
+
</html>
|
|
308
|
+
\`
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export const onRequest: PagesFunction<GateEnv> = async (context) => {
|
|
312
|
+
const url = new URL(context.request.url)
|
|
313
|
+
const decision = decideGate(url.hostname, url.pathname, url.search, context.env)
|
|
314
|
+
|
|
315
|
+
if (decision.action === 'redirect') {
|
|
316
|
+
return Response.redirect(decision.location, 301)
|
|
317
|
+
}
|
|
318
|
+
if (decision.action === 'block') {
|
|
319
|
+
return new Response(holdingPage(decision.status), {
|
|
320
|
+
status: decision.status,
|
|
321
|
+
headers: {
|
|
322
|
+
'content-type': 'text/html; charset=utf-8',
|
|
323
|
+
'cache-control': 'no-store',
|
|
324
|
+
},
|
|
325
|
+
})
|
|
326
|
+
}
|
|
327
|
+
return context.next()
|
|
328
|
+
}
|
|
329
|
+
`;
|
|
159
330
|
export function generateStorageFunction() {
|
|
160
331
|
return `interface Env {
|
|
161
332
|
R2: R2Bucket
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c-time/frelio-cli",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.11",
|
|
4
4
|
"description": "Frelio CMS setup CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,6 +31,6 @@
|
|
|
31
31
|
"@types/prompts": "^2.4.9",
|
|
32
32
|
"@types/node": "^22.0.0",
|
|
33
33
|
"typescript": "^5.7.0",
|
|
34
|
-
"vitest": "^4.0
|
|
34
|
+
"vitest": "^4.1.0"
|
|
35
35
|
}
|
|
36
36
|
}
|