@c-time/frelio-cli 1.3.13 → 1.4.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/dist/commands/add-staging.d.ts +2 -3
- package/dist/commands/add-staging.js +38 -184
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +171 -368
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +11 -67
- package/dist/core/bundle.d.ts +14 -0
- package/dist/core/bundle.js +122 -0
- package/dist/core/cloudflare.d.ts +26 -0
- package/dist/core/cloudflare.js +60 -0
- package/dist/core/config.d.ts +26 -0
- package/dist/core/config.js +120 -0
- package/dist/core/content-structure.d.ts +19 -0
- package/dist/core/content-structure.js +116 -0
- package/dist/core/file-generators.d.ts +28 -0
- package/dist/core/file-generators.js +93 -0
- package/dist/core/git-operations.d.ts +15 -0
- package/dist/core/git-operations.js +78 -0
- package/dist/core/github.d.ts +16 -0
- package/dist/core/github.js +43 -0
- package/dist/core/index.d.ts +23 -0
- package/dist/core/index.js +30 -0
- package/dist/core/prerequisites.d.ts +22 -0
- package/dist/core/prerequisites.js +107 -0
- package/dist/core/status.d.ts +18 -0
- package/dist/core/status.js +122 -0
- package/dist/core/terraform.d.ts +7 -0
- package/dist/core/terraform.js +47 -0
- package/dist/core/types.d.ts +48 -0
- package/dist/core/types.js +21 -0
- package/dist/core/workflows.d.ts +11 -0
- package/dist/core/workflows.js +180 -0
- package/dist/index.js +2 -4
- package/dist/lib/templates.d.ts +9 -1
- package/dist/lib/templates.js +351 -27
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -1,27 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* frelio init - 新規プロジェクトの対話式セットアップ
|
|
3
|
+
*
|
|
4
|
+
* core/* の関数を順番に呼ぶ薄いオーケストレーター。
|
|
5
|
+
* UI 層(プロンプト、ログ出力、process.exit)はここに閉じる。
|
|
3
6
|
*/
|
|
4
7
|
import prompts from 'prompts';
|
|
5
8
|
import fs from 'node:fs';
|
|
6
9
|
import path from 'node:path';
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import { getLatestRelease, downloadTarball, extractNpmTarball } from '../lib/npm-registry.js';
|
|
10
|
-
import { generateInitialContent } from '../lib/initial-content.js';
|
|
11
|
-
import { generateConfigJson, generateWranglerToml, generateUsersIndex, generateVersionJson, generateRedirects, generateRoutesJson, generateStorageFunction, generateViteConfig, generatePackageJson, generateTsConfig, generateTsConfigNode, generateStagingDomain, writeFile, ensureDir, } from '../lib/templates.js';
|
|
10
|
+
import { log, logStep, logSuccess, logError } from '../lib/shell.js';
|
|
11
|
+
import { generateStagingDomain } from '../lib/templates.js';
|
|
12
12
|
import { validateContentRepo, validateR2PublicUrl, validateRequired } from '../lib/validators.js';
|
|
13
|
+
// core functions
|
|
14
|
+
import { checkPrerequisitesFor } from '../core/prerequisites.js';
|
|
15
|
+
import { buildConfig } from '../core/config.js';
|
|
16
|
+
import { createRepo, cloneRepo, getAuthenticatedUser } from '../core/github.js';
|
|
17
|
+
import { setupCloudflareResources } from '../core/cloudflare.js';
|
|
18
|
+
import { createFullContentStructure } from '../core/content-structure.js';
|
|
19
|
+
import { regenerateAllConfigFiles } from '../core/file-generators.js';
|
|
20
|
+
import { generateWorkflows } from '../core/workflows.js';
|
|
21
|
+
import { generateTerraformFiles } from '../core/terraform.js';
|
|
22
|
+
import { installBundle } from '../core/bundle.js';
|
|
23
|
+
import { initialCommitAndBranches } from '../core/git-operations.js';
|
|
13
24
|
export async function initCommand(options) {
|
|
14
25
|
log('');
|
|
15
26
|
log('🚀 Frelio CMS プロジェクトセットアップ');
|
|
16
27
|
log('');
|
|
17
|
-
// 前提チェック
|
|
28
|
+
// ── 前提チェック ──
|
|
18
29
|
log('🔍 前提チェック...');
|
|
19
|
-
const
|
|
20
|
-
if (!
|
|
30
|
+
const targets = ['git'];
|
|
31
|
+
if (!options.skipGithub)
|
|
32
|
+
targets.push('github');
|
|
33
|
+
if (!options.skipCloudflare && !options.terraform)
|
|
34
|
+
targets.push('cloudflare');
|
|
35
|
+
const prereqs = checkPrerequisitesFor(targets);
|
|
36
|
+
if (prereqs.success) {
|
|
37
|
+
for (const [name, r] of Object.entries(prereqs.data.results)) {
|
|
38
|
+
if (r.success)
|
|
39
|
+
logSuccess(name);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
logError(prereqs.error);
|
|
21
44
|
process.exit(1);
|
|
22
45
|
}
|
|
23
46
|
log('');
|
|
24
|
-
// 対話式プロンプト
|
|
47
|
+
// ── 対話式プロンプト ──
|
|
25
48
|
log('📝 プロジェクト設定:');
|
|
26
49
|
const config = await promptConfig(options);
|
|
27
50
|
if (!config) {
|
|
@@ -29,7 +52,7 @@ export async function initCommand(options) {
|
|
|
29
52
|
process.exit(0);
|
|
30
53
|
}
|
|
31
54
|
log('');
|
|
32
|
-
// OAuth App 案内
|
|
55
|
+
// ── OAuth App 案内 ──
|
|
33
56
|
log('🔑 GitHub OAuth App:');
|
|
34
57
|
if (!config.githubClientId) {
|
|
35
58
|
const isInteractive = process.stdin.isTTY === true;
|
|
@@ -40,9 +63,9 @@ export async function initCommand(options) {
|
|
|
40
63
|
log(' ⚠ OAuth App は GitHub の Web UI で作成が必要です。');
|
|
41
64
|
log(' → https://github.com/settings/developers');
|
|
42
65
|
log(' → New OAuth App:');
|
|
43
|
-
log(` - Application name: ${config.siteTitle || config.
|
|
44
|
-
log(` - Homepage URL: https://${config.
|
|
45
|
-
log(` - Callback URL: https://${config.
|
|
66
|
+
log(` - Application name: ${config.siteTitle || config.adminPagesProjectName} CMS`);
|
|
67
|
+
log(` - Homepage URL: https://${config.adminPagesProjectName}.pages.dev`);
|
|
68
|
+
log(` - Callback URL: https://${config.adminPagesProjectName}.pages.dev/api/auth/callback`);
|
|
46
69
|
log('');
|
|
47
70
|
const oauthResponse = await prompts([
|
|
48
71
|
{
|
|
@@ -67,26 +90,20 @@ export async function initCommand(options) {
|
|
|
67
90
|
oauthResponse.clientSecret;
|
|
68
91
|
}
|
|
69
92
|
log('');
|
|
70
|
-
// セットアップ実行
|
|
93
|
+
// ── セットアップ実行 ──
|
|
71
94
|
const totalSteps = getTotalSteps(options);
|
|
72
95
|
let step = 0;
|
|
73
96
|
// GitHub リポジトリ作成
|
|
74
97
|
if (!options.skipGithub) {
|
|
75
98
|
step++;
|
|
76
99
|
logStep(step, totalSteps, 'GitHub リポジトリ作成...');
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
logSuccess('リポジトリ作成完了');
|
|
100
|
+
const repoResult = createRepo(config.contentRepo);
|
|
101
|
+
if (repoResult.success) {
|
|
102
|
+
logSuccess(repoResult.data.alreadyExisted ? 'リポジトリは既に存在します' : 'リポジトリ作成完了');
|
|
80
103
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
logSuccess('リポジトリは既に存在します');
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
logError(`リポジトリ作成失敗: ${msg}`);
|
|
88
|
-
process.exit(1);
|
|
89
|
-
}
|
|
104
|
+
else {
|
|
105
|
+
logError(repoResult.error);
|
|
106
|
+
process.exit(1);
|
|
90
107
|
}
|
|
91
108
|
}
|
|
92
109
|
// 作業ディレクトリ作成
|
|
@@ -95,18 +112,13 @@ export async function initCommand(options) {
|
|
|
95
112
|
if (!options.skipGithub) {
|
|
96
113
|
step++;
|
|
97
114
|
logStep(step, totalSteps, 'リポジトリをクローン...');
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
logSuccess('クローン完了');
|
|
115
|
+
const cloneResult = cloneRepo(config.contentRepo, projectDir);
|
|
116
|
+
if (cloneResult.success) {
|
|
117
|
+
logSuccess(cloneResult.data.alreadyExisted ? 'ディレクトリは既に存在します' : 'クローン完了');
|
|
101
118
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
logError(`クローン失敗: ${error.message}`);
|
|
108
|
-
process.exit(1);
|
|
109
|
-
}
|
|
119
|
+
else {
|
|
120
|
+
logError(cloneResult.error);
|
|
121
|
+
process.exit(1);
|
|
110
122
|
}
|
|
111
123
|
}
|
|
112
124
|
else {
|
|
@@ -117,101 +129,98 @@ export async function initCommand(options) {
|
|
|
117
129
|
// コンテンツリポジトリ初期構造
|
|
118
130
|
step++;
|
|
119
131
|
logStep(step, totalSteps, 'コンテンツリポジトリ初期構造作成...');
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
132
|
+
const structResult = createFullContentStructure(projectDir, config);
|
|
133
|
+
if (structResult.success) {
|
|
134
|
+
logSuccess('初期構造作成完了(デモサイトテンプレート付き)');
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
logError(structResult.error);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
123
140
|
// GitHub Actions ワークフロー
|
|
124
141
|
step++;
|
|
125
142
|
logStep(step, totalSteps, 'GitHub Actions ワークフロー配置...');
|
|
126
|
-
|
|
127
|
-
|
|
143
|
+
const workflowResult = generateWorkflows(projectDir, config);
|
|
144
|
+
if (workflowResult.success) {
|
|
145
|
+
logSuccess('ワークフロー配置完了');
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
logError(workflowResult.error);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
128
151
|
// CMS Admin バンドル展開
|
|
129
152
|
step++;
|
|
130
153
|
logStep(step, totalSteps, 'CMS Admin バンドルをダウンロード・展開...');
|
|
131
|
-
|
|
132
|
-
|
|
154
|
+
const bundleResult = await installBundle(projectDir);
|
|
155
|
+
if (bundleResult.success) {
|
|
133
156
|
logSuccess('バンドル展開完了');
|
|
134
157
|
}
|
|
135
|
-
|
|
136
|
-
logError(
|
|
158
|
+
else {
|
|
159
|
+
logError(bundleResult.error);
|
|
137
160
|
log(' ℹ リリースが公開されていない場合は、手動でバンドルを展開してください。');
|
|
138
161
|
log(' scripts/build-distributable.sh を実行してから dist-release/ の内容をコピーします。');
|
|
139
162
|
}
|
|
140
163
|
// 設定ファイル生成
|
|
141
164
|
step++;
|
|
142
165
|
logStep(step, totalSteps, '設定ファイル生成...');
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
166
|
+
const configResult = regenerateAllConfigFiles(projectDir, config);
|
|
167
|
+
if (configResult.success) {
|
|
168
|
+
logSuccess('設定ファイル生成完了');
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
logError(configResult.error);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
// Cloudflare セットアップ or Terraform ファイル生成
|
|
175
|
+
if (options.terraform) {
|
|
152
176
|
step++;
|
|
153
|
-
logStep(step, totalSteps, '
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
logSuccess(
|
|
157
|
-
}
|
|
158
|
-
catch (error) {
|
|
159
|
-
const msg = error.message;
|
|
160
|
-
if (msg.includes('already exists')) {
|
|
161
|
-
logSuccess(`R2 バケット "${config.r2BucketName}" は既に存在します`);
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
logError(`R2 バケット作成失敗: ${msg}`);
|
|
165
|
-
}
|
|
177
|
+
logStep(step, totalSteps, 'Terraform ファイル生成...');
|
|
178
|
+
const tfResult = generateTerraformFiles(projectDir, config);
|
|
179
|
+
if (tfResult.success) {
|
|
180
|
+
logSuccess('Terraform ファイル生成完了(terraform/)');
|
|
166
181
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
silent: true,
|
|
170
|
-
});
|
|
171
|
-
logSuccess(`Pages プロジェクト "${config.pagesProjectName}" 作成完了`);
|
|
182
|
+
else {
|
|
183
|
+
logError(tfResult.error);
|
|
172
184
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
185
|
+
}
|
|
186
|
+
else if (!options.skipCloudflare) {
|
|
187
|
+
step++;
|
|
188
|
+
logStep(step, totalSteps, 'Cloudflare セットアップ...');
|
|
189
|
+
const secret = config.githubClientSecret;
|
|
190
|
+
const cfResult = setupCloudflareResources(config, secret);
|
|
191
|
+
if (cfResult.success) {
|
|
192
|
+
const { r2Bucket, contentPages, adminPages, secrets } = cfResult.data;
|
|
193
|
+
if (r2Bucket.success) {
|
|
194
|
+
logSuccess(r2Bucket.alreadyExisted
|
|
195
|
+
? `R2 バケット "${config.r2BucketName}" は既に存在します`
|
|
196
|
+
: `R2 バケット "${config.r2BucketName}" 作成完了`);
|
|
177
197
|
}
|
|
178
198
|
else {
|
|
179
|
-
logError(
|
|
199
|
+
logError(r2Bucket.error);
|
|
180
200
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
exec(`wrangler pages project create ${stagingProjectName} --production-branch staging`, {
|
|
186
|
-
silent: true,
|
|
187
|
-
});
|
|
188
|
-
logSuccess(`Pages プロジェクト "${stagingProjectName}"(ステージング)作成完了`);
|
|
189
|
-
}
|
|
190
|
-
catch (error) {
|
|
191
|
-
const msg = error.message;
|
|
192
|
-
if (msg.includes('already exists') || msg.includes('A project with this name already exists')) {
|
|
193
|
-
logSuccess(`Pages プロジェクト "${stagingProjectName}" は既に存在します`);
|
|
201
|
+
if (contentPages.success) {
|
|
202
|
+
logSuccess(contentPages.alreadyExisted
|
|
203
|
+
? `Pages プロジェクト "${config.pagesProjectName}" は既に存在します`
|
|
204
|
+
: `Pages プロジェクト "${config.pagesProjectName}"(コンテンツ配信)作成完了`);
|
|
194
205
|
}
|
|
195
206
|
else {
|
|
196
|
-
logError(
|
|
207
|
+
logError(contentPages.error);
|
|
197
208
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
try {
|
|
203
|
-
exec(`echo "${secret}" | wrangler pages secret put GITHUB_CLIENT_SECRET --project-name ${config.pagesProjectName}`, { silent: true });
|
|
204
|
-
logSuccess('GITHUB_CLIENT_SECRET 設定完了');
|
|
209
|
+
if (adminPages.success) {
|
|
210
|
+
logSuccess(adminPages.alreadyExisted
|
|
211
|
+
? `Pages プロジェクト "${config.adminPagesProjectName}" は既に存在します`
|
|
212
|
+
: `Pages プロジェクト "${config.adminPagesProjectName}"(管理画面)作成完了`);
|
|
205
213
|
}
|
|
206
|
-
|
|
207
|
-
logError(
|
|
208
|
-
}
|
|
209
|
-
try {
|
|
210
|
-
exec(`echo "${config.githubClientId}" | wrangler pages secret put GITHUB_CLIENT_ID --project-name ${config.pagesProjectName}`, { silent: true });
|
|
211
|
-
logSuccess('GITHUB_CLIENT_ID 設定完了');
|
|
214
|
+
else {
|
|
215
|
+
logError(adminPages.error);
|
|
212
216
|
}
|
|
213
|
-
|
|
214
|
-
|
|
217
|
+
for (const s of secrets) {
|
|
218
|
+
if (s.success) {
|
|
219
|
+
logSuccess('シークレット設定完了');
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
logError(s.error);
|
|
223
|
+
}
|
|
215
224
|
}
|
|
216
225
|
}
|
|
217
226
|
}
|
|
@@ -219,84 +228,47 @@ export async function initCommand(options) {
|
|
|
219
228
|
if (!options.skipGithub) {
|
|
220
229
|
step++;
|
|
221
230
|
logStep(step, totalSteps, '初回コミット & ブランチ作成...');
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
exec('git commit -m "Initial Frelio CMS setup"', { cwd: projectDir, silent: true });
|
|
225
|
-
// develop, staging ブランチ作成
|
|
226
|
-
exec('git branch develop', { cwd: projectDir, silent: true });
|
|
227
|
-
exec('git branch staging', { cwd: projectDir, silent: true });
|
|
228
|
-
// プッシュ
|
|
229
|
-
exec('git push -u origin main', { cwd: projectDir, silent: true });
|
|
230
|
-
exec('git push -u origin develop', { cwd: projectDir, silent: true });
|
|
231
|
-
exec('git push -u origin staging', { cwd: projectDir, silent: true });
|
|
232
|
-
// デフォルトブランチを develop に変更
|
|
233
|
-
exec(`gh repo edit ${config.contentRepo} --default-branch develop`, { silent: true });
|
|
231
|
+
const gitResult = initialCommitAndBranches(projectDir, config.contentRepo);
|
|
232
|
+
if (gitResult.success) {
|
|
234
233
|
logSuccess('ブランチ作成・プッシュ完了');
|
|
235
234
|
}
|
|
236
|
-
|
|
237
|
-
logError(
|
|
235
|
+
else {
|
|
236
|
+
logError(gitResult.error);
|
|
238
237
|
}
|
|
239
238
|
}
|
|
240
|
-
// 完了
|
|
239
|
+
// ── 完了 ──
|
|
241
240
|
log('');
|
|
242
241
|
log('✅ セットアップ完了!');
|
|
243
242
|
log('');
|
|
244
|
-
log(` 管理画面: https://${config.
|
|
243
|
+
log(` 管理画面: https://${config.adminPagesProjectName}.pages.dev/admin/`);
|
|
244
|
+
log(` プレビュー: https://staging.${config.pagesProjectName}.pages.dev`);
|
|
245
245
|
log('');
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
246
|
+
if (options.terraform) {
|
|
247
|
+
log(' Terraform でインフラを構築:');
|
|
248
|
+
log(` 1. cd ${repoName}/terraform`);
|
|
249
|
+
log(' 2. cp terraform.tfvars.example terraform.tfvars && vim terraform.tfvars');
|
|
250
|
+
log(' 3. terraform init');
|
|
251
|
+
log(' 4. terraform plan');
|
|
252
|
+
log(' 5. terraform apply');
|
|
253
|
+
log('');
|
|
254
|
+
log(' 残りの手動作業:');
|
|
255
|
+
log(` 1. コンテンツ配信 Pages(${config.pagesProjectName})にリポジトリを接続`);
|
|
256
|
+
log(` 2. 管理画面 Pages(${config.adminPagesProjectName})にリポジトリを接続(admin ブランチ)`);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
log(' 残りの手動作業:');
|
|
260
|
+
log(` 1. コンテンツ配信 Pages(${config.pagesProjectName})にリポジトリを接続`);
|
|
261
|
+
log(` 2. 管理画面 Pages(${config.adminPagesProjectName})にリポジトリを接続(admin ブランチ)`);
|
|
262
|
+
}
|
|
249
263
|
if (config.stagingDomain) {
|
|
250
|
-
log(` 3.
|
|
264
|
+
log(` 3. プレビュー用カスタムドメインを設定: ${config.stagingDomain}`);
|
|
251
265
|
}
|
|
252
266
|
log(` ${config.stagingDomain ? '4' : '3'}. ステージングのアクセス制限を設定(Cloudflare Access 推奨)`);
|
|
253
267
|
log('');
|
|
254
268
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (commandExists('gh')) {
|
|
259
|
-
logSuccess('gh CLI (GitHub)');
|
|
260
|
-
try {
|
|
261
|
-
exec('gh auth status', { silent: true });
|
|
262
|
-
logSuccess('gh auth status (ログイン済み)');
|
|
263
|
-
}
|
|
264
|
-
catch {
|
|
265
|
-
logError('gh にログインしていません。`gh auth login` を実行してください。');
|
|
266
|
-
ok = false;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
logError('gh CLI が見つかりません。https://cli.github.com/ からインストールしてください。');
|
|
271
|
-
ok = false;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
if (!options.skipCloudflare) {
|
|
275
|
-
if (commandExists('wrangler')) {
|
|
276
|
-
logSuccess('wrangler CLI (Cloudflare)');
|
|
277
|
-
try {
|
|
278
|
-
exec('wrangler whoami', { silent: true });
|
|
279
|
-
logSuccess('wrangler whoami (ログイン済み)');
|
|
280
|
-
}
|
|
281
|
-
catch {
|
|
282
|
-
logError('wrangler にログインしていません。`wrangler login` を実行してください。');
|
|
283
|
-
ok = false;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
logError('wrangler CLI が見つかりません。`npm i -g wrangler` でインストールしてください。');
|
|
288
|
-
ok = false;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
if (commandExists('git')) {
|
|
292
|
-
logSuccess('git');
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
logError('git が見つかりません。');
|
|
296
|
-
ok = false;
|
|
297
|
-
}
|
|
298
|
-
return { ok };
|
|
299
|
-
}
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
// UI helpers (prompts — commands 層に閉じる)
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
300
272
|
async function promptConfig(options) {
|
|
301
273
|
const isInteractive = process.stdin.isTTY === true;
|
|
302
274
|
// 必須オプションが揃っている場合 → プロンプトスキップ
|
|
@@ -316,7 +288,7 @@ async function promptConfig(options) {
|
|
|
316
288
|
logError('使用例: frelio init --content-repo owner/repo --client-id xxx --client-secret yyy');
|
|
317
289
|
process.exit(1);
|
|
318
290
|
}
|
|
319
|
-
//
|
|
291
|
+
// 対話モード
|
|
320
292
|
const response = await prompts([
|
|
321
293
|
{
|
|
322
294
|
type: options.contentRepo ? null : 'text',
|
|
@@ -366,12 +338,8 @@ async function promptConfig(options) {
|
|
|
366
338
|
name: 'ownerUsername',
|
|
367
339
|
message: '管理者の GitHub ユーザー名:',
|
|
368
340
|
initial: () => {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
catch {
|
|
373
|
-
return '';
|
|
374
|
-
}
|
|
341
|
+
const userResult = getAuthenticatedUser();
|
|
342
|
+
return userResult.success ? userResult.data.username : '';
|
|
375
343
|
},
|
|
376
344
|
},
|
|
377
345
|
], { onCancel: () => process.exit(0) });
|
|
@@ -390,6 +358,7 @@ async function promptConfig(options) {
|
|
|
390
358
|
r2BucketName: options.r2BucketName ?? response.r2BucketName ?? `${repoName}-files`,
|
|
391
359
|
r2PublicUrl: options.r2PublicUrl ?? response.r2PublicUrl ?? '',
|
|
392
360
|
pagesProjectName: repoName,
|
|
361
|
+
adminPagesProjectName: `${repoName}-admin`,
|
|
393
362
|
ownerUsername: options.ownerUsername ?? response.ownerUsername ?? '',
|
|
394
363
|
stagingDomain,
|
|
395
364
|
};
|
|
@@ -399,206 +368,40 @@ async function promptConfig(options) {
|
|
|
399
368
|
return config;
|
|
400
369
|
}
|
|
401
370
|
function buildConfigFromOptions(options) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
if (
|
|
413
|
-
|
|
371
|
+
const result = buildConfig({
|
|
372
|
+
contentRepo: options.contentRepo,
|
|
373
|
+
githubClientId: options.clientId,
|
|
374
|
+
siteTitle: options.siteTitle,
|
|
375
|
+
productionUrl: options.productionUrl,
|
|
376
|
+
stagingDomain: options.stagingDomain,
|
|
377
|
+
r2BucketName: options.r2BucketName,
|
|
378
|
+
r2PublicUrl: options.r2PublicUrl,
|
|
379
|
+
ownerUsername: options.ownerUsername,
|
|
380
|
+
});
|
|
381
|
+
if (!result.success) {
|
|
382
|
+
logError(result.error);
|
|
414
383
|
process.exit(1);
|
|
415
384
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
if (!ownerUsername) {
|
|
423
|
-
try {
|
|
424
|
-
ownerUsername = exec('gh api user -q .login', { silent: true });
|
|
425
|
-
}
|
|
426
|
-
catch {
|
|
427
|
-
ownerUsername = '';
|
|
385
|
+
// ownerUsername を gh api から補完
|
|
386
|
+
const config = result.data.config;
|
|
387
|
+
if (!config.ownerUsername) {
|
|
388
|
+
const userResult = getAuthenticatedUser();
|
|
389
|
+
if (userResult.success) {
|
|
390
|
+
config.ownerUsername = userResult.data.username;
|
|
428
391
|
}
|
|
429
392
|
}
|
|
430
393
|
return {
|
|
431
|
-
|
|
432
|
-
githubClientId: options.clientId || '',
|
|
433
|
-
siteTitle: options.siteTitle || '',
|
|
434
|
-
productionUrl: options.productionUrl || '',
|
|
435
|
-
previewUrl,
|
|
436
|
-
r2BucketName: options.r2BucketName ?? `${repoName}-files`,
|
|
437
|
-
r2PublicUrl: options.r2PublicUrl || '',
|
|
438
|
-
pagesProjectName: repoName,
|
|
439
|
-
ownerUsername,
|
|
440
|
-
stagingDomain,
|
|
394
|
+
...config,
|
|
441
395
|
githubClientSecret: options.clientSecret,
|
|
442
396
|
};
|
|
443
397
|
}
|
|
444
|
-
function createContentStructure(projectDir, config) {
|
|
445
|
-
// frelio-data ディレクトリ構造
|
|
446
|
-
const dirs = [
|
|
447
|
-
'frelio-data/site/content_types',
|
|
448
|
-
'frelio-data/site/contents/published',
|
|
449
|
-
'frelio-data/site/contents/private',
|
|
450
|
-
'frelio-data/site/templates/assets/scss',
|
|
451
|
-
'frelio-data/site/templates/assets/ts',
|
|
452
|
-
'frelio-data/site/templates/assets/entries',
|
|
453
|
-
'frelio-data/site/data/data-json',
|
|
454
|
-
'frelio-data/admin/metadata',
|
|
455
|
-
'frelio-data/admin/users',
|
|
456
|
-
'frelio-data/admin/recipes',
|
|
457
|
-
'scripts',
|
|
458
|
-
'public',
|
|
459
|
-
];
|
|
460
|
-
for (const dir of dirs) {
|
|
461
|
-
ensureDir(path.join(projectDir, dir));
|
|
462
|
-
}
|
|
463
|
-
// メタデータファイル
|
|
464
|
-
writeFile(path.join(projectDir, 'frelio-data/admin/users/_index.json'), generateUsersIndex(config));
|
|
465
|
-
// version.json
|
|
466
|
-
writeFile(path.join(projectDir, 'version.json'), generateVersionJson());
|
|
467
|
-
// vite.config.ts + tsconfig.json + tsconfig.node.json + package.json
|
|
468
|
-
writeFile(path.join(projectDir, 'vite.config.ts'), generateViteConfig());
|
|
469
|
-
writeFile(path.join(projectDir, 'tsconfig.json'), generateTsConfig());
|
|
470
|
-
writeFile(path.join(projectDir, 'tsconfig.node.json'), generateTsConfigNode());
|
|
471
|
-
writeFile(path.join(projectDir, 'package.json'), generatePackageJson(config));
|
|
472
|
-
// R2 ファイル配信用 Pages Function (/storage/*)
|
|
473
|
-
writeFile(path.join(projectDir, 'functions', 'storage', '[[path]].ts'), generateStorageFunction());
|
|
474
|
-
// .gitignore
|
|
475
|
-
const gitignore = [
|
|
476
|
-
'node_modules/',
|
|
477
|
-
'.wrangler/',
|
|
478
|
-
'.dev.vars',
|
|
479
|
-
'',
|
|
480
|
-
].join('\n');
|
|
481
|
-
writeFile(path.join(projectDir, '.gitignore'), gitignore);
|
|
482
|
-
}
|
|
483
|
-
function copyWorkflows(projectDir) {
|
|
484
|
-
const workflowsDir = path.join(projectDir, '.github', 'workflows');
|
|
485
|
-
fs.mkdirSync(workflowsDir, { recursive: true });
|
|
486
|
-
// build-staging.yml
|
|
487
|
-
writeFile(path.join(workflowsDir, 'build-staging.yml'), `name: Build Staging
|
|
488
|
-
on:
|
|
489
|
-
push:
|
|
490
|
-
branches: [staging, 'staging-*']
|
|
491
|
-
|
|
492
|
-
permissions:
|
|
493
|
-
contents: write
|
|
494
|
-
|
|
495
|
-
jobs:
|
|
496
|
-
build:
|
|
497
|
-
runs-on: ubuntu-latest
|
|
498
|
-
steps:
|
|
499
|
-
- uses: actions/checkout@v4
|
|
500
|
-
- uses: actions/setup-node@v4
|
|
501
|
-
with:
|
|
502
|
-
node-version: 20
|
|
503
|
-
- run: echo "TODO: Add SSG build steps"
|
|
504
|
-
`);
|
|
505
|
-
// promote-production.yml
|
|
506
|
-
writeFile(path.join(workflowsDir, 'promote-production.yml'), `name: Promote to Production
|
|
507
|
-
on:
|
|
508
|
-
push:
|
|
509
|
-
branches: [main]
|
|
510
|
-
|
|
511
|
-
permissions:
|
|
512
|
-
contents: write
|
|
513
|
-
|
|
514
|
-
jobs:
|
|
515
|
-
tag:
|
|
516
|
-
runs-on: ubuntu-latest
|
|
517
|
-
steps:
|
|
518
|
-
- uses: actions/checkout@v4
|
|
519
|
-
with:
|
|
520
|
-
fetch-depth: 0
|
|
521
|
-
- name: Create deploy tag
|
|
522
|
-
run: |
|
|
523
|
-
VERSION=$(cat version.json | node -e "process.stdin.on('data',d=>console.log(JSON.parse(d).version))")
|
|
524
|
-
EXISTING=\$(git tag -l "d\${VERSION}.*" | wc -l)
|
|
525
|
-
NEXT=\$((EXISTING + 1))
|
|
526
|
-
TAG="d\${VERSION}.\${NEXT}"
|
|
527
|
-
git tag "\$TAG"
|
|
528
|
-
git push origin "\$TAG"
|
|
529
|
-
`);
|
|
530
|
-
// direct-deploy.yml
|
|
531
|
-
writeFile(path.join(workflowsDir, 'direct-deploy.yml'), `name: Direct Deploy
|
|
532
|
-
on:
|
|
533
|
-
workflow_dispatch:
|
|
534
|
-
|
|
535
|
-
permissions:
|
|
536
|
-
contents: write
|
|
537
|
-
|
|
538
|
-
jobs:
|
|
539
|
-
deploy:
|
|
540
|
-
runs-on: ubuntu-latest
|
|
541
|
-
steps:
|
|
542
|
-
- uses: actions/checkout@v4
|
|
543
|
-
with:
|
|
544
|
-
fetch-depth: 0
|
|
545
|
-
- name: Merge develop to staging
|
|
546
|
-
run: |
|
|
547
|
-
git config user.name "github-actions[bot]"
|
|
548
|
-
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
549
|
-
git checkout staging
|
|
550
|
-
git merge origin/develop --no-edit
|
|
551
|
-
git push origin staging
|
|
552
|
-
- name: Fast-forward main
|
|
553
|
-
run: |
|
|
554
|
-
git checkout main
|
|
555
|
-
git merge staging --ff-only
|
|
556
|
-
git push origin main
|
|
557
|
-
`);
|
|
558
|
-
}
|
|
559
|
-
async function extractBundle(projectDir) {
|
|
560
|
-
const release = await getLatestRelease();
|
|
561
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'frelio-'));
|
|
562
|
-
try {
|
|
563
|
-
const tarPath = await downloadTarball(release, tmpDir);
|
|
564
|
-
const { adminDir, functionsDir, workersDir } = await extractNpmTarball(tarPath, tmpDir);
|
|
565
|
-
// admin/ をコピー(dist/ の中身から functions/ を除く)
|
|
566
|
-
if (fs.existsSync(adminDir)) {
|
|
567
|
-
copyDir(adminDir, path.join(projectDir, 'admin'), ['functions']);
|
|
568
|
-
}
|
|
569
|
-
// functions/ をコピー(dist/functions/ → functions/)
|
|
570
|
-
if (fs.existsSync(functionsDir)) {
|
|
571
|
-
copyDir(functionsDir, path.join(projectDir, 'functions'));
|
|
572
|
-
}
|
|
573
|
-
// workers/ をコピー(Pages Functions が import する)
|
|
574
|
-
if (fs.existsSync(workersDir)) {
|
|
575
|
-
copyDir(workersDir, path.join(projectDir, 'workers'));
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
finally {
|
|
579
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
function copyDir(src, dest, exclude = []) {
|
|
583
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
584
|
-
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
585
|
-
if (exclude.includes(entry.name))
|
|
586
|
-
continue;
|
|
587
|
-
const srcPath = path.join(src, entry.name);
|
|
588
|
-
const destPath = path.join(dest, entry.name);
|
|
589
|
-
if (entry.isDirectory()) {
|
|
590
|
-
copyDir(srcPath, destPath);
|
|
591
|
-
}
|
|
592
|
-
else {
|
|
593
|
-
fs.copyFileSync(srcPath, destPath);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
398
|
function getTotalSteps(options) {
|
|
598
399
|
let steps = 4; // content structure, workflows, bundle, config files
|
|
599
400
|
if (!options.skipGithub)
|
|
600
401
|
steps += 3; // create repo, clone, commit/push
|
|
601
|
-
if (
|
|
602
|
-
steps += 1;
|
|
402
|
+
if (options.terraform)
|
|
403
|
+
steps += 1;
|
|
404
|
+
else if (!options.skipCloudflare)
|
|
405
|
+
steps += 1;
|
|
603
406
|
return steps;
|
|
604
407
|
}
|