@c-time/frelio-cli 1.4.0 → 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 CHANGED
@@ -142,12 +142,11 @@ frelio init
142
142
  2. **対話式プロンプト** — リポジトリ名、サイトタイトル、R2 設定などを入力
143
143
  3. **GitHub OAuth App の案内** — OAuth App の作成手順を表示し、Client ID / Secret を入力
144
144
  4. **GitHub リポジトリ作成** — `gh repo create` でプライベートリポジトリを作成・クローン
145
- 5. **コンテンツ初期構造の作成** — `frelio-data/` ディレクトリとメタデータファイルを生成
146
- 6. **GitHub Actions ワークフロー配置**ステージングビルド、本番プロモート、直接デプロイの3つ
147
- 7. **CMS Admin バンドルの展開** 最新リリースから `admin/`、`functions/`、`workers/` を配置
148
- 8. **設定ファイル生成**`admin/config.json`、`wrangler.toml`、`_redirects`、`_routes.json`
149
- 9. **Cloudflare セットアップ** R2 バケット作成、本番用 Pages プロジェクト作成、ステージング用 Pages プロジェクト作成、OAuth シークレット設定
150
- 10. **ブランチ構造の作成** — `main`、`develop`、`staging` ブランチを作成・プッシュ、デフォルトブランチを `develop` に設定
145
+ 5. **コンテンツリポジトリのスキャフォールド**GitHub から [`c-time/frelio-content-template`](https://github.com/c-time/frelio-content-template) をダウンロードし、設定値を埋め込んで展開(`frelio-data/`、`scripts/`、`.github/workflows/`、`vite.config.ts` 等)
146
+ 6. **CMS Admin バンドルの展開**最新リリースから `admin/`、`functions/`、`workers/` を配置
147
+ 7. **設定ファイル生成** — `admin/config.json`、`wrangler.toml`、`_redirects`、`_routes.json`
148
+ 8. **Cloudflare セットアップ** R2 バケット作成、本番用 Pages プロジェクト作成、ステージング用 Pages プロジェクト作成、OAuth シークレット設定
149
+ 9. **ブランチ構造の作成**`main`、`develop`、`staging` ブランチを作成・プッシュ、デフォルトブランチを `develop` に設定
151
150
 
152
151
  #### プロンプトで聞かれること
153
152
 
@@ -212,7 +211,7 @@ frelio init \
212
211
 
213
212
  ```
214
213
  my-site/
215
- ├── admin/ # CMS 管理画面(ビルド済み)
214
+ ├── admin/ # CMS 管理画面(ビルド済み、frelio update で更新)
216
215
  │ ├── index.html
217
216
  │ ├── config.json # 実行時設定
218
217
  │ └── assets/
@@ -231,19 +230,24 @@ my-site/
231
230
  │ │ ├── contents/
232
231
  │ │ │ ├── published/ # 公開済み
233
232
  │ │ │ └── private/ # 下書き
234
- │ │ ├── templates/ # HTML テンプレート
235
- │ │ │ └── assets/
236
- │ │ │ ├── scss/ # スタイルソース
237
- │ │ │ └── ts/ # スクリプトソース
233
+ │ │ ├── templates/ # HTML テンプレート + アセットソース
234
+ │ │ │ ├── _parts/ # 共通パーツ (head.htm, header.htm, footer.htm)
235
+ │ │ │ ├── common/ # 全ページ共通 (scripts/ + styles/)
236
+ │ │ │ ├── about/ # ページ別 (index.html + scripts/ + styles/)
237
+ │ │ │ ├── contact/
238
+ │ │ │ └── news/
238
239
  │ │ └── data/data-json/ # SSG 中間データ
239
240
  │ └── admin/
240
- │ ├── metadata/ # メタデータ
241
+ │ ├── content_types/ # UI/Views 設定
242
+ │ ├── metadata/ # content_types.json, _dashboard.json
241
243
  │ ├── recipes/ # ビルドレシピ
242
- │ └── users/ # ユーザー管理
243
- ├── .github/workflows/ # GitHub Actions
244
+ │ └── structure/ # users.json, stages.json
245
+ ├── scripts/ # SSG ビルドスクリプト (tsx)
246
+ ├── .github/workflows/ # GitHub Actions(4ワークフロー)
244
247
  ├── vite.config.ts # テンプレートアセットビルド(SCSS/TS)
245
248
  ├── package.json # vite + sass + typescript
246
249
  ├── tsconfig.json
250
+ ├── tsconfig.node.json
247
251
  ├── wrangler.toml # Cloudflare 設定(R2 バインディング)
248
252
  ├── _redirects # /admin/* → SPA, /* → /public/:splat
249
253
  ├── _routes.json # /api/*, /storage/* → Functions
@@ -17,7 +17,6 @@ import { createRepo, cloneRepo, getAuthenticatedUser } from '../core/github.js';
17
17
  import { setupCloudflareResources } from '../core/cloudflare.js';
18
18
  import { createFullContentStructure } from '../core/content-structure.js';
19
19
  import { regenerateAllConfigFiles } from '../core/file-generators.js';
20
- import { generateWorkflows } from '../core/workflows.js';
21
20
  import { generateTerraformFiles } from '../core/terraform.js';
22
21
  import { installBundle } from '../core/bundle.js';
23
22
  import { initialCommitAndBranches } from '../core/git-operations.js';
@@ -126,28 +125,17 @@ export async function initCommand(options) {
126
125
  fs.mkdirSync(projectDir, { recursive: true });
127
126
  }
128
127
  }
129
- // コンテンツリポジトリ初期構造
128
+ // コンテンツリポジトリ初期構造(ワークフロー含む)
130
129
  step++;
131
130
  logStep(step, totalSteps, 'コンテンツリポジトリ初期構造作成...');
132
- const structResult = createFullContentStructure(projectDir, config);
131
+ const structResult = await createFullContentStructure(projectDir, config);
133
132
  if (structResult.success) {
134
- logSuccess('初期構造作成完了(デモサイトテンプレート付き)');
133
+ logSuccess('初期構造作成完了(デモサイトテンプレート + ワークフロー)');
135
134
  }
136
135
  else {
137
136
  logError(structResult.error);
138
137
  process.exit(1);
139
138
  }
140
- // GitHub Actions ワークフロー
141
- step++;
142
- logStep(step, totalSteps, 'GitHub Actions ワークフロー配置...');
143
- const workflowResult = generateWorkflows(projectDir, config);
144
- if (workflowResult.success) {
145
- logSuccess('ワークフロー配置完了');
146
- }
147
- else {
148
- logError(workflowResult.error);
149
- process.exit(1);
150
- }
151
139
  // CMS Admin バンドル展開
152
140
  step++;
153
141
  logStep(step, totalSteps, 'CMS Admin バンドルをダウンロード・展開...');
@@ -396,7 +384,7 @@ function buildConfigFromOptions(options) {
396
384
  };
397
385
  }
398
386
  function getTotalSteps(options) {
399
- let steps = 4; // content structure, workflows, bundle, config files
387
+ let steps = 3; // content structure (includes workflows), bundle, config files
400
388
  if (!options.skipGithub)
401
389
  steps += 3; // create repo, clone, commit/push
402
390
  if (options.terraform)
@@ -1,19 +1,13 @@
1
1
  /**
2
2
  * core/content-structure — コンテンツリポジトリのディレクトリ・ファイル構造生成
3
+ *
4
+ * テンプレートからのスキャフォールディングに委譲する。
3
5
  */
4
6
  import { type ProjectConfig, type OperationResult } from './types.js';
5
- export declare function createContentDirectories(projectDir: string): OperationResult<{
6
- directories: string[];
7
- }>;
8
- export declare function createStructureFiles(projectDir: string, config: ProjectConfig): OperationResult<{
9
- files: string[];
10
- }>;
11
- export declare function createProjectFiles(projectDir: string, config: ProjectConfig): OperationResult<{
12
- files: string[];
13
- }>;
14
- export declare function createDemoContent(projectDir: string): OperationResult<{
15
- generated: boolean;
16
- }>;
17
- export declare function createFullContentStructure(projectDir: string, config: ProjectConfig): OperationResult<{
7
+ /**
8
+ * テンプレートからコンテンツリポジトリの全構造を生成する。
9
+ * ワークフローファイル(.github/workflows/)もテンプレートに含まれる。
10
+ */
11
+ export declare function createFullContentStructure(projectDir: string, config: ProjectConfig): Promise<OperationResult<{
18
12
  files: string[];
19
- }>;
13
+ }>>;
@@ -1,116 +1,13 @@
1
1
  /**
2
2
  * core/content-structure — コンテンツリポジトリのディレクトリ・ファイル構造生成
3
+ *
4
+ * テンプレートからのスキャフォールディングに委譲する。
3
5
  */
4
- import path from 'node:path';
5
- import { ok, fail } from './types.js';
6
- import { generateUsersJson, generateStagesJson, generateVersionJson, generateViteConfig, generateTsConfig, generateTsConfigNode, generatePackageJson, generateStorageFunction, writeFile, ensureDir, } from '../lib/templates.js';
7
- import { generateInitialContent } from '../lib/initial-content.js';
8
- // ---------------------------------------------------------------------------
9
- // Directory tree
10
- // ---------------------------------------------------------------------------
11
- const CONTENT_DIRS = [
12
- 'frelio-data/site/content_types',
13
- 'frelio-data/site/contents/published',
14
- 'frelio-data/site/contents/private',
15
- 'frelio-data/site/templates/assets/scss',
16
- 'frelio-data/site/templates/assets/ts',
17
- 'frelio-data/site/templates/assets/entries',
18
- 'frelio-data/site/data/data-json',
19
- 'frelio-data/admin/metadata',
20
- 'frelio-data/admin/structure',
21
- 'frelio-data/admin/recipes',
22
- 'scripts',
23
- 'public',
24
- ];
25
- export function createContentDirectories(projectDir) {
26
- try {
27
- for (const dir of CONTENT_DIRS) {
28
- ensureDir(path.join(projectDir, dir));
29
- }
30
- return ok({ directories: CONTENT_DIRS });
31
- }
32
- catch (e) {
33
- return fail(`ディレクトリ作成失敗: ${e.message}`, 'EXEC_FAILED');
34
- }
35
- }
36
- // ---------------------------------------------------------------------------
37
- // Structure files (users.json, stages.json, version.json)
38
- // ---------------------------------------------------------------------------
39
- export function createStructureFiles(projectDir, config) {
40
- const files = [];
41
- try {
42
- const writes = [
43
- ['frelio-data/admin/structure/users.json', generateUsersJson(config)],
44
- ['frelio-data/admin/structure/stages.json', generateStagesJson()],
45
- ['version.json', generateVersionJson()],
46
- ];
47
- for (const [rel, content] of writes) {
48
- writeFile(path.join(projectDir, rel), content);
49
- files.push(rel);
50
- }
51
- return ok({ files });
52
- }
53
- catch (e) {
54
- return fail(`構造ファイル生成失敗: ${e.message}`, 'EXEC_FAILED');
55
- }
56
- }
57
- // ---------------------------------------------------------------------------
58
- // Project scaffolding files (package.json, vite.config, tsconfig, etc.)
59
- // ---------------------------------------------------------------------------
60
- export function createProjectFiles(projectDir, config) {
61
- const files = [];
62
- try {
63
- const writes = [
64
- ['vite.config.ts', generateViteConfig()],
65
- ['tsconfig.json', generateTsConfig()],
66
- ['tsconfig.node.json', generateTsConfigNode()],
67
- ['package.json', generatePackageJson(config)],
68
- [path.join('functions', 'storage', '[[path]].ts'), generateStorageFunction()],
69
- ];
70
- for (const [rel, content] of writes) {
71
- writeFile(path.join(projectDir, rel), content);
72
- files.push(rel);
73
- }
74
- // .gitignore
75
- const gitignoreLines = ['node_modules/', '.wrangler/', '.dev.vars'];
76
- writeFile(path.join(projectDir, '.gitignore'), gitignoreLines.join('\n') + '\n');
77
- files.push('.gitignore');
78
- return ok({ files });
79
- }
80
- catch (e) {
81
- return fail(`プロジェクトファイル生成失敗: ${e.message}`, 'EXEC_FAILED');
82
- }
83
- }
84
- // ---------------------------------------------------------------------------
85
- // Demo content
86
- // ---------------------------------------------------------------------------
87
- export function createDemoContent(projectDir) {
88
- try {
89
- generateInitialContent(projectDir);
90
- return ok({ generated: true });
91
- }
92
- catch (e) {
93
- return fail(`デモコンテンツ生成失敗: ${e.message}`, 'EXEC_FAILED');
94
- }
95
- }
96
- // ---------------------------------------------------------------------------
97
- // All-in-one
98
- // ---------------------------------------------------------------------------
99
- export function createFullContentStructure(projectDir, config) {
100
- const allFiles = [];
101
- const dirsResult = createContentDirectories(projectDir);
102
- if (!dirsResult.success)
103
- return dirsResult;
104
- const structResult = createStructureFiles(projectDir, config);
105
- if (!structResult.success)
106
- return structResult;
107
- allFiles.push(...structResult.data.files);
108
- const projResult = createProjectFiles(projectDir, config);
109
- if (!projResult.success)
110
- return projResult;
111
- allFiles.push(...projResult.data.files);
112
- const demoResult = createDemoContent(projectDir);
113
- if (!demoResult.success)
114
- return demoResult;
115
- return ok({ files: allFiles });
6
+ import { scaffoldFromTemplate } from './template-scaffold.js';
7
+ /**
8
+ * テンプレートからコンテンツリポジトリの全構造を生成する。
9
+ * ワークフローファイル(.github/workflows/)もテンプレートに含まれる。
10
+ */
11
+ export async function createFullContentStructure(projectDir, config) {
12
+ return scaffoldFromTemplate(projectDir, config);
116
13
  }
@@ -14,10 +14,9 @@ export type { PrerequisiteTarget } from './prerequisites.js';
14
14
  export { createRepo, cloneRepo, getAuthenticatedUser } from './github.js';
15
15
  export { createR2Bucket, createPagesProject, setPagesSecret, setupCloudflareResources, } from './cloudflare.js';
16
16
  export type { CloudflareSetupResult } from './cloudflare.js';
17
- export { createContentDirectories, createStructureFiles, createProjectFiles, createDemoContent, createFullContentStructure, } from './content-structure.js';
17
+ export { createFullContentStructure } from './content-structure.js';
18
+ export { scaffoldFromTemplate } from './template-scaffold.js';
18
19
  export { generateAndWriteConfigJson, generateAndWriteWranglerToml, generateAndWriteRedirects, generateAndWriteRoutesJson, generateAndWritePublicRoutesJson, generateAndWriteStorageFunction, regenerateAllConfigFiles, } from './file-generators.js';
19
- export { generateWorkflow, generateWorkflows } from './workflows.js';
20
- export type { WorkflowName } from './workflows.js';
21
20
  export { generateTerraformFiles } from './terraform.js';
22
21
  export { installBundle, updateBundle, getBundleVersion } from './bundle.js';
23
22
  export { initialCommitAndBranches, createBranch, checkWorkflowBranchCoverage, } from './git-operations.js';
@@ -17,11 +17,11 @@ export { createRepo, cloneRepo, getAuthenticatedUser } from './github.js';
17
17
  // Cloudflare
18
18
  export { createR2Bucket, createPagesProject, setPagesSecret, setupCloudflareResources, } from './cloudflare.js';
19
19
  // Content structure
20
- export { createContentDirectories, createStructureFiles, createProjectFiles, createDemoContent, createFullContentStructure, } from './content-structure.js';
20
+ export { createFullContentStructure } from './content-structure.js';
21
+ // Template scaffold
22
+ export { scaffoldFromTemplate } from './template-scaffold.js';
21
23
  // File generators
22
24
  export { generateAndWriteConfigJson, generateAndWriteWranglerToml, generateAndWriteRedirects, generateAndWriteRoutesJson, generateAndWritePublicRoutesJson, generateAndWriteStorageFunction, regenerateAllConfigFiles, } from './file-generators.js';
23
- // Workflows
24
- export { generateWorkflow, generateWorkflows } from './workflows.js';
25
25
  // Terraform
26
26
  export { generateTerraformFiles } from './terraform.js';
27
27
  // Bundle
@@ -0,0 +1,14 @@
1
+ /**
2
+ * core/template-scaffold — テンプレートからのコンテンツリポジトリ生成
3
+ *
4
+ * GitHub から c-time/frelio-content-template の tarball をダウンロードし、
5
+ * .hbs ファイルは変数置換、それ以外はそのままコピーして出力先に展開する。
6
+ */
7
+ import { type ProjectConfig, type OperationResult } from './types.js';
8
+ /**
9
+ * テンプレートからコンテンツリポジトリを生成する。
10
+ * GitHub から c-time/frelio-content-template を取得して展開する。
11
+ */
12
+ export declare function scaffoldFromTemplate(projectDir: string, config: ProjectConfig): Promise<OperationResult<{
13
+ files: string[];
14
+ }>>;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * core/template-scaffold — テンプレートからのコンテンツリポジトリ生成
3
+ *
4
+ * GitHub から c-time/frelio-content-template の tarball をダウンロードし、
5
+ * .hbs ファイルは変数置換、それ以外はそのままコピーして出力先に展開する。
6
+ */
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import os from 'node:os';
10
+ import { pipeline } from 'node:stream/promises';
11
+ import { extract } from 'tar';
12
+ import { ok, fail } from './types.js';
13
+ import { renderTemplate, projectConfigToVars } from '../lib/template-renderer.js';
14
+ const TEMPLATE_REPO = 'c-time/frelio-content-template';
15
+ const TEMPLATE_BRANCH = 'main';
16
+ /**
17
+ * GitHub から tarball をダウンロードして一時ディレクトリに展開する
18
+ */
19
+ async function fetchTemplate() {
20
+ const url = `https://api.github.com/repos/${TEMPLATE_REPO}/tarball/${TEMPLATE_BRANCH}`;
21
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'frelio-template-'));
22
+ const tarPath = path.join(tmpDir, 'template.tar.gz');
23
+ const response = await fetch(url, {
24
+ headers: { Accept: 'application/vnd.github+json' },
25
+ redirect: 'follow',
26
+ });
27
+ if (!response.ok || !response.body) {
28
+ throw new Error(`GitHub API responded with ${response.status}`);
29
+ }
30
+ const dest = fs.createWriteStream(tarPath);
31
+ await pipeline(response.body, dest);
32
+ const extractDir = path.join(tmpDir, 'extracted');
33
+ fs.mkdirSync(extractDir, { recursive: true });
34
+ await extract({ file: tarPath, cwd: extractDir });
35
+ // GitHub の tarball は owner-repo-sha/ というプレフィックスディレクトリに入る
36
+ const entries = fs.readdirSync(extractDir);
37
+ if (entries.length !== 1) {
38
+ throw new Error(`Unexpected tarball structure: ${entries.join(', ')}`);
39
+ }
40
+ return path.join(extractDir, entries[0]);
41
+ }
42
+ /**
43
+ * ディレクトリを再帰的に走査してファイル一覧を返す
44
+ */
45
+ function walkDir(dir, base = dir) {
46
+ const files = [];
47
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
48
+ const fullPath = path.join(dir, entry.name);
49
+ if (entry.isDirectory()) {
50
+ files.push(...walkDir(fullPath, base));
51
+ }
52
+ else {
53
+ files.push(path.relative(base, fullPath));
54
+ }
55
+ }
56
+ return files;
57
+ }
58
+ /** コピー対象外のファイル */
59
+ const SKIP_FILES = new Set(['template.config.json', 'README.md']);
60
+ /**
61
+ * テンプレートからコンテンツリポジトリを生成する。
62
+ * GitHub から c-time/frelio-content-template を取得して展開する。
63
+ */
64
+ export async function scaffoldFromTemplate(projectDir, config) {
65
+ let templateDir;
66
+ try {
67
+ templateDir = await fetchTemplate();
68
+ }
69
+ catch (e) {
70
+ return fail(`テンプレートの取得に失敗しました: ${e.message}`, 'EXEC_FAILED');
71
+ }
72
+ try {
73
+ const vars = projectConfigToVars(config);
74
+ const templateFiles = walkDir(templateDir);
75
+ const outputFiles = [];
76
+ for (const relPath of templateFiles) {
77
+ if (SKIP_FILES.has(relPath))
78
+ continue;
79
+ const srcPath = path.join(templateDir, relPath);
80
+ const content = fs.readFileSync(srcPath, 'utf-8');
81
+ const isHbs = relPath.endsWith('.hbs');
82
+ const outputRelPath = isHbs ? relPath.slice(0, -4) : relPath;
83
+ const outputContent = isHbs ? renderTemplate(content, vars) : content;
84
+ const outputPath = path.join(projectDir, outputRelPath);
85
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
86
+ fs.writeFileSync(outputPath, outputContent, 'utf-8');
87
+ outputFiles.push(outputRelPath);
88
+ }
89
+ return ok({ files: outputFiles });
90
+ }
91
+ catch (e) {
92
+ return fail(`テンプレート展開失敗: ${e.message}`, 'EXEC_FAILED');
93
+ }
94
+ finally {
95
+ // 一時ディレクトリを削除
96
+ const tmpDir = path.resolve(templateDir, '..', '..');
97
+ fs.rmSync(tmpDir, { recursive: true, force: true });
98
+ }
99
+ }