@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.
@@ -1,5 +1,7 @@
1
1
  /**
2
2
  * frelio update - CMS Admin バンドルを最新版に更新
3
+ *
4
+ * core/bundle の updateBundle を呼ぶ薄いオーケストレーター。
3
5
  */
4
6
  type UpdateOptions = {
5
7
  version?: string;
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * frelio update - CMS Admin バンドルを最新版に更新
3
+ *
4
+ * core/bundle の updateBundle を呼ぶ薄いオーケストレーター。
3
5
  */
4
6
  import fs from 'node:fs';
5
7
  import path from 'node:path';
6
- import os from 'node:os';
7
- import { getLatestRelease, getRelease, downloadTarball, extractNpmTarball } from '../lib/npm-registry.js';
8
8
  import { log, logSuccess, logError } from '../lib/shell.js';
9
+ import { updateBundle } from '../core/bundle.js';
9
10
  export async function updateCommand(options) {
10
11
  log('');
11
12
  log('🔄 Frelio CMS Admin アップデート');
@@ -17,73 +18,16 @@ export async function updateCommand(options) {
17
18
  logError('admin/ ディレクトリが見つかりません。Frelio プロジェクトのルートで実行してください。');
18
19
  process.exit(1);
19
20
  }
20
- // config.json のバックアップ
21
- const configPath = path.join(adminDir, 'config.json');
22
- let configBackup = null;
23
- if (fs.existsSync(configPath)) {
24
- configBackup = fs.readFileSync(configPath, 'utf-8');
25
- logSuccess('config.json をバックアップしました');
26
- }
27
- // リリース取得
28
- log(' 📥 リリースを取得中...');
29
- const release = options.version
30
- ? await getRelease(options.version)
31
- : await getLatestRelease();
32
- log(` → ${release.tag_name}`);
33
- // tarball ダウンロード & 展開
34
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'frelio-update-'));
35
- try {
36
- const tarPath = await downloadTarball(release, tmpDir);
37
- const { adminDir: srcAdminDir, functionsDir: srcFunctionsDir, workersDir: srcWorkersDir } = await extractNpmTarball(tarPath, tmpDir);
38
- // admin/ を置き換え(dist/ の中身から functions/ を除く)
39
- if (fs.existsSync(srcAdminDir)) {
40
- fs.rmSync(adminDir, { recursive: true, force: true });
41
- copyDir(srcAdminDir, adminDir, ['functions']);
42
- logSuccess('admin/ を更新しました');
43
- }
44
- // functions/api/ を置き換え(functions/storage/ はユーザー管理なので保持)
45
- const functionsApiDir = path.join(projectDir, 'functions', 'api');
46
- if (fs.existsSync(path.join(srcFunctionsDir, 'api'))) {
47
- if (fs.existsSync(functionsApiDir)) {
48
- fs.rmSync(functionsApiDir, { recursive: true, force: true });
49
- }
50
- copyDir(path.join(srcFunctionsDir, 'api'), functionsApiDir);
51
- logSuccess('functions/api/ を更新しました');
52
- }
53
- // workers/ を置き換え(Pages Functions が import する)
54
- const workersDestDir = path.join(projectDir, 'workers');
55
- if (fs.existsSync(srcWorkersDir)) {
56
- if (fs.existsSync(workersDestDir)) {
57
- fs.rmSync(workersDestDir, { recursive: true, force: true });
58
- }
59
- copyDir(srcWorkersDir, workersDestDir);
60
- logSuccess('workers/ を更新しました');
61
- }
62
- // config.json を復元
63
- if (configBackup) {
64
- fs.writeFileSync(configPath, configBackup, 'utf-8');
65
- logSuccess('config.json を復元しました');
66
- }
21
+ log(' 📥 バンドルを取得・更新中...');
22
+ const result = await updateBundle(projectDir, options.version);
23
+ if (result.success) {
24
+ logSuccess('config.json を保持したままバンドルを更新しました');
67
25
  log('');
68
- log(`✅ ${release.tag_name} にアップデートしました!`);
26
+ log(`✅ ${result.data.version} にアップデートしました!`);
69
27
  log('');
70
28
  }
71
- finally {
72
- fs.rmSync(tmpDir, { recursive: true, force: true });
73
- }
74
- }
75
- function copyDir(src, dest, exclude = []) {
76
- fs.mkdirSync(dest, { recursive: true });
77
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
78
- if (exclude.includes(entry.name))
79
- continue;
80
- const srcPath = path.join(src, entry.name);
81
- const destPath = path.join(dest, entry.name);
82
- if (entry.isDirectory()) {
83
- copyDir(srcPath, destPath);
84
- }
85
- else {
86
- fs.copyFileSync(srcPath, destPath);
87
- }
29
+ else {
30
+ logError(result.error);
31
+ process.exit(1);
88
32
  }
89
33
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * core/bundle — CMS Admin バンドルのダウンロード・展開・更新
3
+ */
4
+ import { type OperationResult } from './types.js';
5
+ export declare function installBundle(projectDir: string, version?: string): Promise<OperationResult<{
6
+ version: string;
7
+ }>>;
8
+ export declare function updateBundle(projectDir: string, version?: string): Promise<OperationResult<{
9
+ version: string;
10
+ }>>;
11
+ export declare function getBundleVersion(projectDir: string): OperationResult<{
12
+ version: string | null;
13
+ installed: boolean;
14
+ }>;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * core/bundle — CMS Admin バンドルのダウンロード・展開・更新
3
+ */
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import os from 'node:os';
7
+ import { ok, fail } from './types.js';
8
+ import { getLatestRelease, getRelease, downloadTarball, extractNpmTarball, } from '../lib/npm-registry.js';
9
+ // ---------------------------------------------------------------------------
10
+ // Install (initial)
11
+ // ---------------------------------------------------------------------------
12
+ export async function installBundle(projectDir, version) {
13
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'frelio-'));
14
+ try {
15
+ const release = version ? await getRelease(version) : await getLatestRelease();
16
+ const tarPath = await downloadTarball(release, tmpDir);
17
+ const { adminDir, functionsDir, workersDir } = await extractNpmTarball(tarPath, tmpDir);
18
+ if (fs.existsSync(adminDir)) {
19
+ copyDir(adminDir, path.join(projectDir, 'admin'), ['functions']);
20
+ }
21
+ if (fs.existsSync(functionsDir)) {
22
+ copyDir(functionsDir, path.join(projectDir, 'functions'));
23
+ }
24
+ if (fs.existsSync(workersDir)) {
25
+ copyDir(workersDir, path.join(projectDir, 'workers'));
26
+ }
27
+ return ok({ version: release.tag_name });
28
+ }
29
+ catch (e) {
30
+ return fail(`バンドル展開失敗: ${e.message}`, 'EXEC_FAILED');
31
+ }
32
+ finally {
33
+ fs.rmSync(tmpDir, { recursive: true, force: true });
34
+ }
35
+ }
36
+ // ---------------------------------------------------------------------------
37
+ // Update (preserves config.json)
38
+ // ---------------------------------------------------------------------------
39
+ export async function updateBundle(projectDir, version) {
40
+ const adminDir = path.join(projectDir, 'admin');
41
+ if (!fs.existsSync(adminDir)) {
42
+ return fail('admin/ ディレクトリが見つかりません。', 'NOT_FOUND');
43
+ }
44
+ // config.json バックアップ
45
+ const configPath = path.join(adminDir, 'config.json');
46
+ let configBackup = null;
47
+ if (fs.existsSync(configPath)) {
48
+ configBackup = fs.readFileSync(configPath, 'utf-8');
49
+ }
50
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'frelio-update-'));
51
+ try {
52
+ const release = version ? await getRelease(version) : await getLatestRelease();
53
+ const tarPath = await downloadTarball(release, tmpDir);
54
+ const { adminDir: srcAdminDir, functionsDir: srcFunctionsDir, workersDir: srcWorkersDir, } = await extractNpmTarball(tarPath, tmpDir);
55
+ // admin/ 置き換え
56
+ if (fs.existsSync(srcAdminDir)) {
57
+ fs.rmSync(adminDir, { recursive: true, force: true });
58
+ copyDir(srcAdminDir, adminDir, ['functions']);
59
+ }
60
+ // functions/api/ 置き換え(functions/storage/ は保持)
61
+ const functionsApiDir = path.join(projectDir, 'functions', 'api');
62
+ if (fs.existsSync(path.join(srcFunctionsDir, 'api'))) {
63
+ if (fs.existsSync(functionsApiDir)) {
64
+ fs.rmSync(functionsApiDir, { recursive: true, force: true });
65
+ }
66
+ copyDir(path.join(srcFunctionsDir, 'api'), functionsApiDir);
67
+ }
68
+ // workers/ 置き換え
69
+ const workersDestDir = path.join(projectDir, 'workers');
70
+ if (fs.existsSync(srcWorkersDir)) {
71
+ if (fs.existsSync(workersDestDir)) {
72
+ fs.rmSync(workersDestDir, { recursive: true, force: true });
73
+ }
74
+ copyDir(srcWorkersDir, workersDestDir);
75
+ }
76
+ // config.json 復元
77
+ if (configBackup) {
78
+ fs.writeFileSync(configPath, configBackup, 'utf-8');
79
+ }
80
+ return ok({ version: release.tag_name });
81
+ }
82
+ catch (e) {
83
+ return fail(`バンドル更新失敗: ${e.message}`, 'EXEC_FAILED');
84
+ }
85
+ finally {
86
+ fs.rmSync(tmpDir, { recursive: true, force: true });
87
+ }
88
+ }
89
+ // ---------------------------------------------------------------------------
90
+ // Version check
91
+ // ---------------------------------------------------------------------------
92
+ export function getBundleVersion(projectDir) {
93
+ const installed = fs.existsSync(path.join(projectDir, 'admin'));
94
+ const versionPath = path.join(projectDir, 'version.json');
95
+ let version = null;
96
+ try {
97
+ const raw = fs.readFileSync(versionPath, 'utf-8');
98
+ version = JSON.parse(raw).version ?? null;
99
+ }
100
+ catch {
101
+ // version.json がない場合
102
+ }
103
+ return ok({ version, installed });
104
+ }
105
+ // ---------------------------------------------------------------------------
106
+ // Shared helper
107
+ // ---------------------------------------------------------------------------
108
+ function copyDir(src, dest, exclude = []) {
109
+ fs.mkdirSync(dest, { recursive: true });
110
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
111
+ if (exclude.includes(entry.name))
112
+ continue;
113
+ const srcPath = path.join(src, entry.name);
114
+ const destPath = path.join(dest, entry.name);
115
+ if (entry.isDirectory()) {
116
+ copyDir(srcPath, destPath);
117
+ }
118
+ else {
119
+ fs.copyFileSync(srcPath, destPath);
120
+ }
121
+ }
122
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * core/cloudflare — Cloudflare リソース操作
3
+ *
4
+ * 各操作は冪等。「既に存在」は成功バリアントとして返す。
5
+ */
6
+ import { type ProjectConfig, type OperationResult } from './types.js';
7
+ export declare function createR2Bucket(bucketName: string): OperationResult<{
8
+ alreadyExisted: boolean;
9
+ }>;
10
+ export declare function createPagesProject(projectName: string, productionBranch: string): OperationResult<{
11
+ alreadyExisted: boolean;
12
+ }>;
13
+ export declare function setPagesSecret(projectName: string, secretName: string, secretValue: string): OperationResult;
14
+ export type CloudflareSetupResult = {
15
+ r2Bucket: OperationResult<{
16
+ alreadyExisted: boolean;
17
+ }>;
18
+ contentPages: OperationResult<{
19
+ alreadyExisted: boolean;
20
+ }>;
21
+ adminPages: OperationResult<{
22
+ alreadyExisted: boolean;
23
+ }>;
24
+ secrets: OperationResult[];
25
+ };
26
+ export declare function setupCloudflareResources(config: ProjectConfig, githubClientSecret?: string): OperationResult<CloudflareSetupResult>;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * core/cloudflare — Cloudflare リソース操作
3
+ *
4
+ * 各操作は冪等。「既に存在」は成功バリアントとして返す。
5
+ */
6
+ import { ok, okVoid, fail } from './types.js';
7
+ import { exec } from '../lib/shell.js';
8
+ // ---------------------------------------------------------------------------
9
+ // Individual resource operations
10
+ // ---------------------------------------------------------------------------
11
+ export function createR2Bucket(bucketName) {
12
+ try {
13
+ exec(`wrangler r2 bucket create ${bucketName}`, { silent: true });
14
+ return ok({ alreadyExisted: false });
15
+ }
16
+ catch (error) {
17
+ const msg = error.message;
18
+ if (msg.includes('already exists')) {
19
+ return ok({ alreadyExisted: true }, true);
20
+ }
21
+ return fail(`R2 バケット作成失敗: ${msg}`, 'EXEC_FAILED');
22
+ }
23
+ }
24
+ export function createPagesProject(projectName, productionBranch) {
25
+ try {
26
+ exec(`wrangler pages project create ${projectName} --production-branch ${productionBranch}`, {
27
+ silent: true,
28
+ });
29
+ return ok({ alreadyExisted: false });
30
+ }
31
+ catch (error) {
32
+ const msg = error.message;
33
+ if (msg.includes('already exists') || msg.includes('A project with this name already exists')) {
34
+ return ok({ alreadyExisted: true }, true);
35
+ }
36
+ return fail(`Pages プロジェクト作成失敗: ${msg}`, 'EXEC_FAILED');
37
+ }
38
+ }
39
+ export function setPagesSecret(projectName, secretName, secretValue) {
40
+ try {
41
+ exec(`echo "${secretValue}" | wrangler pages secret put ${secretName} --project-name ${projectName}`, {
42
+ silent: true,
43
+ });
44
+ return okVoid();
45
+ }
46
+ catch (e) {
47
+ return fail(`${secretName} の設定に失敗しました(${projectName}): ${e.message}`, 'EXEC_FAILED');
48
+ }
49
+ }
50
+ export function setupCloudflareResources(config, githubClientSecret) {
51
+ const r2Bucket = createR2Bucket(config.r2BucketName);
52
+ const contentPages = createPagesProject(config.pagesProjectName, 'main');
53
+ const adminPages = createPagesProject(config.adminPagesProjectName, 'admin');
54
+ const secrets = [];
55
+ if (githubClientSecret) {
56
+ secrets.push(setPagesSecret(config.pagesProjectName, 'GITHUB_CLIENT_SECRET', githubClientSecret));
57
+ secrets.push(setPagesSecret(config.pagesProjectName, 'GITHUB_CLIENT_ID', config.githubClientId));
58
+ }
59
+ return ok({ r2Bucket, contentPages, adminPages, secrets });
60
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * core/config — config.json の読み書き・バリデーション・構築
3
+ *
4
+ * CMS 管理画面の GeneralSettings パターンに倣い、
5
+ * read-only フィールド(contentRepo, githubClientId)を保護しつつ部分更新できる。
6
+ */
7
+ import { type ProjectConfig, type BuildConfigParams, type OperationResult } from './types.js';
8
+ export declare function readConfig(projectDir: string): OperationResult<{
9
+ config: ProjectConfig | null;
10
+ path: string;
11
+ }>;
12
+ export declare function writeConfig(projectDir: string, config: ProjectConfig): OperationResult;
13
+ export type EditableConfigFields = Omit<ProjectConfig, 'contentRepo' | 'githubClientId'>;
14
+ export declare function updateConfig(projectDir: string, updates: Partial<EditableConfigFields>): OperationResult<{
15
+ config: ProjectConfig;
16
+ }>;
17
+ export type ConfigValidationError = {
18
+ field: string;
19
+ message: string;
20
+ };
21
+ export declare function validateConfig(config: Partial<ProjectConfig>): OperationResult<{
22
+ errors: ConfigValidationError[];
23
+ }>;
24
+ export declare function buildConfig(params: BuildConfigParams): OperationResult<{
25
+ config: ProjectConfig;
26
+ }>;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * core/config — config.json の読み書き・バリデーション・構築
3
+ *
4
+ * CMS 管理画面の GeneralSettings パターンに倣い、
5
+ * read-only フィールド(contentRepo, githubClientId)を保護しつつ部分更新できる。
6
+ */
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { ok, okVoid, fail, } from './types.js';
10
+ import { generateStagingDomain } from '../lib/templates.js';
11
+ import { validateContentRepo, validateR2PublicUrl, } from '../lib/validators.js';
12
+ /** read-only フィールド(updateConfig で上書き不可) */
13
+ const READ_ONLY_FIELDS = ['contentRepo', 'githubClientId'];
14
+ /** config.json の相対パス */
15
+ const CONFIG_REL_PATH = path.join('admin', 'config.json');
16
+ // ---------------------------------------------------------------------------
17
+ // Read
18
+ // ---------------------------------------------------------------------------
19
+ export function readConfig(projectDir) {
20
+ const configPath = path.join(projectDir, CONFIG_REL_PATH);
21
+ if (!fs.existsSync(configPath)) {
22
+ return ok({ config: null, path: configPath });
23
+ }
24
+ try {
25
+ const raw = fs.readFileSync(configPath, 'utf-8');
26
+ const config = JSON.parse(raw);
27
+ return ok({ config, path: configPath });
28
+ }
29
+ catch (e) {
30
+ return fail(`config.json の読み込みに失敗: ${e.message}`, 'EXEC_FAILED');
31
+ }
32
+ }
33
+ // ---------------------------------------------------------------------------
34
+ // Write (full)
35
+ // ---------------------------------------------------------------------------
36
+ export function writeConfig(projectDir, config) {
37
+ const configPath = path.join(projectDir, CONFIG_REL_PATH);
38
+ try {
39
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
40
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
41
+ return okVoid();
42
+ }
43
+ catch (e) {
44
+ return fail(`config.json の書き込みに失敗: ${e.message}`, 'EXEC_FAILED');
45
+ }
46
+ }
47
+ export function updateConfig(projectDir, updates) {
48
+ const readResult = readConfig(projectDir);
49
+ if (!readResult.success)
50
+ return readResult;
51
+ if (!readResult.data.config) {
52
+ return fail('config.json が存在しません。先に writeConfig で作成してください。', 'NOT_FOUND');
53
+ }
54
+ const existing = readResult.data.config;
55
+ // read-only フィールドが updates に含まれていたら除去(型で制限済みだが安全策)
56
+ const safeUpdates = { ...updates };
57
+ for (const key of READ_ONLY_FIELDS) {
58
+ delete safeUpdates[key];
59
+ }
60
+ const merged = { ...existing, ...safeUpdates };
61
+ const writeResult = writeConfig(projectDir, merged);
62
+ if (!writeResult.success)
63
+ return writeResult;
64
+ return ok({ config: merged });
65
+ }
66
+ export function validateConfig(config) {
67
+ const errors = [];
68
+ if (config.contentRepo !== undefined) {
69
+ const check = validateContentRepo(config.contentRepo);
70
+ if (check !== true)
71
+ errors.push({ field: 'contentRepo', message: check });
72
+ }
73
+ if (config.r2PublicUrl !== undefined) {
74
+ const check = validateR2PublicUrl(config.r2PublicUrl);
75
+ if (check !== true)
76
+ errors.push({ field: 'r2PublicUrl', message: check });
77
+ }
78
+ if (config.productionUrl !== undefined && config.productionUrl !== '') {
79
+ try {
80
+ new URL(config.productionUrl);
81
+ }
82
+ catch {
83
+ errors.push({ field: 'productionUrl', message: '有効な URL を入力してください' });
84
+ }
85
+ }
86
+ return ok({ errors });
87
+ }
88
+ // ---------------------------------------------------------------------------
89
+ // Build (raw params → ProjectConfig with derived values)
90
+ // ---------------------------------------------------------------------------
91
+ export function buildConfig(params) {
92
+ // バリデーション
93
+ const repoCheck = validateContentRepo(params.contentRepo);
94
+ if (repoCheck !== true) {
95
+ return fail(`contentRepo: ${repoCheck}`, 'VALIDATION_ERROR');
96
+ }
97
+ if (params.r2PublicUrl) {
98
+ const urlCheck = validateR2PublicUrl(params.r2PublicUrl);
99
+ if (urlCheck !== true) {
100
+ return fail(`r2PublicUrl: ${urlCheck}`, 'VALIDATION_ERROR');
101
+ }
102
+ }
103
+ const repoName = params.contentRepo.split('/')[1];
104
+ const stagingDomain = params.stagingDomain ?? generateStagingDomain(params.productionUrl || '', repoName);
105
+ const previewUrl = stagingDomain ? `https://${stagingDomain}` : '';
106
+ const config = {
107
+ contentRepo: params.contentRepo,
108
+ githubClientId: params.githubClientId || '',
109
+ siteTitle: params.siteTitle || '',
110
+ productionUrl: params.productionUrl || '',
111
+ previewUrl,
112
+ r2BucketName: params.r2BucketName ?? `${repoName}-files`,
113
+ r2PublicUrl: params.r2PublicUrl || '',
114
+ pagesProjectName: repoName,
115
+ adminPagesProjectName: `${repoName}-admin`,
116
+ ownerUsername: params.ownerUsername || '',
117
+ stagingDomain,
118
+ };
119
+ return ok({ config });
120
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * core/content-structure — コンテンツリポジトリのディレクトリ・ファイル構造生成
3
+ */
4
+ 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<{
18
+ files: string[];
19
+ }>;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * core/content-structure — コンテンツリポジトリのディレクトリ・ファイル構造生成
3
+ */
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 });
116
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * core/file-generators — 個別ファイルの再生成
3
+ *
4
+ * config.json 変更後に特定のファイルだけ再生成する場合に使う。
5
+ * 既存ファイルを上書きする(冪等)。
6
+ */
7
+ import { type ProjectConfig, type OperationResult } from './types.js';
8
+ export declare function generateAndWriteConfigJson(projectDir: string, config: ProjectConfig): OperationResult<{
9
+ path: string;
10
+ }>;
11
+ export declare function generateAndWriteWranglerToml(projectDir: string, config: ProjectConfig): OperationResult<{
12
+ path: string;
13
+ }>;
14
+ export declare function generateAndWriteRedirects(projectDir: string): OperationResult<{
15
+ path: string;
16
+ }>;
17
+ export declare function generateAndWriteRoutesJson(projectDir: string): OperationResult<{
18
+ path: string;
19
+ }>;
20
+ export declare function generateAndWritePublicRoutesJson(projectDir: string): OperationResult<{
21
+ path: string;
22
+ }>;
23
+ export declare function generateAndWriteStorageFunction(projectDir: string): OperationResult<{
24
+ path: string;
25
+ }>;
26
+ export declare function regenerateAllConfigFiles(projectDir: string, config: ProjectConfig): OperationResult<{
27
+ paths: string[];
28
+ }>;