@c-time/frelio-cli 0.1.0 → 1.3.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 +72 -17
- package/dist/commands/add-staging.d.ts +2 -0
- package/dist/commands/add-staging.js +106 -46
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.js +103 -27
- package/dist/index.js +11 -0
- package/dist/lib/initial-content.js +891 -61
- package/dist/lib/templates.d.ts +1 -0
- package/dist/lib/templates.js +141 -9
- package/dist/lib/validators.d.ts +8 -0
- package/dist/lib/validators.js +22 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Frelio CMS のプロジェクトセットアップと管理を行う CLI ツール。
|
|
4
4
|
|
|
5
|
-
対話式のプロンプトに従うだけで、GitHub リポジトリの作成からコンテンツ構造の初期化、Cloudflare のセットアップまで、すべてを自動化する。
|
|
5
|
+
対話式のプロンプトに従うだけで、GitHub リポジトリの作成からコンテンツ構造の初期化、Cloudflare のセットアップまで、すべてを自動化する。CLI オプションで全パラメータを指定すれば、非対話モードでも実行可能(AI エージェントや CI 向け)。
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -151,23 +151,61 @@ frelio init
|
|
|
151
151
|
|
|
152
152
|
#### プロンプトで聞かれること
|
|
153
153
|
|
|
154
|
-
| 項目 | 説明 | 例 |
|
|
155
|
-
|
|
156
|
-
| リポジトリ名 | `owner/repo` 形式 | `my-org/my-site` |
|
|
157
|
-
| サイトタイトル | 管理画面に表示される名前 | `My Website` |
|
|
158
|
-
| 本番 URL | 公開サイトの URL(任意) | `https://example.com` |
|
|
159
|
-
|
|
|
160
|
-
| R2 バケット名 | ファイルストレージ用バケット | `my-site-files` |
|
|
161
|
-
| R2 公開 URL | ファイル配信の URL | `https://storage.example.com` |
|
|
162
|
-
| 管理者 GitHub ユーザー名 | 最初の管理者ユーザー | `your-username` |
|
|
163
|
-
| OAuth Client ID | GitHub OAuth App の Client ID | `Ov23li...` |
|
|
164
|
-
| OAuth Client Secret | GitHub OAuth App の Client Secret | `********` |
|
|
154
|
+
| 項目 | CLI オプション | 説明 | 例 |
|
|
155
|
+
|------|---------------|------|----|
|
|
156
|
+
| リポジトリ名 | `--content-repo` | `owner/repo` 形式 | `my-org/my-site` |
|
|
157
|
+
| サイトタイトル | `--site-title` | 管理画面に表示される名前 | `My Website` |
|
|
158
|
+
| 本番 URL | `--production-url` | 公開サイトの URL(任意) | `https://example.com` |
|
|
159
|
+
| ステージングドメイン | `--staging-domain` | ステージング確認用ドメイン(任意) | `staging-abc123.example.com` |
|
|
160
|
+
| R2 バケット名 | `--r2-bucket-name` | ファイルストレージ用バケット | `my-site-files` |
|
|
161
|
+
| R2 公開 URL | `--r2-public-url` | ファイル配信の URL | `https://storage.example.com` |
|
|
162
|
+
| 管理者 GitHub ユーザー名 | `--owner-username` | 最初の管理者ユーザー | `your-username` |
|
|
163
|
+
| OAuth Client ID | `--client-id` | GitHub OAuth App の Client ID | `Ov23li...` |
|
|
164
|
+
| OAuth Client Secret | `--client-secret` | GitHub OAuth App の Client Secret | `********` |
|
|
165
165
|
|
|
166
166
|
#### オプション
|
|
167
167
|
|
|
168
168
|
```bash
|
|
169
|
+
# スキップオプション
|
|
169
170
|
frelio init --skip-github # GitHub 操作をスキップ(手動でリポジトリを用意する場合)
|
|
170
171
|
frelio init --skip-cloudflare # Cloudflare 操作をスキップ(後から設定する場合)
|
|
172
|
+
|
|
173
|
+
# パラメータ指定(非対話モード用)
|
|
174
|
+
frelio init \
|
|
175
|
+
--content-repo <owner/repo> # リポジトリ名(必須)
|
|
176
|
+
--site-title <title> # サイトタイトル
|
|
177
|
+
--production-url <url> # 本番 URL
|
|
178
|
+
--staging-domain <domain> # ステージングドメイン
|
|
179
|
+
--r2-bucket-name <name> # R2 バケット名
|
|
180
|
+
--r2-public-url <url> # R2 公開 URL
|
|
181
|
+
--owner-username <user> # 管理者 GitHub ユーザー名
|
|
182
|
+
--client-id <id> # OAuth Client ID(必須)
|
|
183
|
+
--client-secret <secret> # OAuth Client Secret(必須)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### 非対話モード(AI エージェント / CI 向け)
|
|
187
|
+
|
|
188
|
+
`--content-repo`、`--client-id`、`--client-secret` の3つを指定すると、プロンプトなしで実行される。他のパラメータは省略するとデフォルト値が自動算出される。
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# 最小構成
|
|
192
|
+
frelio init \
|
|
193
|
+
--content-repo my-org/my-site \
|
|
194
|
+
--client-id Ov23liXXX \
|
|
195
|
+
--client-secret ghp_XXX
|
|
196
|
+
|
|
197
|
+
# 全パラメータ指定
|
|
198
|
+
frelio init \
|
|
199
|
+
--content-repo my-org/my-site \
|
|
200
|
+
--site-title "My Site" \
|
|
201
|
+
--production-url https://example.com \
|
|
202
|
+
--staging-domain staging-abc123.example.com \
|
|
203
|
+
--r2-bucket-name my-site-files \
|
|
204
|
+
--r2-public-url https://storage.example.com \
|
|
205
|
+
--owner-username my-username \
|
|
206
|
+
--client-id Ov23liXXX \
|
|
207
|
+
--client-secret ghp_XXX \
|
|
208
|
+
--skip-cloudflare
|
|
171
209
|
```
|
|
172
210
|
|
|
173
211
|
#### 生成されるリポジトリ構造
|
|
@@ -234,17 +272,34 @@ frelio add-staging
|
|
|
234
272
|
|
|
235
273
|
#### プロンプトで聞かれること
|
|
236
274
|
|
|
237
|
-
| 項目 | 説明 | 例 |
|
|
238
|
-
|
|
239
|
-
| ステージング名 | ブランチ名に使われる識別子 | `design`, `alice` |
|
|
240
|
-
| Pages プロジェクト名 | Cloudflare Pages プロジェクト名 | `my-site-staging-design` |
|
|
241
|
-
| カスタムドメイン | 任意。空欄なら `<project>.pages.dev` | `staging-design.example.com` |
|
|
275
|
+
| 項目 | CLI オプション | 説明 | 例 |
|
|
276
|
+
|------|---------------|------|----|
|
|
277
|
+
| ステージング名 | `--name` | ブランチ名に使われる識別子 | `design`, `alice` |
|
|
278
|
+
| Pages プロジェクト名 | `--pages-project` | Cloudflare Pages プロジェクト名 | `my-site-staging-design` |
|
|
279
|
+
| カスタムドメイン | `--domain` | 任意。空欄なら `<project>.pages.dev` | `staging-design.example.com` |
|
|
242
280
|
|
|
243
281
|
#### オプション
|
|
244
282
|
|
|
245
283
|
```bash
|
|
246
284
|
frelio add-staging --name design # 名前を事前指定
|
|
247
285
|
frelio add-staging --skip-cloudflare # Pages プロジェクト作成をスキップ
|
|
286
|
+
frelio add-staging --pages-project <name> # Pages プロジェクト名を指定
|
|
287
|
+
frelio add-staging --domain <domain> # カスタムドメインを指定
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
#### 非対話モード(AI エージェント / CI 向け)
|
|
291
|
+
|
|
292
|
+
`--name` を指定するとプロンプトなしで実行される。`--pages-project` と `--domain` は省略するとデフォルト値が自動算出される。
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
# 最小構成(name のみ、他はデフォルト算出)
|
|
296
|
+
frelio add-staging --name preview1
|
|
297
|
+
|
|
298
|
+
# 全パラメータ指定
|
|
299
|
+
frelio add-staging \
|
|
300
|
+
--name preview1 \
|
|
301
|
+
--pages-project my-site-staging-preview1 \
|
|
302
|
+
--domain preview1-abc123.example.com
|
|
248
303
|
```
|
|
249
304
|
|
|
250
305
|
#### 完了後の手動作業
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import prompts from 'prompts';
|
|
5
5
|
import { exec, commandExists, log, logStep, logSuccess, logError } from '../lib/shell.js';
|
|
6
6
|
import { generateHash } from '../lib/templates.js';
|
|
7
|
+
import { validateStagingName, validateRequired } from '../lib/validators.js';
|
|
7
8
|
export async function addStagingCommand(options) {
|
|
8
9
|
log('');
|
|
9
10
|
log('🌿 ステージング環境の追加');
|
|
@@ -67,54 +68,105 @@ export async function addStagingCommand(options) {
|
|
|
67
68
|
catch {
|
|
68
69
|
// config.json がなくても続行
|
|
69
70
|
}
|
|
70
|
-
//
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
71
|
+
// 非対話 / 対話モード判定
|
|
72
|
+
const isInteractive = process.stdin.isTTY === true;
|
|
73
|
+
let stagingName;
|
|
74
|
+
let pagesProject;
|
|
75
|
+
let stagingDomain;
|
|
76
|
+
if (options.name && options.pagesProject) {
|
|
77
|
+
// 全必須オプション指定 → プロンプトスキップ
|
|
78
|
+
const nameCheck = validateStagingName(options.name);
|
|
79
|
+
if (nameCheck !== true) {
|
|
80
|
+
logError(`--name: ${nameCheck}`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
stagingName = options.name;
|
|
84
|
+
pagesProject = options.pagesProject;
|
|
85
|
+
stagingDomain = options.domain ?? generateDefaultDomain(stagingName, basePagesProject, baseDomain);
|
|
86
|
+
}
|
|
87
|
+
else if (options.name && !options.pagesProject) {
|
|
88
|
+
// --name のみ指定 → pagesProject はデフォルト算出 or プロンプト
|
|
89
|
+
const nameCheck = validateStagingName(options.name);
|
|
90
|
+
if (nameCheck !== true) {
|
|
91
|
+
logError(`--name: ${nameCheck}`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
stagingName = options.name;
|
|
95
|
+
const defaultPagesProject = basePagesProject ? `${basePagesProject}-staging-${stagingName}` : '';
|
|
96
|
+
if (!isInteractive) {
|
|
97
|
+
// 非 TTY → デフォルト値で続行
|
|
98
|
+
pagesProject = defaultPagesProject;
|
|
99
|
+
if (!pagesProject) {
|
|
100
|
+
logError('--pages-project が必要です(wrangler.toml からプロジェクト名を推定できません)');
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
stagingDomain = options.domain ?? generateDefaultDomain(stagingName, basePagesProject, baseDomain);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
// TTY → 残りをプロンプト
|
|
107
|
+
const response = await prompts([
|
|
108
|
+
{
|
|
109
|
+
type: 'text',
|
|
110
|
+
name: 'pagesProject',
|
|
111
|
+
message: 'ステージング用 Pages プロジェクト名:',
|
|
112
|
+
initial: defaultPagesProject,
|
|
113
|
+
validate: (v) => validateRequired(v, 'プロジェクト名'),
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
type: 'text',
|
|
117
|
+
name: 'domain',
|
|
118
|
+
message: 'カスタムドメイン(推測困難なハッシュ付き推奨):',
|
|
119
|
+
initial: generateDefaultDomain(stagingName, basePagesProject, baseDomain),
|
|
120
|
+
},
|
|
121
|
+
], { onCancel: () => process.exit(0) });
|
|
122
|
+
pagesProject = response.pagesProject;
|
|
123
|
+
stagingDomain = response.domain;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// --name 未指定
|
|
128
|
+
if (!isInteractive) {
|
|
129
|
+
logError('非対話モードでは --name が必須です。');
|
|
130
|
+
logError('使用例: frelio add-staging --name preview1 [--pages-project <name>] [--domain <domain>]');
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
// 従来通りの対話式プロンプト
|
|
134
|
+
const response = await prompts([
|
|
135
|
+
{
|
|
136
|
+
type: 'text',
|
|
137
|
+
name: 'name',
|
|
138
|
+
message: 'ステージング名(staging-{name} のブランチが作成されます):',
|
|
139
|
+
validate: (v) => validateStagingName(v),
|
|
84
140
|
},
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
141
|
+
{
|
|
142
|
+
type: 'text',
|
|
143
|
+
name: 'pagesProject',
|
|
144
|
+
message: 'ステージング用 Pages プロジェクト名:',
|
|
145
|
+
initial: (_prev, values) => {
|
|
146
|
+
const name = values.name || '';
|
|
147
|
+
return basePagesProject ? `${basePagesProject}-staging-${name}` : '';
|
|
148
|
+
},
|
|
149
|
+
validate: (v) => validateRequired(v, 'プロジェクト名'),
|
|
93
150
|
},
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const hash = generateHash();
|
|
103
|
-
if (baseDomain) {
|
|
104
|
-
return `${name}-${hash}.${baseDomain}`;
|
|
105
|
-
}
|
|
106
|
-
const project = basePagesProject || 'site';
|
|
107
|
-
return `${project}-${name}-${hash}.pages.dev`;
|
|
151
|
+
{
|
|
152
|
+
type: 'text',
|
|
153
|
+
name: 'domain',
|
|
154
|
+
message: 'カスタムドメイン(推測困難なハッシュ付き推奨):',
|
|
155
|
+
initial: (_prev, values) => {
|
|
156
|
+
const name = values.name || '';
|
|
157
|
+
return generateDefaultDomain(name, basePagesProject, baseDomain);
|
|
158
|
+
},
|
|
108
159
|
},
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
160
|
+
], { onCancel: () => process.exit(0) });
|
|
161
|
+
if (!response.name) {
|
|
162
|
+
log('キャンセルしました。');
|
|
163
|
+
process.exit(0);
|
|
164
|
+
}
|
|
165
|
+
stagingName = response.name;
|
|
166
|
+
pagesProject = response.pagesProject;
|
|
167
|
+
stagingDomain = response.domain;
|
|
115
168
|
}
|
|
116
169
|
const branchName = `staging-${stagingName}`;
|
|
117
|
-
const pagesProject = response.pagesProject;
|
|
118
170
|
log('');
|
|
119
171
|
const totalSteps = options.skipCloudflare ? 2 : 3;
|
|
120
172
|
let step = 0;
|
|
@@ -187,16 +239,24 @@ export async function addStagingCommand(options) {
|
|
|
187
239
|
log('');
|
|
188
240
|
log(` ブランチ: ${branchName}`);
|
|
189
241
|
if (!options.skipCloudflare) {
|
|
190
|
-
const domain =
|
|
242
|
+
const domain = stagingDomain || `${pagesProject}.pages.dev`;
|
|
191
243
|
log(` URL: https://${domain}`);
|
|
192
244
|
}
|
|
193
245
|
log('');
|
|
194
246
|
log(' 残りの手動作業:');
|
|
195
247
|
log(' - Cloudflare Pages でリポジトリを接続(GitHub integration)');
|
|
196
|
-
if (
|
|
197
|
-
log(` - Pages プロジェクトにカスタムドメイン "${
|
|
248
|
+
if (stagingDomain) {
|
|
249
|
+
log(` - Pages プロジェクトにカスタムドメイン "${stagingDomain}" を設定`);
|
|
198
250
|
}
|
|
199
251
|
log(' - Cloudflare Access でアクセス制限を設定(推奨)');
|
|
200
252
|
log(' - CMS 管理画面の /staging ページでステージングブランチを登録');
|
|
201
253
|
log('');
|
|
202
254
|
}
|
|
255
|
+
function generateDefaultDomain(name, basePagesProject, baseDomain) {
|
|
256
|
+
const hash = generateHash();
|
|
257
|
+
if (baseDomain) {
|
|
258
|
+
return `${name}-${hash}.${baseDomain}`;
|
|
259
|
+
}
|
|
260
|
+
const project = basePagesProject || 'site';
|
|
261
|
+
return `${project}-${name}-${hash}.pages.dev`;
|
|
262
|
+
}
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
type InitOptions = {
|
|
5
5
|
skipGithub?: boolean;
|
|
6
6
|
skipCloudflare?: boolean;
|
|
7
|
+
contentRepo?: string;
|
|
8
|
+
siteTitle?: string;
|
|
9
|
+
productionUrl?: string;
|
|
10
|
+
stagingDomain?: string;
|
|
11
|
+
r2BucketName?: string;
|
|
12
|
+
r2PublicUrl?: string;
|
|
13
|
+
ownerUsername?: string;
|
|
14
|
+
clientId?: string;
|
|
15
|
+
clientSecret?: string;
|
|
7
16
|
};
|
|
8
17
|
export declare function initCommand(options: InitOptions): Promise<void>;
|
|
9
18
|
export {};
|
package/dist/commands/init.js
CHANGED
|
@@ -8,7 +8,8 @@ import os from 'node:os';
|
|
|
8
8
|
import { exec, commandExists, log, logStep, logSuccess, logError } from '../lib/shell.js';
|
|
9
9
|
import { getLatestRelease, downloadTarball } from '../lib/github-release.js';
|
|
10
10
|
import { generateInitialContent } from '../lib/initial-content.js';
|
|
11
|
-
import { generateConfigJson, generateWranglerToml, generateUsersIndex, generateVersionJson, generateRedirects, generateRoutesJson, generateStorageFunction, generateViteConfig, generatePackageJson, generateTsConfig, generateStagingDomain, writeFile, ensureDir, } from '../lib/templates.js';
|
|
11
|
+
import { generateConfigJson, generateWranglerToml, generateUsersIndex, generateVersionJson, generateRedirects, generateRoutesJson, generateStorageFunction, generateViteConfig, generatePackageJson, generateTsConfig, generateTsConfigNode, generateStagingDomain, writeFile, ensureDir, } from '../lib/templates.js';
|
|
12
|
+
import { validateContentRepo, validateR2PublicUrl, validateRequired } from '../lib/validators.js';
|
|
12
13
|
export async function initCommand(options) {
|
|
13
14
|
log('');
|
|
14
15
|
log('🚀 Frelio CMS プロジェクトセットアップ');
|
|
@@ -31,6 +32,11 @@ export async function initCommand(options) {
|
|
|
31
32
|
// OAuth App 案内
|
|
32
33
|
log('🔑 GitHub OAuth App:');
|
|
33
34
|
if (!config.githubClientId) {
|
|
35
|
+
const isInteractive = process.stdin.isTTY === true;
|
|
36
|
+
if (!isInteractive) {
|
|
37
|
+
logError('非対話モードでは --client-id と --client-secret が必須です。');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
34
40
|
log(' ⚠ OAuth App は GitHub の Web UI で作成が必要です。');
|
|
35
41
|
log(' → https://github.com/settings/developers');
|
|
36
42
|
log(' → New OAuth App:');
|
|
@@ -43,13 +49,13 @@ export async function initCommand(options) {
|
|
|
43
49
|
type: 'text',
|
|
44
50
|
name: 'clientId',
|
|
45
51
|
message: 'OAuth Client ID を入力:',
|
|
46
|
-
validate: (v) => v
|
|
52
|
+
validate: (v) => validateRequired(v, 'Client ID'),
|
|
47
53
|
},
|
|
48
54
|
{
|
|
49
55
|
type: 'password',
|
|
50
56
|
name: 'clientSecret',
|
|
51
57
|
message: 'OAuth Client Secret を入力:',
|
|
52
|
-
validate: (v) => v
|
|
58
|
+
validate: (v) => validateRequired(v, 'Client Secret'),
|
|
53
59
|
},
|
|
54
60
|
]);
|
|
55
61
|
if (!oauthResponse.clientId) {
|
|
@@ -292,52 +298,71 @@ function checkPrerequisites(options) {
|
|
|
292
298
|
return { ok };
|
|
293
299
|
}
|
|
294
300
|
async function promptConfig(options) {
|
|
301
|
+
const isInteractive = process.stdin.isTTY === true;
|
|
302
|
+
// 必須オプションが揃っている場合 → プロンプトスキップ
|
|
303
|
+
if (options.contentRepo && options.clientId && options.clientSecret) {
|
|
304
|
+
return buildConfigFromOptions(options);
|
|
305
|
+
}
|
|
306
|
+
// 非 TTY で必須オプション不足 → エラー終了
|
|
307
|
+
if (!isInteractive) {
|
|
308
|
+
const missing = [];
|
|
309
|
+
if (!options.contentRepo)
|
|
310
|
+
missing.push('--content-repo');
|
|
311
|
+
if (!options.clientId)
|
|
312
|
+
missing.push('--client-id');
|
|
313
|
+
if (!options.clientSecret)
|
|
314
|
+
missing.push('--client-secret');
|
|
315
|
+
logError(`非対話モードでは以下のオプションが必須です: ${missing.join(', ')}`);
|
|
316
|
+
logError('使用例: frelio init --content-repo owner/repo --client-id xxx --client-secret yyy');
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
// 対話モード(渡されたオプションはスキップ)
|
|
295
320
|
const response = await prompts([
|
|
296
321
|
{
|
|
297
|
-
type: 'text',
|
|
322
|
+
type: options.contentRepo ? null : 'text',
|
|
298
323
|
name: 'contentRepo',
|
|
299
324
|
message: 'リポジトリ名 (owner/repo):',
|
|
300
|
-
validate: (v) => v
|
|
325
|
+
validate: (v) => validateContentRepo(v),
|
|
301
326
|
},
|
|
302
327
|
{
|
|
303
|
-
type: 'text',
|
|
328
|
+
type: options.siteTitle !== undefined ? null : 'text',
|
|
304
329
|
name: 'siteTitle',
|
|
305
330
|
message: 'サイトタイトル:',
|
|
306
331
|
initial: '',
|
|
307
332
|
},
|
|
308
333
|
{
|
|
309
|
-
type: 'text',
|
|
334
|
+
type: options.productionUrl !== undefined ? null : 'text',
|
|
310
335
|
name: 'productionUrl',
|
|
311
336
|
message: '本番 URL (optional):',
|
|
312
337
|
initial: '',
|
|
313
338
|
},
|
|
314
339
|
{
|
|
315
|
-
type: 'text',
|
|
340
|
+
type: options.stagingDomain !== undefined ? null : 'text',
|
|
316
341
|
name: 'stagingDomain',
|
|
317
342
|
message: 'ステージングのドメイン(推測困難なハッシュ付き推奨):',
|
|
318
343
|
initial: (_prev, values) => {
|
|
319
|
-
const repo = values.contentRepo?.split('/')[1] || 'site';
|
|
320
|
-
return generateStagingDomain(values.productionUrl || '', repo);
|
|
344
|
+
const repo = (options.contentRepo || values.contentRepo)?.split('/')[1] || 'site';
|
|
345
|
+
return generateStagingDomain((options.productionUrl || values.productionUrl) || '', repo);
|
|
321
346
|
},
|
|
322
347
|
},
|
|
323
348
|
{
|
|
324
|
-
type: 'text',
|
|
349
|
+
type: options.r2BucketName !== undefined ? null : 'text',
|
|
325
350
|
name: 'r2BucketName',
|
|
326
351
|
message: 'R2 バケット名:',
|
|
327
|
-
initial: (
|
|
328
|
-
const repo = values.contentRepo?.split('/')[1] || 'site';
|
|
352
|
+
initial: (_prev, values) => {
|
|
353
|
+
const repo = (options.contentRepo || values.contentRepo)?.split('/')[1] || 'site';
|
|
329
354
|
return `${repo}-files`;
|
|
330
355
|
},
|
|
331
356
|
},
|
|
332
357
|
{
|
|
333
|
-
type: 'text',
|
|
358
|
+
type: options.r2PublicUrl !== undefined ? null : 'text',
|
|
334
359
|
name: 'r2PublicUrl',
|
|
335
360
|
message: 'R2 公開 URL:',
|
|
336
361
|
initial: '',
|
|
337
|
-
validate: (v) => v
|
|
362
|
+
validate: (v) => validateR2PublicUrl(v),
|
|
338
363
|
},
|
|
339
364
|
{
|
|
340
|
-
type: 'text',
|
|
365
|
+
type: options.ownerUsername !== undefined ? null : 'text',
|
|
341
366
|
name: 'ownerUsername',
|
|
342
367
|
message: '管理者の GitHub ユーザー名:',
|
|
343
368
|
initial: () => {
|
|
@@ -350,22 +375,70 @@ async function promptConfig(options) {
|
|
|
350
375
|
},
|
|
351
376
|
},
|
|
352
377
|
], { onCancel: () => process.exit(0) });
|
|
353
|
-
|
|
378
|
+
const contentRepo = options.contentRepo || response.contentRepo;
|
|
379
|
+
if (!contentRepo)
|
|
354
380
|
return null;
|
|
355
|
-
const repoName =
|
|
356
|
-
const stagingDomain = response.stagingDomain
|
|
381
|
+
const repoName = contentRepo.split('/')[1];
|
|
382
|
+
const stagingDomain = options.stagingDomain ?? response.stagingDomain ?? '';
|
|
357
383
|
const previewUrl = stagingDomain ? `https://${stagingDomain}` : '';
|
|
384
|
+
const config = {
|
|
385
|
+
contentRepo,
|
|
386
|
+
githubClientId: options.clientId || '',
|
|
387
|
+
siteTitle: options.siteTitle ?? response.siteTitle ?? '',
|
|
388
|
+
productionUrl: options.productionUrl ?? response.productionUrl ?? '',
|
|
389
|
+
previewUrl,
|
|
390
|
+
r2BucketName: options.r2BucketName ?? response.r2BucketName ?? `${repoName}-files`,
|
|
391
|
+
r2PublicUrl: options.r2PublicUrl ?? response.r2PublicUrl ?? '',
|
|
392
|
+
pagesProjectName: repoName,
|
|
393
|
+
ownerUsername: options.ownerUsername ?? response.ownerUsername ?? '',
|
|
394
|
+
stagingDomain,
|
|
395
|
+
};
|
|
396
|
+
if (options.clientSecret) {
|
|
397
|
+
config.githubClientSecret = options.clientSecret;
|
|
398
|
+
}
|
|
399
|
+
return config;
|
|
400
|
+
}
|
|
401
|
+
function buildConfigFromOptions(options) {
|
|
402
|
+
// バリデーション
|
|
403
|
+
const errors = [];
|
|
404
|
+
const repoCheck = validateContentRepo(options.contentRepo);
|
|
405
|
+
if (repoCheck !== true)
|
|
406
|
+
errors.push(`--content-repo: ${repoCheck}`);
|
|
407
|
+
if (options.r2PublicUrl) {
|
|
408
|
+
const urlCheck = validateR2PublicUrl(options.r2PublicUrl);
|
|
409
|
+
if (urlCheck !== true)
|
|
410
|
+
errors.push(`--r2-public-url: ${urlCheck}`);
|
|
411
|
+
}
|
|
412
|
+
if (errors.length > 0) {
|
|
413
|
+
errors.forEach((e) => logError(e));
|
|
414
|
+
process.exit(1);
|
|
415
|
+
}
|
|
416
|
+
const repoName = options.contentRepo.split('/')[1];
|
|
417
|
+
// デフォルト算出
|
|
418
|
+
const stagingDomain = options.stagingDomain ??
|
|
419
|
+
generateStagingDomain(options.productionUrl || '', repoName);
|
|
420
|
+
const previewUrl = stagingDomain ? `https://${stagingDomain}` : '';
|
|
421
|
+
let ownerUsername = options.ownerUsername ?? '';
|
|
422
|
+
if (!ownerUsername) {
|
|
423
|
+
try {
|
|
424
|
+
ownerUsername = exec('gh api user -q .login', { silent: true });
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
ownerUsername = '';
|
|
428
|
+
}
|
|
429
|
+
}
|
|
358
430
|
return {
|
|
359
|
-
contentRepo:
|
|
360
|
-
githubClientId: '',
|
|
361
|
-
siteTitle:
|
|
362
|
-
productionUrl:
|
|
431
|
+
contentRepo: options.contentRepo,
|
|
432
|
+
githubClientId: options.clientId || '',
|
|
433
|
+
siteTitle: options.siteTitle || '',
|
|
434
|
+
productionUrl: options.productionUrl || '',
|
|
363
435
|
previewUrl,
|
|
364
|
-
r2BucketName:
|
|
365
|
-
r2PublicUrl:
|
|
436
|
+
r2BucketName: options.r2BucketName ?? `${repoName}-files`,
|
|
437
|
+
r2PublicUrl: options.r2PublicUrl || '',
|
|
366
438
|
pagesProjectName: repoName,
|
|
367
|
-
ownerUsername
|
|
439
|
+
ownerUsername,
|
|
368
440
|
stagingDomain,
|
|
441
|
+
githubClientSecret: options.clientSecret,
|
|
369
442
|
};
|
|
370
443
|
}
|
|
371
444
|
function createContentStructure(projectDir, config) {
|
|
@@ -376,10 +449,12 @@ function createContentStructure(projectDir, config) {
|
|
|
376
449
|
'frelio-data/site/contents/private',
|
|
377
450
|
'frelio-data/site/templates/assets/scss',
|
|
378
451
|
'frelio-data/site/templates/assets/ts',
|
|
452
|
+
'frelio-data/site/templates/assets/entries',
|
|
379
453
|
'frelio-data/site/data/data-json',
|
|
380
454
|
'frelio-data/admin/metadata',
|
|
381
455
|
'frelio-data/admin/users',
|
|
382
456
|
'frelio-data/admin/recipes',
|
|
457
|
+
'scripts',
|
|
383
458
|
'public',
|
|
384
459
|
];
|
|
385
460
|
for (const dir of dirs) {
|
|
@@ -389,9 +464,10 @@ function createContentStructure(projectDir, config) {
|
|
|
389
464
|
writeFile(path.join(projectDir, 'frelio-data/admin/users/_index.json'), generateUsersIndex(config));
|
|
390
465
|
// version.json
|
|
391
466
|
writeFile(path.join(projectDir, 'version.json'), generateVersionJson());
|
|
392
|
-
// vite.config.ts + tsconfig.json + package.json
|
|
467
|
+
// vite.config.ts + tsconfig.json + tsconfig.node.json + package.json
|
|
393
468
|
writeFile(path.join(projectDir, 'vite.config.ts'), generateViteConfig());
|
|
394
469
|
writeFile(path.join(projectDir, 'tsconfig.json'), generateTsConfig());
|
|
470
|
+
writeFile(path.join(projectDir, 'tsconfig.node.json'), generateTsConfigNode());
|
|
395
471
|
writeFile(path.join(projectDir, 'package.json'), generatePackageJson(config));
|
|
396
472
|
// R2 ファイル配信用 Pages Function (/storage/*)
|
|
397
473
|
writeFile(path.join(projectDir, 'functions', 'storage', '[[path]].ts'), generateStorageFunction());
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,15 @@ program
|
|
|
13
13
|
.description('Initialize a new Frelio CMS project (creates repo, deploys admin, sets up R2)')
|
|
14
14
|
.option('--skip-github', 'Skip GitHub repository creation')
|
|
15
15
|
.option('--skip-cloudflare', 'Skip Cloudflare setup (R2, Pages)')
|
|
16
|
+
.option('--content-repo <repo>', 'Repository name (owner/repo)')
|
|
17
|
+
.option('--site-title <title>', 'Site title')
|
|
18
|
+
.option('--production-url <url>', 'Production URL')
|
|
19
|
+
.option('--staging-domain <domain>', 'Staging domain')
|
|
20
|
+
.option('--r2-bucket-name <name>', 'R2 bucket name')
|
|
21
|
+
.option('--r2-public-url <url>', 'R2 public URL')
|
|
22
|
+
.option('--owner-username <user>', 'Admin GitHub username')
|
|
23
|
+
.option('--client-id <id>', 'GitHub OAuth Client ID')
|
|
24
|
+
.option('--client-secret <secret>', 'GitHub OAuth Client Secret')
|
|
16
25
|
.action(initCommand);
|
|
17
26
|
program
|
|
18
27
|
.command('update')
|
|
@@ -24,5 +33,7 @@ program
|
|
|
24
33
|
.description('Add a staging environment (preview branch + Cloudflare Pages project)')
|
|
25
34
|
.option('--name <name>', 'Staging name (creates staging-{name} branch)')
|
|
26
35
|
.option('--skip-cloudflare', 'Skip Cloudflare Pages project creation')
|
|
36
|
+
.option('--pages-project <name>', 'Cloudflare Pages project name for staging')
|
|
37
|
+
.option('--domain <domain>', 'Custom domain for staging')
|
|
27
38
|
.action(addStagingCommand);
|
|
28
39
|
program.parse();
|