@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.
Files changed (44) hide show
  1. package/README.md +18 -14
  2. package/dist/commands/add-staging.d.ts +2 -3
  3. package/dist/commands/add-staging.js +38 -184
  4. package/dist/commands/init.d.ts +4 -0
  5. package/dist/commands/init.js +164 -373
  6. package/dist/commands/update.d.ts +2 -0
  7. package/dist/commands/update.js +11 -67
  8. package/dist/core/bundle.d.ts +14 -0
  9. package/dist/core/bundle.js +122 -0
  10. package/dist/core/cloudflare.d.ts +26 -0
  11. package/dist/core/cloudflare.js +60 -0
  12. package/dist/core/config.d.ts +26 -0
  13. package/dist/core/config.js +120 -0
  14. package/dist/core/content-structure.d.ts +13 -0
  15. package/dist/core/content-structure.js +13 -0
  16. package/dist/core/file-generators.d.ts +28 -0
  17. package/dist/core/file-generators.js +93 -0
  18. package/dist/core/git-operations.d.ts +15 -0
  19. package/dist/core/git-operations.js +78 -0
  20. package/dist/core/github.d.ts +16 -0
  21. package/dist/core/github.js +43 -0
  22. package/dist/core/index.d.ts +22 -0
  23. package/dist/core/index.js +30 -0
  24. package/dist/core/prerequisites.d.ts +22 -0
  25. package/dist/core/prerequisites.js +107 -0
  26. package/dist/core/status.d.ts +18 -0
  27. package/dist/core/status.js +122 -0
  28. package/dist/core/template-scaffold.d.ts +14 -0
  29. package/dist/core/template-scaffold.js +99 -0
  30. package/dist/core/terraform.d.ts +7 -0
  31. package/dist/core/terraform.js +47 -0
  32. package/dist/core/types.d.ts +48 -0
  33. package/dist/core/types.js +21 -0
  34. package/dist/core/workflows.d.ts +11 -0
  35. package/dist/core/workflows.js +345 -0
  36. package/dist/index.js +2 -4
  37. package/dist/lib/github-release.d.ts +15 -0
  38. package/dist/lib/github-release.js +41 -0
  39. package/dist/lib/initial-content.js +87 -55
  40. package/dist/lib/template-renderer.d.ts +16 -0
  41. package/dist/lib/template-renderer.js +32 -0
  42. package/dist/lib/templates.d.ts +7 -7
  43. package/dist/lib/templates.js +311 -214
  44. package/package.json +2 -3
@@ -0,0 +1,48 @@
1
+ /**
2
+ * core/ モジュール共通の型定義
3
+ *
4
+ * 全 core 関数は OperationResult<T> を返す。
5
+ * AI エージェントが結果を構造的に判断できるようにする。
6
+ */
7
+ import type { ProjectConfig } from '../lib/templates.js';
8
+ export type { ProjectConfig };
9
+ export type ErrorCode = 'NOT_FOUND' | 'ALREADY_EXISTS' | 'AUTH_REQUIRED' | 'COMMAND_MISSING' | 'VALIDATION_ERROR' | 'EXEC_FAILED';
10
+ export type OperationSuccess<T = void> = {
11
+ success: true;
12
+ alreadyExisted?: boolean;
13
+ data: T;
14
+ };
15
+ export type OperationFailure = {
16
+ success: false;
17
+ error: string;
18
+ code: ErrorCode;
19
+ };
20
+ export type OperationResult<T = void> = OperationSuccess<T> | OperationFailure;
21
+ export declare function ok<T>(data: T, alreadyExisted?: boolean): OperationSuccess<T>;
22
+ export declare function okVoid(alreadyExisted?: boolean): OperationSuccess<void>;
23
+ export declare function fail(error: string, code: ErrorCode): OperationFailure;
24
+ export type ProjectStatus = {
25
+ projectDir: string;
26
+ hasGitRepo: boolean;
27
+ hasContentStructure: boolean;
28
+ hasAdminBundle: boolean;
29
+ hasConfigJson: boolean;
30
+ hasWranglerToml: boolean;
31
+ hasWorkflows: boolean;
32
+ hasTerraform: boolean;
33
+ config: ProjectConfig | null;
34
+ branches: string[];
35
+ remoteUrl: string | null;
36
+ missingPrerequisites: string[];
37
+ bundleVersion: string | null;
38
+ };
39
+ export type BuildConfigParams = {
40
+ contentRepo: string;
41
+ githubClientId?: string;
42
+ siteTitle?: string;
43
+ productionUrl?: string;
44
+ stagingDomain?: string;
45
+ r2BucketName?: string;
46
+ r2PublicUrl?: string;
47
+ ownerUsername?: string;
48
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * core/ モジュール共通の型定義
3
+ *
4
+ * 全 core 関数は OperationResult<T> を返す。
5
+ * AI エージェントが結果を構造的に判断できるようにする。
6
+ */
7
+ // ---------------------------------------------------------------------------
8
+ // Helper constructors
9
+ // ---------------------------------------------------------------------------
10
+ export function ok(data, alreadyExisted) {
11
+ const result = { success: true, data };
12
+ if (alreadyExisted)
13
+ result.alreadyExisted = true;
14
+ return result;
15
+ }
16
+ export function okVoid(alreadyExisted) {
17
+ return ok(undefined, alreadyExisted);
18
+ }
19
+ export function fail(error, code) {
20
+ return { success: false, error, code };
21
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * core/workflows — GitHub Actions ワークフロー生成
3
+ */
4
+ import { type ProjectConfig, type OperationResult } from './types.js';
5
+ export type WorkflowName = 'deploy-admin' | 'build-staging' | 'promote-production' | 'direct-deploy';
6
+ export declare function generateWorkflow(projectDir: string, config: ProjectConfig, workflow: WorkflowName): OperationResult<{
7
+ path: string;
8
+ }>;
9
+ export declare function generateWorkflows(projectDir: string, config: ProjectConfig): OperationResult<{
10
+ files: string[];
11
+ }>;
@@ -0,0 +1,345 @@
1
+ /**
2
+ * core/workflows — GitHub Actions ワークフロー生成
3
+ */
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { ok, fail } from './types.js';
7
+ import { writeFile } from '../lib/templates.js';
8
+ // ---------------------------------------------------------------------------
9
+ // Individual workflow
10
+ // ---------------------------------------------------------------------------
11
+ export function generateWorkflow(projectDir, config, workflow) {
12
+ const workflowsDir = path.join(projectDir, '.github', 'workflows');
13
+ const content = getWorkflowContent(config, workflow);
14
+ const fileName = `${workflow}.yml`;
15
+ const filePath = path.join(workflowsDir, fileName);
16
+ try {
17
+ fs.mkdirSync(workflowsDir, { recursive: true });
18
+ writeFile(filePath, content);
19
+ return ok({ path: filePath });
20
+ }
21
+ catch (e) {
22
+ return fail(`ワークフロー生成失敗 (${fileName}): ${e.message}`, 'EXEC_FAILED');
23
+ }
24
+ }
25
+ // ---------------------------------------------------------------------------
26
+ // All workflows
27
+ // ---------------------------------------------------------------------------
28
+ export function generateWorkflows(projectDir, config) {
29
+ const names = ['deploy-admin', 'build-staging', 'promote-production', 'direct-deploy'];
30
+ const files = [];
31
+ for (const name of names) {
32
+ const result = generateWorkflow(projectDir, config, name);
33
+ if (!result.success)
34
+ return result;
35
+ files.push(result.data.path);
36
+ }
37
+ return ok({ files });
38
+ }
39
+ // ---------------------------------------------------------------------------
40
+ // Workflow templates
41
+ // ---------------------------------------------------------------------------
42
+ function getWorkflowContent(config, workflow) {
43
+ switch (workflow) {
44
+ case 'deploy-admin':
45
+ return `name: Deploy Admin
46
+ on:
47
+ push:
48
+ branches: [admin]
49
+
50
+ jobs:
51
+ deploy:
52
+ runs-on: ubuntu-latest
53
+ steps:
54
+ - uses: actions/checkout@v4
55
+ - uses: actions/setup-node@v4
56
+ with:
57
+ node-version: 20
58
+ cache: 'npm'
59
+ - run: npm ci
60
+ - run: npm run build
61
+ - name: Deploy to Cloudflare Pages
62
+ uses: cloudflare/wrangler-action@v3
63
+ with:
64
+ apiToken: \${{ secrets.CLOUDFLARE_API_TOKEN }}
65
+ accountId: \${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
66
+ command: pages deploy dist --project-name=${config.adminPagesProjectName}
67
+ `;
68
+ case 'build-staging':
69
+ return `# staging ブランチ(デフォルト + カスタム)への push で発火し、SSG を実行する。
70
+ # カスタム staging(staging-*)は artifact アップロードなし(プレビュー専用)。
71
+ name: Build Staging
72
+ on:
73
+ push:
74
+ branches: [staging, 'staging-*']
75
+
76
+ permissions:
77
+ contents: read
78
+
79
+ jobs:
80
+ build:
81
+ runs-on: ubuntu-latest
82
+ steps:
83
+ - uses: actions/checkout@v4
84
+ - uses: actions/setup-node@v4
85
+ with:
86
+ node-version: 20
87
+ cache: 'npm'
88
+
89
+ # 前回のビルド状態を復元(差分ビルド用)
90
+ - name: Restore SSG cache
91
+ uses: actions/cache@v4
92
+ with:
93
+ path: |
94
+ frelio-data/site/data/data-json
95
+ public
96
+ key: ssg-cache-\${{ github.ref_name }}-\${{ github.sha }}
97
+ restore-keys: |
98
+ ssg-cache-\${{ github.ref_name }}-
99
+
100
+ - run: npm ci
101
+
102
+ # Phase 1: JSON 中間データ生成
103
+ - name: Generate JSON data
104
+ run: npx frelio-data-json-generator
105
+
106
+ # Phase 2: HTML 生成
107
+ - name: Generate HTML
108
+ run: npx frelio-gentl
109
+
110
+ # Phase 3: アセットビルド(静的アセットコピー + Vite)
111
+ - name: Build assets
112
+ run: npm run build
113
+
114
+ # デフォルト staging のみ: artifact をアップロード(本番デプロイ用)
115
+ - name: Upload artifact
116
+ if: github.ref == 'refs/heads/staging'
117
+ uses: actions/upload-artifact@v4
118
+ with:
119
+ name: staging-build
120
+ path: public/
121
+ retention-days: 14
122
+
123
+ # Cloudflare Pages にブランチプレビューとしてデプロイ
124
+ - name: Deploy preview
125
+ uses: cloudflare/wrangler-action@v3
126
+ with:
127
+ apiToken: \${{ secrets.CLOUDFLARE_API_TOKEN }}
128
+ accountId: \${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
129
+ command: pages deploy public --project-name=${config.pagesProjectName} --branch=\${{ github.ref_name }}
130
+ `;
131
+ case 'promote-production':
132
+ return `# staging → main マージ(push to main)で発火。
133
+ # 最新の staging artifact をダウンロードし、Pages 本番にデプロイする。
134
+ # artifact 欠損時はフォールバックとしてフル SSG ビルドを実行する。
135
+ name: Promote to Production
136
+ on:
137
+ push:
138
+ branches: [main]
139
+
140
+ permissions:
141
+ contents: write
142
+ actions: read
143
+
144
+ jobs:
145
+ deploy:
146
+ runs-on: ubuntu-latest
147
+ steps:
148
+ - uses: actions/checkout@v4
149
+ with:
150
+ fetch-depth: 0
151
+
152
+ # 最新の成功した Build Staging ワークフロー run を検索
153
+ - name: Find latest staging build run
154
+ id: find-run
155
+ uses: actions/github-script@v7
156
+ with:
157
+ script: |
158
+ const runs = await github.rest.actions.listWorkflowRuns({
159
+ owner: context.repo.owner,
160
+ repo: context.repo.repo,
161
+ workflow_id: 'build-staging.yml',
162
+ branch: 'staging',
163
+ status: 'success',
164
+ per_page: 1,
165
+ });
166
+ if (runs.data.workflow_runs.length > 0) {
167
+ core.setOutput('run-id', runs.data.workflow_runs[0].id);
168
+ } else {
169
+ core.setOutput('run-id', '');
170
+ }
171
+
172
+ # cross-workflow artifact ダウンロード
173
+ - name: Download staging artifact
174
+ id: download-artifact
175
+ if: steps.find-run.outputs.run-id != ''
176
+ continue-on-error: true
177
+ uses: actions/download-artifact@v4
178
+ with:
179
+ name: staging-build
180
+ path: public/
181
+ github-token: \${{ secrets.GITHUB_TOKEN }}
182
+ run-id: \${{ steps.find-run.outputs.run-id }}
183
+
184
+ # artifact 取得結果を判定
185
+ - name: Check if artifact was downloaded
186
+ id: check-artifact
187
+ run: |
188
+ if [ -d "public" ] && [ "\$(ls -A public 2>/dev/null)" ]; then
189
+ echo "has_artifact=true" >> "\$GITHUB_OUTPUT"
190
+ else
191
+ echo "::warning::Staging artifact not found or expired. Running full SSG build."
192
+ echo "has_artifact=false" >> "\$GITHUB_OUTPUT"
193
+ fi
194
+
195
+ # --- フォールバックビルド(artifact 欠損時のみ) ---
196
+ - name: Setup Node.js (fallback)
197
+ if: steps.check-artifact.outputs.has_artifact == 'false'
198
+ uses: actions/setup-node@v4
199
+ with:
200
+ node-version: 20
201
+ cache: 'npm'
202
+
203
+ - name: Install dependencies (fallback)
204
+ if: steps.check-artifact.outputs.has_artifact == 'false'
205
+ run: npm ci
206
+
207
+ - name: Full SSG build (fallback)
208
+ if: steps.check-artifact.outputs.has_artifact == 'false'
209
+ run: |
210
+ npx frelio-data-json-generator --full-rebuild
211
+ npx frelio-gentl
212
+ npm run build
213
+
214
+ # Cloudflare Pages 本番にデプロイ
215
+ - name: Deploy to production
216
+ uses: cloudflare/wrangler-action@v3
217
+ with:
218
+ apiToken: \${{ secrets.CLOUDFLARE_API_TOKEN }}
219
+ accountId: \${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
220
+ command: pages deploy public --project-name=${config.pagesProjectName} --branch=main
221
+
222
+ # デプロイタグ作成
223
+ - name: Create deploy tag
224
+ run: |
225
+ VERSION=\$(jq -r '.version' version.json 2>/dev/null || echo "0.0.0")
226
+ PREFIX="d\${VERSION}."
227
+
228
+ LATEST=\$(git tag -l "\${PREFIX}*" | sed "s/^\${PREFIX}//" | sort -n | tail -1)
229
+ NEXT=\$(( \${LATEST:-0} + 1 ))
230
+
231
+ TAG="\${PREFIX}\${NEXT}"
232
+ echo "Creating tag: \$TAG"
233
+
234
+ git config user.name "github-actions[bot]"
235
+ git config user.email "github-actions[bot]@users.noreply.github.com"
236
+ git tag -a "\$TAG" -m "Production deploy \$TAG"
237
+ git push origin "\$TAG"
238
+
239
+ echo "::notice::Tagged as \$TAG"
240
+ `;
241
+ case 'direct-deploy':
242
+ return `# CMS の「直接デプロイ」ボタンから workflow_dispatch で発火。
243
+ # develop → staging マージ → SSG ビルド → artifact → staging プレビュー →
244
+ # staging → main マージ → 本番デプロイ → デプロイタグ付与
245
+ name: Direct Deploy
246
+ on:
247
+ workflow_dispatch:
248
+
249
+ permissions:
250
+ contents: write
251
+
252
+ jobs:
253
+ build:
254
+ runs-on: ubuntu-latest
255
+ steps:
256
+ - uses: actions/checkout@v4
257
+ with:
258
+ fetch-depth: 0
259
+
260
+ - name: Configure git
261
+ run: |
262
+ git config user.name "github-actions[bot]"
263
+ git config user.email "github-actions[bot]@users.noreply.github.com"
264
+
265
+ # Step 1: develop → staging マージ
266
+ - name: Merge develop into staging
267
+ run: |
268
+ git checkout staging
269
+ git merge origin/develop --no-edit
270
+ git push origin staging
271
+
272
+ # Step 2: SSG ビルド
273
+ - uses: actions/setup-node@v4
274
+ with:
275
+ node-version: 20
276
+ cache: 'npm'
277
+
278
+ # 前回の staging ビルド状態を復元(差分ビルド用)
279
+ - name: Restore SSG cache
280
+ uses: actions/cache@v4
281
+ with:
282
+ path: |
283
+ frelio-data/site/data/data-json
284
+ public
285
+ key: ssg-cache-staging-\${{ github.sha }}
286
+ restore-keys: |
287
+ ssg-cache-staging-
288
+
289
+ - run: npm ci
290
+
291
+ - name: Generate JSON data
292
+ run: npx frelio-data-json-generator
293
+
294
+ - name: Generate HTML
295
+ run: npx frelio-gentl
296
+
297
+ - name: Build assets
298
+ run: npm run build
299
+
300
+ # Step 3: artifact アップロード
301
+ - name: Upload artifact
302
+ uses: actions/upload-artifact@v4
303
+ with:
304
+ name: staging-build
305
+ path: public/
306
+ retention-days: 14
307
+
308
+ # Step 4: Cloudflare Pages プレビューデプロイ(staging ブランチ)
309
+ - name: Deploy staging preview
310
+ uses: cloudflare/wrangler-action@v3
311
+ with:
312
+ apiToken: \${{ secrets.CLOUDFLARE_API_TOKEN }}
313
+ accountId: \${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
314
+ command: pages deploy public --project-name=${config.pagesProjectName} --branch=staging
315
+
316
+ # Step 5: staging → main マージ
317
+ - name: Fast-forward main to staging
318
+ run: |
319
+ git checkout main
320
+ git merge --ff-only staging
321
+ git push origin main
322
+
323
+ # Step 6: Cloudflare Pages 本番デプロイ
324
+ - name: Deploy to production
325
+ uses: cloudflare/wrangler-action@v3
326
+ with:
327
+ apiToken: \${{ secrets.CLOUDFLARE_API_TOKEN }}
328
+ accountId: \${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
329
+ command: pages deploy public --project-name=${config.pagesProjectName} --branch=main
330
+
331
+ # Step 7: デプロイタグ作成
332
+ - name: Create deploy tag
333
+ run: |
334
+ VERSION=\$(jq -r '.version' version.json 2>/dev/null || echo "0.0.0")
335
+ PREFIX="d\${VERSION}."
336
+ LATEST=\$(git tag -l "\${PREFIX}*" | sed "s/^\${PREFIX}//" | sort -n | tail -1)
337
+ NEXT=\$(( \${LATEST:-0} + 1 ))
338
+ TAG="\${PREFIX}\${NEXT}"
339
+
340
+ git tag -a "\$TAG" -m "Direct deploy \$TAG"
341
+ git push origin "\$TAG"
342
+ echo "::notice::Tagged as \$TAG"
343
+ `;
344
+ }
345
+ }
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ 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('--terraform', 'Generate Terraform files instead of running wrangler commands')
16
17
  .option('--content-repo <repo>', 'Repository name (owner/repo)')
17
18
  .option('--site-title <title>', 'Site title')
18
19
  .option('--production-url <url>', 'Production URL')
@@ -30,10 +31,7 @@ program
30
31
  .action(updateCommand);
31
32
  program
32
33
  .command('add-staging')
33
- .description('Add a staging environment (preview branch + Cloudflare Pages project)')
34
+ .description('Add a staging environment (creates branch, uses Pages branch preview)')
34
35
  .option('--name <name>', 'Staging name (creates staging-{name} branch)')
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')
38
36
  .action(addStagingCommand);
39
37
  program.parse();
@@ -0,0 +1,15 @@
1
+ /**
2
+ * GitHub Release からの tarball ダウンロード
3
+ */
4
+ type ReleaseAsset = {
5
+ name: string;
6
+ browser_download_url: string;
7
+ };
8
+ type Release = {
9
+ tag_name: string;
10
+ assets: ReleaseAsset[];
11
+ };
12
+ export declare function getLatestRelease(): Promise<Release>;
13
+ export declare function getRelease(version: string): Promise<Release>;
14
+ export declare function downloadTarball(release: Release, destDir: string): Promise<string>;
15
+ export {};
@@ -0,0 +1,41 @@
1
+ /**
2
+ * GitHub Release からの tarball ダウンロード
3
+ */
4
+ import { createWriteStream } from 'node:fs';
5
+ import { pipeline } from 'node:stream/promises';
6
+ import { Readable } from 'node:stream';
7
+ import path from 'node:path';
8
+ const REPO = 'ctime-projects/frelio';
9
+ export async function getLatestRelease() {
10
+ const res = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`, {
11
+ headers: { Accept: 'application/vnd.github.v3+json' },
12
+ });
13
+ if (!res.ok) {
14
+ throw new Error(`Failed to fetch latest release: ${res.status} ${res.statusText}`);
15
+ }
16
+ return res.json();
17
+ }
18
+ export async function getRelease(version) {
19
+ const tag = version.startsWith('v') ? version : `v${version}`;
20
+ const res = await fetch(`https://api.github.com/repos/${REPO}/releases/tags/${tag}`, {
21
+ headers: { Accept: 'application/vnd.github.v3+json' },
22
+ });
23
+ if (!res.ok) {
24
+ throw new Error(`Release ${tag} not found: ${res.status}`);
25
+ }
26
+ return res.json();
27
+ }
28
+ export async function downloadTarball(release, destDir) {
29
+ const asset = release.assets.find((a) => a.name.endsWith('.tar.gz'));
30
+ if (!asset) {
31
+ throw new Error('No tarball found in release assets');
32
+ }
33
+ const destPath = path.join(destDir, asset.name);
34
+ const res = await fetch(asset.browser_download_url);
35
+ if (!res.ok || !res.body) {
36
+ throw new Error(`Failed to download: ${res.status}`);
37
+ }
38
+ const readable = Readable.fromWeb(res.body);
39
+ await pipeline(readable, createWriteStream(destPath));
40
+ return destPath;
41
+ }