@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
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/file-generators — 個別ファイルの再生成
|
|
3
|
+
*
|
|
4
|
+
* config.json 変更後に特定のファイルだけ再生成する場合に使う。
|
|
5
|
+
* 既存ファイルを上書きする(冪等)。
|
|
6
|
+
*/
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { ok, fail } from './types.js';
|
|
9
|
+
import { generateConfigJson, generateWranglerToml, generateRedirects, generateRoutesJson, generateStorageFunction, writeFile, } from '../lib/templates.js';
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Individual file generators
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
export function generateAndWriteConfigJson(projectDir, config) {
|
|
14
|
+
try {
|
|
15
|
+
const filePath = path.join(projectDir, 'admin', 'config.json');
|
|
16
|
+
writeFile(filePath, generateConfigJson(config));
|
|
17
|
+
return ok({ path: filePath });
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
return fail(`config.json 生成失敗: ${e.message}`, 'EXEC_FAILED');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function generateAndWriteWranglerToml(projectDir, config) {
|
|
24
|
+
try {
|
|
25
|
+
const filePath = path.join(projectDir, 'wrangler.toml');
|
|
26
|
+
writeFile(filePath, generateWranglerToml(config));
|
|
27
|
+
return ok({ path: filePath });
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
return fail(`wrangler.toml 生成失敗: ${e.message}`, 'EXEC_FAILED');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function generateAndWriteRedirects(projectDir) {
|
|
34
|
+
try {
|
|
35
|
+
const filePath = path.join(projectDir, '_redirects');
|
|
36
|
+
writeFile(filePath, generateRedirects());
|
|
37
|
+
return ok({ path: filePath });
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
return fail(`_redirects 生成失敗: ${e.message}`, 'EXEC_FAILED');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function generateAndWriteRoutesJson(projectDir) {
|
|
44
|
+
try {
|
|
45
|
+
const filePath = path.join(projectDir, '_routes.json');
|
|
46
|
+
writeFile(filePath, generateRoutesJson());
|
|
47
|
+
return ok({ path: filePath });
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
return fail(`_routes.json 生成失敗: ${e.message}`, 'EXEC_FAILED');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function generateAndWritePublicRoutesJson(projectDir) {
|
|
54
|
+
try {
|
|
55
|
+
const filePath = path.join(projectDir, 'public', '_routes.json');
|
|
56
|
+
const content = JSON.stringify({ version: 1, include: ['/storage/*'], exclude: [] }, null, 2);
|
|
57
|
+
writeFile(filePath, content);
|
|
58
|
+
return ok({ path: filePath });
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
return fail(`public/_routes.json 生成失敗: ${e.message}`, 'EXEC_FAILED');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function generateAndWriteStorageFunction(projectDir) {
|
|
65
|
+
try {
|
|
66
|
+
const filePath = path.join(projectDir, 'functions', 'storage', '[[path]].ts');
|
|
67
|
+
writeFile(filePath, generateStorageFunction());
|
|
68
|
+
return ok({ path: filePath });
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
return fail(`storage function 生成失敗: ${e.message}`, 'EXEC_FAILED');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Regenerate all config-derived files at once
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
export function regenerateAllConfigFiles(projectDir, config) {
|
|
78
|
+
const paths = [];
|
|
79
|
+
const generators = [
|
|
80
|
+
() => generateAndWriteConfigJson(projectDir, config),
|
|
81
|
+
() => generateAndWriteWranglerToml(projectDir, config),
|
|
82
|
+
() => generateAndWriteRedirects(projectDir),
|
|
83
|
+
() => generateAndWriteRoutesJson(projectDir),
|
|
84
|
+
() => generateAndWritePublicRoutesJson(projectDir),
|
|
85
|
+
];
|
|
86
|
+
for (const gen of generators) {
|
|
87
|
+
const result = gen();
|
|
88
|
+
if (!result.success)
|
|
89
|
+
return result;
|
|
90
|
+
paths.push(result.data.path);
|
|
91
|
+
}
|
|
92
|
+
return ok({ paths });
|
|
93
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/git-operations — Git コミット・ブランチ・プッシュ操作
|
|
3
|
+
*/
|
|
4
|
+
import { type OperationResult } from './types.js';
|
|
5
|
+
export declare function initialCommitAndBranches(projectDir: string, contentRepo: string): OperationResult<{
|
|
6
|
+
branches: string[];
|
|
7
|
+
defaultBranch: string;
|
|
8
|
+
}>;
|
|
9
|
+
export declare function createBranch(projectDir: string, branchName: string, baseBranch: string): OperationResult<{
|
|
10
|
+
alreadyExisted: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function isGitRepo(projectDir: string): boolean;
|
|
13
|
+
export declare function checkWorkflowBranchCoverage(projectDir: string, branchName: string): OperationResult<{
|
|
14
|
+
covered: boolean;
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/git-operations — Git コミット・ブランチ・プッシュ操作
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { ok, fail } from './types.js';
|
|
6
|
+
import { exec } from '../lib/shell.js';
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Initial commit + branch creation
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
export function initialCommitAndBranches(projectDir, contentRepo) {
|
|
11
|
+
try {
|
|
12
|
+
exec('git add -A', { cwd: projectDir, silent: true });
|
|
13
|
+
exec('git commit -m "Initial Frelio CMS setup"', { cwd: projectDir, silent: true });
|
|
14
|
+
// develop, admin, staging ブランチ作成
|
|
15
|
+
const branches = ['develop', 'admin', 'staging'];
|
|
16
|
+
for (const branch of branches) {
|
|
17
|
+
exec(`git branch ${branch}`, { cwd: projectDir, silent: true });
|
|
18
|
+
}
|
|
19
|
+
// プッシュ
|
|
20
|
+
exec('git push -u origin main', { cwd: projectDir, silent: true });
|
|
21
|
+
for (const branch of branches) {
|
|
22
|
+
exec(`git push -u origin ${branch}`, { cwd: projectDir, silent: true });
|
|
23
|
+
}
|
|
24
|
+
// デフォルトブランチを develop に変更
|
|
25
|
+
exec(`gh repo edit ${contentRepo} --default-branch develop`, { silent: true });
|
|
26
|
+
return ok({ branches: ['main', ...branches], defaultBranch: 'develop' });
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
return fail(`Git 操作失敗: ${e.message}`, 'EXEC_FAILED');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Single branch creation
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
export function createBranch(projectDir, branchName, baseBranch) {
|
|
36
|
+
try {
|
|
37
|
+
exec('git fetch origin', { cwd: projectDir, silent: true });
|
|
38
|
+
// リモートに既にあるか確認
|
|
39
|
+
try {
|
|
40
|
+
exec(`git rev-parse --verify origin/${branchName}`, { cwd: projectDir, silent: true });
|
|
41
|
+
return ok({ alreadyExisted: true }, true);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// 存在しない → 作成
|
|
45
|
+
}
|
|
46
|
+
exec(`git branch ${branchName} ${baseBranch}`, { cwd: projectDir, silent: true });
|
|
47
|
+
exec(`git push -u origin ${branchName}`, { cwd: projectDir, silent: true });
|
|
48
|
+
return ok({ alreadyExisted: false });
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
return fail(`ブランチ作成失敗: ${e.message}`, 'EXEC_FAILED');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Check
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
export function isGitRepo(projectDir) {
|
|
58
|
+
try {
|
|
59
|
+
exec('git rev-parse --is-inside-work-tree', { cwd: projectDir, silent: true });
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Workflow branch check
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
export function checkWorkflowBranchCoverage(projectDir, branchName) {
|
|
70
|
+
try {
|
|
71
|
+
const workflow = fs.readFileSync(`${projectDir}/.github/workflows/build-staging.yml`, 'utf-8');
|
|
72
|
+
const covered = workflow.includes('staging-*') || workflow.includes(branchName);
|
|
73
|
+
return ok({ covered });
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return fail('build-staging.yml が見つかりません。', 'NOT_FOUND');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/github — GitHub リポジトリ操作
|
|
3
|
+
*
|
|
4
|
+
* 各操作は冪等。「既に存在」は成功バリアントとして返す。
|
|
5
|
+
*/
|
|
6
|
+
import { type OperationResult } from './types.js';
|
|
7
|
+
export declare function createRepo(contentRepo: string): OperationResult<{
|
|
8
|
+
alreadyExisted: boolean;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function cloneRepo(contentRepo: string, projectDir: string): OperationResult<{
|
|
11
|
+
path: string;
|
|
12
|
+
alreadyExisted: boolean;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function getAuthenticatedUser(): OperationResult<{
|
|
15
|
+
username: string;
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/github — GitHub リポジトリ操作
|
|
3
|
+
*
|
|
4
|
+
* 各操作は冪等。「既に存在」は成功バリアントとして返す。
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import { ok, fail } from './types.js';
|
|
8
|
+
import { exec } from '../lib/shell.js';
|
|
9
|
+
export function createRepo(contentRepo) {
|
|
10
|
+
try {
|
|
11
|
+
exec(`gh repo create ${contentRepo} --private --confirm`, { silent: true });
|
|
12
|
+
return ok({ alreadyExisted: false });
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
const msg = error.message;
|
|
16
|
+
if (msg.includes('already exists')) {
|
|
17
|
+
return ok({ alreadyExisted: true }, true);
|
|
18
|
+
}
|
|
19
|
+
return fail(`リポジトリ作成失敗: ${msg}`, 'EXEC_FAILED');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function cloneRepo(contentRepo, projectDir) {
|
|
23
|
+
try {
|
|
24
|
+
const repoName = contentRepo.split('/')[1];
|
|
25
|
+
exec(`gh repo clone ${contentRepo} ${repoName}`, { silent: true });
|
|
26
|
+
return ok({ path: projectDir, alreadyExisted: false });
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (fs.existsSync(projectDir)) {
|
|
30
|
+
return ok({ path: projectDir, alreadyExisted: true }, true);
|
|
31
|
+
}
|
|
32
|
+
return fail(`クローン失敗: ${error.message}`, 'EXEC_FAILED');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function getAuthenticatedUser() {
|
|
36
|
+
try {
|
|
37
|
+
const username = exec('gh api user -q .login', { silent: true });
|
|
38
|
+
return ok({ username });
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
return fail(`ユーザー情報取得失敗: ${e.message}`, 'AUTH_REQUIRED');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/ — AI エージェント向け関数群の一括エクスポート
|
|
3
|
+
*
|
|
4
|
+
* AI エージェントはこのモジュールから全関数にアクセスできる。
|
|
5
|
+
* 各関数は OperationResult<T> を返し、prompts/process.exit/console.log を含まない。
|
|
6
|
+
*/
|
|
7
|
+
export type { OperationResult, OperationSuccess, OperationFailure, ErrorCode, ProjectConfig, ProjectStatus, BuildConfigParams, } from './types.js';
|
|
8
|
+
export { ok, okVoid, fail } from './types.js';
|
|
9
|
+
export { inspectProject, isGitRepo, hasContentStructure, hasAdminBundle, hasValidConfig, listBranches, getRemoteUrl, } from './status.js';
|
|
10
|
+
export { readConfig, writeConfig, updateConfig, validateConfig, buildConfig, } from './config.js';
|
|
11
|
+
export type { EditableConfigFields, ConfigValidationError } from './config.js';
|
|
12
|
+
export { checkGit, checkGhCli, checkWrangler, checkPrerequisitesFor, } from './prerequisites.js';
|
|
13
|
+
export type { PrerequisiteTarget } from './prerequisites.js';
|
|
14
|
+
export { createRepo, cloneRepo, getAuthenticatedUser } from './github.js';
|
|
15
|
+
export { createR2Bucket, createPagesProject, setPagesSecret, setupCloudflareResources, } from './cloudflare.js';
|
|
16
|
+
export type { CloudflareSetupResult } from './cloudflare.js';
|
|
17
|
+
export { createContentDirectories, createStructureFiles, createProjectFiles, createDemoContent, createFullContentStructure, } from './content-structure.js';
|
|
18
|
+
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
|
+
export { generateTerraformFiles } from './terraform.js';
|
|
22
|
+
export { installBundle, updateBundle, getBundleVersion } from './bundle.js';
|
|
23
|
+
export { initialCommitAndBranches, createBranch, checkWorkflowBranchCoverage, } from './git-operations.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/ — AI エージェント向け関数群の一括エクスポート
|
|
3
|
+
*
|
|
4
|
+
* AI エージェントはこのモジュールから全関数にアクセスできる。
|
|
5
|
+
* 各関数は OperationResult<T> を返し、prompts/process.exit/console.log を含まない。
|
|
6
|
+
*/
|
|
7
|
+
export { ok, okVoid, fail } from './types.js';
|
|
8
|
+
// Status — AI の最初のエントリーポイント
|
|
9
|
+
export { inspectProject, isGitRepo, hasContentStructure, hasAdminBundle, hasValidConfig, listBranches, getRemoteUrl, } from './status.js';
|
|
10
|
+
// Note: isGitRepo is also available from git-operations.ts but status.ts is the canonical export
|
|
11
|
+
// Config — config.json CRUD
|
|
12
|
+
export { readConfig, writeConfig, updateConfig, validateConfig, buildConfig, } from './config.js';
|
|
13
|
+
// Prerequisites — ツール別チェック
|
|
14
|
+
export { checkGit, checkGhCli, checkWrangler, checkPrerequisitesFor, } from './prerequisites.js';
|
|
15
|
+
// GitHub
|
|
16
|
+
export { createRepo, cloneRepo, getAuthenticatedUser } from './github.js';
|
|
17
|
+
// Cloudflare
|
|
18
|
+
export { createR2Bucket, createPagesProject, setPagesSecret, setupCloudflareResources, } from './cloudflare.js';
|
|
19
|
+
// Content structure
|
|
20
|
+
export { createContentDirectories, createStructureFiles, createProjectFiles, createDemoContent, createFullContentStructure, } from './content-structure.js';
|
|
21
|
+
// File generators
|
|
22
|
+
export { generateAndWriteConfigJson, generateAndWriteWranglerToml, generateAndWriteRedirects, generateAndWriteRoutesJson, generateAndWritePublicRoutesJson, generateAndWriteStorageFunction, regenerateAllConfigFiles, } from './file-generators.js';
|
|
23
|
+
// Workflows
|
|
24
|
+
export { generateWorkflow, generateWorkflows } from './workflows.js';
|
|
25
|
+
// Terraform
|
|
26
|
+
export { generateTerraformFiles } from './terraform.js';
|
|
27
|
+
// Bundle
|
|
28
|
+
export { installBundle, updateBundle, getBundleVersion } from './bundle.js';
|
|
29
|
+
// Git operations
|
|
30
|
+
export { initialCommitAndBranches, createBranch, checkWorkflowBranchCoverage, } from './git-operations.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/prerequisites — ツール別の前提チェック
|
|
3
|
+
*
|
|
4
|
+
* 各ツールを個別にチェックし、構造化された結果を返す。
|
|
5
|
+
*/
|
|
6
|
+
import { type OperationResult } from './types.js';
|
|
7
|
+
export declare function checkGit(): OperationResult<{
|
|
8
|
+
version: string;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function checkGhCli(): OperationResult<{
|
|
11
|
+
version: string;
|
|
12
|
+
authenticated: boolean;
|
|
13
|
+
username: string;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function checkWrangler(): OperationResult<{
|
|
16
|
+
version: string;
|
|
17
|
+
authenticated: boolean;
|
|
18
|
+
}>;
|
|
19
|
+
export type PrerequisiteTarget = 'git' | 'github' | 'cloudflare';
|
|
20
|
+
export declare function checkPrerequisitesFor(targets: PrerequisiteTarget[]): OperationResult<{
|
|
21
|
+
results: Record<string, OperationResult<Record<string, unknown>>>;
|
|
22
|
+
}>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/prerequisites — ツール別の前提チェック
|
|
3
|
+
*
|
|
4
|
+
* 各ツールを個別にチェックし、構造化された結果を返す。
|
|
5
|
+
*/
|
|
6
|
+
import { ok, fail } from './types.js';
|
|
7
|
+
import { exec, commandExists } from '../lib/shell.js';
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Individual tool checks
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
export function checkGit() {
|
|
12
|
+
if (!commandExists('git')) {
|
|
13
|
+
return fail('git が見つかりません。', 'COMMAND_MISSING');
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const version = exec('git --version', { silent: true });
|
|
17
|
+
return ok({ version });
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
return fail(`git バージョン取得失敗: ${e.message}`, 'EXEC_FAILED');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function checkGhCli() {
|
|
24
|
+
if (!commandExists('gh')) {
|
|
25
|
+
return fail('gh CLI が見つかりません。https://cli.github.com/ からインストールしてください。', 'COMMAND_MISSING');
|
|
26
|
+
}
|
|
27
|
+
let version;
|
|
28
|
+
try {
|
|
29
|
+
version = exec('gh --version', { silent: true }).split('\n')[0];
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
return fail(`gh バージョン取得失敗: ${e.message}`, 'EXEC_FAILED');
|
|
33
|
+
}
|
|
34
|
+
let authenticated = false;
|
|
35
|
+
let username = '';
|
|
36
|
+
try {
|
|
37
|
+
exec('gh auth status', { silent: true });
|
|
38
|
+
authenticated = true;
|
|
39
|
+
try {
|
|
40
|
+
username = exec('gh api user -q .login', { silent: true });
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// username 取得失敗は致命的でない
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// 未認証
|
|
48
|
+
}
|
|
49
|
+
if (!authenticated) {
|
|
50
|
+
return fail('gh にログインしていません。`gh auth login` を実行してください。', 'AUTH_REQUIRED');
|
|
51
|
+
}
|
|
52
|
+
return ok({ version, authenticated, username });
|
|
53
|
+
}
|
|
54
|
+
export function checkWrangler() {
|
|
55
|
+
if (!commandExists('wrangler')) {
|
|
56
|
+
return fail('wrangler CLI が見つかりません。`npm i -g wrangler` でインストールしてください。', 'COMMAND_MISSING');
|
|
57
|
+
}
|
|
58
|
+
let version;
|
|
59
|
+
try {
|
|
60
|
+
version = exec('wrangler --version', { silent: true });
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
return fail(`wrangler バージョン取得失敗: ${e.message}`, 'EXEC_FAILED');
|
|
64
|
+
}
|
|
65
|
+
let authenticated = false;
|
|
66
|
+
try {
|
|
67
|
+
exec('wrangler whoami', { silent: true });
|
|
68
|
+
authenticated = true;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// 未認証
|
|
72
|
+
}
|
|
73
|
+
if (!authenticated) {
|
|
74
|
+
return fail('wrangler にログインしていません。`wrangler login` を実行してください。', 'AUTH_REQUIRED');
|
|
75
|
+
}
|
|
76
|
+
return ok({ version, authenticated });
|
|
77
|
+
}
|
|
78
|
+
export function checkPrerequisitesFor(targets) {
|
|
79
|
+
const results = {};
|
|
80
|
+
let allOk = true;
|
|
81
|
+
if (targets.includes('git')) {
|
|
82
|
+
const r = checkGit();
|
|
83
|
+
results.git = r;
|
|
84
|
+
if (!r.success)
|
|
85
|
+
allOk = false;
|
|
86
|
+
}
|
|
87
|
+
if (targets.includes('github')) {
|
|
88
|
+
const r = checkGhCli();
|
|
89
|
+
results.github = r;
|
|
90
|
+
if (!r.success)
|
|
91
|
+
allOk = false;
|
|
92
|
+
}
|
|
93
|
+
if (targets.includes('cloudflare')) {
|
|
94
|
+
const r = checkWrangler();
|
|
95
|
+
results.cloudflare = r;
|
|
96
|
+
if (!r.success)
|
|
97
|
+
allOk = false;
|
|
98
|
+
}
|
|
99
|
+
if (!allOk) {
|
|
100
|
+
const errors = Object.entries(results)
|
|
101
|
+
.filter(([, r]) => !r.success)
|
|
102
|
+
.map(([name, r]) => `${name}: ${r.error}`)
|
|
103
|
+
.join('; ');
|
|
104
|
+
return fail(errors, 'COMMAND_MISSING');
|
|
105
|
+
}
|
|
106
|
+
return ok({ results });
|
|
107
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/status — プロジェクト状態の検査
|
|
3
|
+
*
|
|
4
|
+
* AI エージェントが最初に呼ぶ関数。
|
|
5
|
+
* プロジェクトの現在の状態を構造的に返す。
|
|
6
|
+
*/
|
|
7
|
+
import type { ProjectConfig, ProjectStatus, OperationResult } from './types.js';
|
|
8
|
+
import type { ConfigValidationError } from './config.js';
|
|
9
|
+
export declare function inspectProject(projectDir: string): ProjectStatus;
|
|
10
|
+
export declare function isGitRepo(projectDir: string): boolean;
|
|
11
|
+
export declare function hasContentStructure(projectDir: string): boolean;
|
|
12
|
+
export declare function hasAdminBundle(projectDir: string): boolean;
|
|
13
|
+
export declare function hasValidConfig(projectDir: string): OperationResult<{
|
|
14
|
+
config: ProjectConfig | null;
|
|
15
|
+
errors: ConfigValidationError[];
|
|
16
|
+
}>;
|
|
17
|
+
export declare function listBranches(projectDir: string): string[];
|
|
18
|
+
export declare function getRemoteUrl(projectDir: string): string | null;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/status — プロジェクト状態の検査
|
|
3
|
+
*
|
|
4
|
+
* AI エージェントが最初に呼ぶ関数。
|
|
5
|
+
* プロジェクトの現在の状態を構造的に返す。
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { ok } from './types.js';
|
|
10
|
+
import { exec, commandExists } from '../lib/shell.js';
|
|
11
|
+
import { readConfig, validateConfig } from './config.js';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Full inspection
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
export function inspectProject(projectDir) {
|
|
16
|
+
const config = readConfigSafe(projectDir);
|
|
17
|
+
const branches = listBranches(projectDir);
|
|
18
|
+
return {
|
|
19
|
+
projectDir,
|
|
20
|
+
hasGitRepo: isGitRepo(projectDir),
|
|
21
|
+
hasContentStructure: hasContentStructure(projectDir),
|
|
22
|
+
hasAdminBundle: hasAdminBundle(projectDir),
|
|
23
|
+
hasConfigJson: config !== null,
|
|
24
|
+
hasWranglerToml: fs.existsSync(path.join(projectDir, 'wrangler.toml')),
|
|
25
|
+
hasWorkflows: fs.existsSync(path.join(projectDir, '.github', 'workflows', 'build-staging.yml')),
|
|
26
|
+
hasTerraform: fs.existsSync(path.join(projectDir, 'terraform', 'main.tf')),
|
|
27
|
+
config,
|
|
28
|
+
branches,
|
|
29
|
+
remoteUrl: getRemoteUrl(projectDir),
|
|
30
|
+
missingPrerequisites: detectMissingPrerequisites(),
|
|
31
|
+
bundleVersion: getBundleVersionFromPackage(projectDir),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Individual checks
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
export function isGitRepo(projectDir) {
|
|
38
|
+
try {
|
|
39
|
+
exec('git rev-parse --is-inside-work-tree', { cwd: projectDir, silent: true });
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function hasContentStructure(projectDir) {
|
|
47
|
+
return (fs.existsSync(path.join(projectDir, 'frelio-data', 'site', 'content_types')) &&
|
|
48
|
+
fs.existsSync(path.join(projectDir, 'frelio-data', 'admin', 'structure')));
|
|
49
|
+
}
|
|
50
|
+
export function hasAdminBundle(projectDir) {
|
|
51
|
+
return (fs.existsSync(path.join(projectDir, 'admin', 'index.html')) ||
|
|
52
|
+
fs.existsSync(path.join(projectDir, 'admin')));
|
|
53
|
+
}
|
|
54
|
+
export function hasValidConfig(projectDir) {
|
|
55
|
+
const readResult = readConfig(projectDir);
|
|
56
|
+
if (!readResult.success)
|
|
57
|
+
return readResult;
|
|
58
|
+
const config = readResult.data.config;
|
|
59
|
+
if (!config) {
|
|
60
|
+
return ok({ config: null, errors: [{ field: 'config.json', message: 'ファイルが存在しません' }] });
|
|
61
|
+
}
|
|
62
|
+
const validateResult = validateConfig(config);
|
|
63
|
+
if (!validateResult.success)
|
|
64
|
+
return validateResult;
|
|
65
|
+
return ok({ config, errors: validateResult.data.errors });
|
|
66
|
+
}
|
|
67
|
+
export function listBranches(projectDir) {
|
|
68
|
+
if (!isGitRepo(projectDir))
|
|
69
|
+
return [];
|
|
70
|
+
try {
|
|
71
|
+
const output = exec('git branch -a --format="%(refname:short)"', {
|
|
72
|
+
cwd: projectDir,
|
|
73
|
+
silent: true,
|
|
74
|
+
});
|
|
75
|
+
return output
|
|
76
|
+
.split('\n')
|
|
77
|
+
.map((b) => b.trim())
|
|
78
|
+
.filter(Boolean);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export function getRemoteUrl(projectDir) {
|
|
85
|
+
if (!isGitRepo(projectDir))
|
|
86
|
+
return null;
|
|
87
|
+
try {
|
|
88
|
+
return exec('git remote get-url origin', { cwd: projectDir, silent: true });
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Internal helpers
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
function readConfigSafe(projectDir) {
|
|
98
|
+
const result = readConfig(projectDir);
|
|
99
|
+
if (!result.success)
|
|
100
|
+
return null;
|
|
101
|
+
return result.data.config;
|
|
102
|
+
}
|
|
103
|
+
function detectMissingPrerequisites() {
|
|
104
|
+
const missing = [];
|
|
105
|
+
if (!commandExists('git'))
|
|
106
|
+
missing.push('git');
|
|
107
|
+
if (!commandExists('gh'))
|
|
108
|
+
missing.push('gh');
|
|
109
|
+
if (!commandExists('wrangler'))
|
|
110
|
+
missing.push('wrangler');
|
|
111
|
+
return missing;
|
|
112
|
+
}
|
|
113
|
+
function getBundleVersionFromPackage(projectDir) {
|
|
114
|
+
const versionPath = path.join(projectDir, 'version.json');
|
|
115
|
+
try {
|
|
116
|
+
const raw = fs.readFileSync(versionPath, 'utf-8');
|
|
117
|
+
return JSON.parse(raw).version ?? null;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/terraform — Terraform ファイル生成
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { ok, fail } from './types.js';
|
|
7
|
+
import { generateTerraformProviders, generateTerraformVariables, generateTerraformMain, generateTerraformOutputs, generateTerraformTfvarsExample, generateTerraformReadme, writeFile, } from '../lib/templates.js';
|
|
8
|
+
export function generateTerraformFiles(projectDir, config) {
|
|
9
|
+
const terraformDir = path.join(projectDir, 'terraform');
|
|
10
|
+
const files = [];
|
|
11
|
+
try {
|
|
12
|
+
const writes = [
|
|
13
|
+
['providers.tf', generateTerraformProviders()],
|
|
14
|
+
['variables.tf', generateTerraformVariables(config)],
|
|
15
|
+
['main.tf', generateTerraformMain(config)],
|
|
16
|
+
['outputs.tf', generateTerraformOutputs()],
|
|
17
|
+
['terraform.tfvars.example', generateTerraformTfvarsExample(config)],
|
|
18
|
+
['README.md', generateTerraformReadme()],
|
|
19
|
+
];
|
|
20
|
+
for (const [name, content] of writes) {
|
|
21
|
+
const filePath = path.join(terraformDir, name);
|
|
22
|
+
writeFile(filePath, content);
|
|
23
|
+
files.push(filePath);
|
|
24
|
+
}
|
|
25
|
+
// .gitignore に Terraform エントリ追加
|
|
26
|
+
appendTerraformGitignore(projectDir);
|
|
27
|
+
return ok({ files });
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
return fail(`Terraform ファイル生成失敗: ${e.message}`, 'EXEC_FAILED');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function appendTerraformGitignore(projectDir) {
|
|
34
|
+
const gitignorePath = path.join(projectDir, '.gitignore');
|
|
35
|
+
const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf-8') : '';
|
|
36
|
+
const terraformIgnore = [
|
|
37
|
+
'',
|
|
38
|
+
'# Terraform',
|
|
39
|
+
'terraform/.terraform/',
|
|
40
|
+
'terraform/terraform.tfvars',
|
|
41
|
+
'terraform/*.tfstate',
|
|
42
|
+
'terraform/*.tfstate.backup',
|
|
43
|
+
].join('\n');
|
|
44
|
+
if (!existing.includes('terraform/.terraform/')) {
|
|
45
|
+
fs.appendFileSync(gitignorePath, terraformIgnore + '\n', 'utf-8');
|
|
46
|
+
}
|
|
47
|
+
}
|