@c-time/frelio-cli 1.3.13 → 1.4.1
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 +18 -14
- 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 +164 -373
- 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 +13 -0
- package/dist/core/content-structure.js +13 -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 +22 -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/template-scaffold.d.ts +14 -0
- package/dist/core/template-scaffold.js +99 -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 +345 -0
- package/dist/index.js +2 -4
- package/dist/lib/github-release.d.ts +15 -0
- package/dist/lib/github-release.js +41 -0
- package/dist/lib/initial-content.js +87 -55
- package/dist/lib/template-renderer.d.ts +16 -0
- package/dist/lib/template-renderer.js +32 -0
- package/dist/lib/templates.d.ts +7 -7
- package/dist/lib/templates.js +311 -214
- package/package.json +2 -3
package/dist/commands/init.js
CHANGED
|
@@ -1,27 +1,49 @@
|
|
|
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 { generateTerraformFiles } from '../core/terraform.js';
|
|
21
|
+
import { installBundle } from '../core/bundle.js';
|
|
22
|
+
import { initialCommitAndBranches } from '../core/git-operations.js';
|
|
13
23
|
export async function initCommand(options) {
|
|
14
24
|
log('');
|
|
15
25
|
log('🚀 Frelio CMS プロジェクトセットアップ');
|
|
16
26
|
log('');
|
|
17
|
-
// 前提チェック
|
|
27
|
+
// ── 前提チェック ──
|
|
18
28
|
log('🔍 前提チェック...');
|
|
19
|
-
const
|
|
20
|
-
if (!
|
|
29
|
+
const targets = ['git'];
|
|
30
|
+
if (!options.skipGithub)
|
|
31
|
+
targets.push('github');
|
|
32
|
+
if (!options.skipCloudflare && !options.terraform)
|
|
33
|
+
targets.push('cloudflare');
|
|
34
|
+
const prereqs = checkPrerequisitesFor(targets);
|
|
35
|
+
if (prereqs.success) {
|
|
36
|
+
for (const [name, r] of Object.entries(prereqs.data.results)) {
|
|
37
|
+
if (r.success)
|
|
38
|
+
logSuccess(name);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
logError(prereqs.error);
|
|
21
43
|
process.exit(1);
|
|
22
44
|
}
|
|
23
45
|
log('');
|
|
24
|
-
// 対話式プロンプト
|
|
46
|
+
// ── 対話式プロンプト ──
|
|
25
47
|
log('📝 プロジェクト設定:');
|
|
26
48
|
const config = await promptConfig(options);
|
|
27
49
|
if (!config) {
|
|
@@ -29,7 +51,7 @@ export async function initCommand(options) {
|
|
|
29
51
|
process.exit(0);
|
|
30
52
|
}
|
|
31
53
|
log('');
|
|
32
|
-
// OAuth App 案内
|
|
54
|
+
// ── OAuth App 案内 ──
|
|
33
55
|
log('🔑 GitHub OAuth App:');
|
|
34
56
|
if (!config.githubClientId) {
|
|
35
57
|
const isInteractive = process.stdin.isTTY === true;
|
|
@@ -40,9 +62,9 @@ export async function initCommand(options) {
|
|
|
40
62
|
log(' ⚠ OAuth App は GitHub の Web UI で作成が必要です。');
|
|
41
63
|
log(' → https://github.com/settings/developers');
|
|
42
64
|
log(' → New OAuth App:');
|
|
43
|
-
log(` - Application name: ${config.siteTitle || config.
|
|
44
|
-
log(` - Homepage URL: https://${config.
|
|
45
|
-
log(` - Callback URL: https://${config.
|
|
65
|
+
log(` - Application name: ${config.siteTitle || config.adminPagesProjectName} CMS`);
|
|
66
|
+
log(` - Homepage URL: https://${config.adminPagesProjectName}.pages.dev`);
|
|
67
|
+
log(` - Callback URL: https://${config.adminPagesProjectName}.pages.dev/api/auth/callback`);
|
|
46
68
|
log('');
|
|
47
69
|
const oauthResponse = await prompts([
|
|
48
70
|
{
|
|
@@ -67,26 +89,20 @@ export async function initCommand(options) {
|
|
|
67
89
|
oauthResponse.clientSecret;
|
|
68
90
|
}
|
|
69
91
|
log('');
|
|
70
|
-
// セットアップ実行
|
|
92
|
+
// ── セットアップ実行 ──
|
|
71
93
|
const totalSteps = getTotalSteps(options);
|
|
72
94
|
let step = 0;
|
|
73
95
|
// GitHub リポジトリ作成
|
|
74
96
|
if (!options.skipGithub) {
|
|
75
97
|
step++;
|
|
76
98
|
logStep(step, totalSteps, 'GitHub リポジトリ作成...');
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
logSuccess('リポジトリ作成完了');
|
|
99
|
+
const repoResult = createRepo(config.contentRepo);
|
|
100
|
+
if (repoResult.success) {
|
|
101
|
+
logSuccess(repoResult.data.alreadyExisted ? 'リポジトリは既に存在します' : 'リポジトリ作成完了');
|
|
80
102
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
logSuccess('リポジトリは既に存在します');
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
logError(`リポジトリ作成失敗: ${msg}`);
|
|
88
|
-
process.exit(1);
|
|
89
|
-
}
|
|
103
|
+
else {
|
|
104
|
+
logError(repoResult.error);
|
|
105
|
+
process.exit(1);
|
|
90
106
|
}
|
|
91
107
|
}
|
|
92
108
|
// 作業ディレクトリ作成
|
|
@@ -95,18 +111,13 @@ export async function initCommand(options) {
|
|
|
95
111
|
if (!options.skipGithub) {
|
|
96
112
|
step++;
|
|
97
113
|
logStep(step, totalSteps, 'リポジトリをクローン...');
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
logSuccess('クローン完了');
|
|
114
|
+
const cloneResult = cloneRepo(config.contentRepo, projectDir);
|
|
115
|
+
if (cloneResult.success) {
|
|
116
|
+
logSuccess(cloneResult.data.alreadyExisted ? 'ディレクトリは既に存在します' : 'クローン完了');
|
|
101
117
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
logError(`クローン失敗: ${error.message}`);
|
|
108
|
-
process.exit(1);
|
|
109
|
-
}
|
|
118
|
+
else {
|
|
119
|
+
logError(cloneResult.error);
|
|
120
|
+
process.exit(1);
|
|
110
121
|
}
|
|
111
122
|
}
|
|
112
123
|
else {
|
|
@@ -114,104 +125,90 @@ export async function initCommand(options) {
|
|
|
114
125
|
fs.mkdirSync(projectDir, { recursive: true });
|
|
115
126
|
}
|
|
116
127
|
}
|
|
117
|
-
//
|
|
128
|
+
// コンテンツリポジトリ初期構造(ワークフロー含む)
|
|
118
129
|
step++;
|
|
119
130
|
logStep(step, totalSteps, 'コンテンツリポジトリ初期構造作成...');
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
const structResult = await createFullContentStructure(projectDir, config);
|
|
132
|
+
if (structResult.success) {
|
|
133
|
+
logSuccess('初期構造作成完了(デモサイトテンプレート + ワークフロー)');
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
logError(structResult.error);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
128
139
|
// CMS Admin バンドル展開
|
|
129
140
|
step++;
|
|
130
141
|
logStep(step, totalSteps, 'CMS Admin バンドルをダウンロード・展開...');
|
|
131
|
-
|
|
132
|
-
|
|
142
|
+
const bundleResult = await installBundle(projectDir);
|
|
143
|
+
if (bundleResult.success) {
|
|
133
144
|
logSuccess('バンドル展開完了');
|
|
134
145
|
}
|
|
135
|
-
|
|
136
|
-
logError(
|
|
146
|
+
else {
|
|
147
|
+
logError(bundleResult.error);
|
|
137
148
|
log(' ℹ リリースが公開されていない場合は、手動でバンドルを展開してください。');
|
|
138
149
|
log(' scripts/build-distributable.sh を実行してから dist-release/ の内容をコピーします。');
|
|
139
150
|
}
|
|
140
151
|
// 設定ファイル生成
|
|
141
152
|
step++;
|
|
142
153
|
logStep(step, totalSteps, '設定ファイル生成...');
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
154
|
+
const configResult = regenerateAllConfigFiles(projectDir, config);
|
|
155
|
+
if (configResult.success) {
|
|
156
|
+
logSuccess('設定ファイル生成完了');
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
logError(configResult.error);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
// Cloudflare セットアップ or Terraform ファイル生成
|
|
163
|
+
if (options.terraform) {
|
|
152
164
|
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
|
-
}
|
|
165
|
+
logStep(step, totalSteps, 'Terraform ファイル生成...');
|
|
166
|
+
const tfResult = generateTerraformFiles(projectDir, config);
|
|
167
|
+
if (tfResult.success) {
|
|
168
|
+
logSuccess('Terraform ファイル生成完了(terraform/)');
|
|
166
169
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
silent: true,
|
|
170
|
-
});
|
|
171
|
-
logSuccess(`Pages プロジェクト "${config.pagesProjectName}" 作成完了`);
|
|
170
|
+
else {
|
|
171
|
+
logError(tfResult.error);
|
|
172
172
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
}
|
|
174
|
+
else if (!options.skipCloudflare) {
|
|
175
|
+
step++;
|
|
176
|
+
logStep(step, totalSteps, 'Cloudflare セットアップ...');
|
|
177
|
+
const secret = config.githubClientSecret;
|
|
178
|
+
const cfResult = setupCloudflareResources(config, secret);
|
|
179
|
+
if (cfResult.success) {
|
|
180
|
+
const { r2Bucket, contentPages, adminPages, secrets } = cfResult.data;
|
|
181
|
+
if (r2Bucket.success) {
|
|
182
|
+
logSuccess(r2Bucket.alreadyExisted
|
|
183
|
+
? `R2 バケット "${config.r2BucketName}" は既に存在します`
|
|
184
|
+
: `R2 バケット "${config.r2BucketName}" 作成完了`);
|
|
177
185
|
}
|
|
178
186
|
else {
|
|
179
|
-
logError(
|
|
187
|
+
logError(r2Bucket.error);
|
|
180
188
|
}
|
|
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}" は既に存在します`);
|
|
189
|
+
if (contentPages.success) {
|
|
190
|
+
logSuccess(contentPages.alreadyExisted
|
|
191
|
+
? `Pages プロジェクト "${config.pagesProjectName}" は既に存在します`
|
|
192
|
+
: `Pages プロジェクト "${config.pagesProjectName}"(コンテンツ配信)作成完了`);
|
|
194
193
|
}
|
|
195
194
|
else {
|
|
196
|
-
logError(
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
// シークレット設定
|
|
200
|
-
const secret = config.githubClientSecret;
|
|
201
|
-
if (secret) {
|
|
202
|
-
try {
|
|
203
|
-
exec(`echo "${secret}" | wrangler pages secret put GITHUB_CLIENT_SECRET --project-name ${config.pagesProjectName}`, { silent: true });
|
|
204
|
-
logSuccess('GITHUB_CLIENT_SECRET 設定完了');
|
|
195
|
+
logError(contentPages.error);
|
|
205
196
|
}
|
|
206
|
-
|
|
207
|
-
|
|
197
|
+
if (adminPages.success) {
|
|
198
|
+
logSuccess(adminPages.alreadyExisted
|
|
199
|
+
? `Pages プロジェクト "${config.adminPagesProjectName}" は既に存在します`
|
|
200
|
+
: `Pages プロジェクト "${config.adminPagesProjectName}"(管理画面)作成完了`);
|
|
208
201
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
logSuccess('GITHUB_CLIENT_ID 設定完了');
|
|
202
|
+
else {
|
|
203
|
+
logError(adminPages.error);
|
|
212
204
|
}
|
|
213
|
-
|
|
214
|
-
|
|
205
|
+
for (const s of secrets) {
|
|
206
|
+
if (s.success) {
|
|
207
|
+
logSuccess('シークレット設定完了');
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
logError(s.error);
|
|
211
|
+
}
|
|
215
212
|
}
|
|
216
213
|
}
|
|
217
214
|
}
|
|
@@ -219,84 +216,47 @@ export async function initCommand(options) {
|
|
|
219
216
|
if (!options.skipGithub) {
|
|
220
217
|
step++;
|
|
221
218
|
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 });
|
|
219
|
+
const gitResult = initialCommitAndBranches(projectDir, config.contentRepo);
|
|
220
|
+
if (gitResult.success) {
|
|
234
221
|
logSuccess('ブランチ作成・プッシュ完了');
|
|
235
222
|
}
|
|
236
|
-
|
|
237
|
-
logError(
|
|
223
|
+
else {
|
|
224
|
+
logError(gitResult.error);
|
|
238
225
|
}
|
|
239
226
|
}
|
|
240
|
-
// 完了
|
|
227
|
+
// ── 完了 ──
|
|
241
228
|
log('');
|
|
242
229
|
log('✅ セットアップ完了!');
|
|
243
230
|
log('');
|
|
244
|
-
log(` 管理画面: https://${config.
|
|
231
|
+
log(` 管理画面: https://${config.adminPagesProjectName}.pages.dev/admin/`);
|
|
232
|
+
log(` プレビュー: https://staging.${config.pagesProjectName}.pages.dev`);
|
|
245
233
|
log('');
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
234
|
+
if (options.terraform) {
|
|
235
|
+
log(' Terraform でインフラを構築:');
|
|
236
|
+
log(` 1. cd ${repoName}/terraform`);
|
|
237
|
+
log(' 2. cp terraform.tfvars.example terraform.tfvars && vim terraform.tfvars');
|
|
238
|
+
log(' 3. terraform init');
|
|
239
|
+
log(' 4. terraform plan');
|
|
240
|
+
log(' 5. terraform apply');
|
|
241
|
+
log('');
|
|
242
|
+
log(' 残りの手動作業:');
|
|
243
|
+
log(` 1. コンテンツ配信 Pages(${config.pagesProjectName})にリポジトリを接続`);
|
|
244
|
+
log(` 2. 管理画面 Pages(${config.adminPagesProjectName})にリポジトリを接続(admin ブランチ)`);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
log(' 残りの手動作業:');
|
|
248
|
+
log(` 1. コンテンツ配信 Pages(${config.pagesProjectName})にリポジトリを接続`);
|
|
249
|
+
log(` 2. 管理画面 Pages(${config.adminPagesProjectName})にリポジトリを接続(admin ブランチ)`);
|
|
250
|
+
}
|
|
249
251
|
if (config.stagingDomain) {
|
|
250
|
-
log(` 3.
|
|
252
|
+
log(` 3. プレビュー用カスタムドメインを設定: ${config.stagingDomain}`);
|
|
251
253
|
}
|
|
252
254
|
log(` ${config.stagingDomain ? '4' : '3'}. ステージングのアクセス制限を設定(Cloudflare Access 推奨)`);
|
|
253
255
|
log('');
|
|
254
256
|
}
|
|
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
|
-
}
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// UI helpers (prompts — commands 層に閉じる)
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
300
260
|
async function promptConfig(options) {
|
|
301
261
|
const isInteractive = process.stdin.isTTY === true;
|
|
302
262
|
// 必須オプションが揃っている場合 → プロンプトスキップ
|
|
@@ -316,7 +276,7 @@ async function promptConfig(options) {
|
|
|
316
276
|
logError('使用例: frelio init --content-repo owner/repo --client-id xxx --client-secret yyy');
|
|
317
277
|
process.exit(1);
|
|
318
278
|
}
|
|
319
|
-
//
|
|
279
|
+
// 対話モード
|
|
320
280
|
const response = await prompts([
|
|
321
281
|
{
|
|
322
282
|
type: options.contentRepo ? null : 'text',
|
|
@@ -366,12 +326,8 @@ async function promptConfig(options) {
|
|
|
366
326
|
name: 'ownerUsername',
|
|
367
327
|
message: '管理者の GitHub ユーザー名:',
|
|
368
328
|
initial: () => {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
catch {
|
|
373
|
-
return '';
|
|
374
|
-
}
|
|
329
|
+
const userResult = getAuthenticatedUser();
|
|
330
|
+
return userResult.success ? userResult.data.username : '';
|
|
375
331
|
},
|
|
376
332
|
},
|
|
377
333
|
], { onCancel: () => process.exit(0) });
|
|
@@ -390,6 +346,7 @@ async function promptConfig(options) {
|
|
|
390
346
|
r2BucketName: options.r2BucketName ?? response.r2BucketName ?? `${repoName}-files`,
|
|
391
347
|
r2PublicUrl: options.r2PublicUrl ?? response.r2PublicUrl ?? '',
|
|
392
348
|
pagesProjectName: repoName,
|
|
349
|
+
adminPagesProjectName: `${repoName}-admin`,
|
|
393
350
|
ownerUsername: options.ownerUsername ?? response.ownerUsername ?? '',
|
|
394
351
|
stagingDomain,
|
|
395
352
|
};
|
|
@@ -399,206 +356,40 @@ async function promptConfig(options) {
|
|
|
399
356
|
return config;
|
|
400
357
|
}
|
|
401
358
|
function buildConfigFromOptions(options) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
if (
|
|
413
|
-
|
|
359
|
+
const result = buildConfig({
|
|
360
|
+
contentRepo: options.contentRepo,
|
|
361
|
+
githubClientId: options.clientId,
|
|
362
|
+
siteTitle: options.siteTitle,
|
|
363
|
+
productionUrl: options.productionUrl,
|
|
364
|
+
stagingDomain: options.stagingDomain,
|
|
365
|
+
r2BucketName: options.r2BucketName,
|
|
366
|
+
r2PublicUrl: options.r2PublicUrl,
|
|
367
|
+
ownerUsername: options.ownerUsername,
|
|
368
|
+
});
|
|
369
|
+
if (!result.success) {
|
|
370
|
+
logError(result.error);
|
|
414
371
|
process.exit(1);
|
|
415
372
|
}
|
|
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 = '';
|
|
373
|
+
// ownerUsername を gh api から補完
|
|
374
|
+
const config = result.data.config;
|
|
375
|
+
if (!config.ownerUsername) {
|
|
376
|
+
const userResult = getAuthenticatedUser();
|
|
377
|
+
if (userResult.success) {
|
|
378
|
+
config.ownerUsername = userResult.data.username;
|
|
428
379
|
}
|
|
429
380
|
}
|
|
430
381
|
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,
|
|
382
|
+
...config,
|
|
441
383
|
githubClientSecret: options.clientSecret,
|
|
442
384
|
};
|
|
443
385
|
}
|
|
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
386
|
function getTotalSteps(options) {
|
|
598
|
-
let steps =
|
|
387
|
+
let steps = 3; // content structure (includes workflows), bundle, config files
|
|
599
388
|
if (!options.skipGithub)
|
|
600
389
|
steps += 3; // create repo, clone, commit/push
|
|
601
|
-
if (
|
|
602
|
-
steps += 1;
|
|
390
|
+
if (options.terraform)
|
|
391
|
+
steps += 1;
|
|
392
|
+
else if (!options.skipCloudflare)
|
|
393
|
+
steps += 1;
|
|
603
394
|
return steps;
|
|
604
395
|
}
|