@c-time/frelio-cli 0.1.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.
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Shell command utilities
3
+ */
4
+ export declare function exec(cmd: string, options?: {
5
+ cwd?: string;
6
+ silent?: boolean;
7
+ }): string;
8
+ export declare function commandExists(cmd: string): boolean;
9
+ export declare function log(message: string): void;
10
+ export declare function logStep(step: number, total: number, message: string): void;
11
+ export declare function logSuccess(message: string): void;
12
+ export declare function logError(message: string): void;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Shell command utilities
3
+ */
4
+ import { execSync } from 'node:child_process';
5
+ export function exec(cmd, options) {
6
+ try {
7
+ return execSync(cmd, {
8
+ cwd: options?.cwd,
9
+ encoding: 'utf-8',
10
+ stdio: options?.silent ? 'pipe' : ['pipe', 'pipe', 'pipe'],
11
+ }).trim();
12
+ }
13
+ catch (error) {
14
+ const err = error;
15
+ throw new Error(err.stderr || err.message);
16
+ }
17
+ }
18
+ export function commandExists(cmd) {
19
+ try {
20
+ execSync(process.platform === 'win32' ? `where ${cmd}` : `which ${cmd}`, {
21
+ stdio: 'pipe',
22
+ });
23
+ return true;
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }
29
+ export function log(message) {
30
+ console.log(message);
31
+ }
32
+ export function logStep(step, total, message) {
33
+ console.log(` [${step}/${total}] ${message}`);
34
+ }
35
+ export function logSuccess(message) {
36
+ console.log(` ✓ ${message}`);
37
+ }
38
+ export function logError(message) {
39
+ console.error(` ✗ ${message}`);
40
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * テンプレートファイル生成
3
+ */
4
+ export type ProjectConfig = {
5
+ contentRepo: string;
6
+ githubClientId: string;
7
+ siteTitle: string;
8
+ productionUrl: string;
9
+ previewUrl: string;
10
+ r2BucketName: string;
11
+ r2PublicUrl: string;
12
+ pagesProjectName: string;
13
+ ownerUsername: string;
14
+ stagingDomain: string;
15
+ };
16
+ /**
17
+ * ランダムな8文字のハッシュを生成(URL推測を困難にする)
18
+ */
19
+ export declare function generateHash(): string;
20
+ /**
21
+ * ドメインからステージング用のデフォルトサブドメインを生成
22
+ * 例: example.com → staging-a3f9c2kp.example.com
23
+ */
24
+ export declare function generateStagingDomain(productionUrl: string, pagesProjectName: string): string;
25
+ export declare function generateConfigJson(config: ProjectConfig): string;
26
+ export declare function generateWranglerToml(config: ProjectConfig): string;
27
+ export declare function generateUsersIndex(config: ProjectConfig): string;
28
+ export declare function generateContentTypesJson(): string;
29
+ export declare function generateVersionJson(): string;
30
+ export declare function generateRedirects(): string;
31
+ export declare function generateRoutesJson(): string;
32
+ export declare function generateStorageFunction(): string;
33
+ export declare function generateViteConfig(): string;
34
+ export declare function generatePackageJson(config: ProjectConfig): string;
35
+ export declare function generateTsConfig(): string;
36
+ /**
37
+ * ファイルを書き込む(ディレクトリがなければ作成)
38
+ */
39
+ export declare function writeFile(filePath: string, content: string): void;
40
+ /**
41
+ * .gitkeep を置く
42
+ */
43
+ export declare function ensureDir(dirPath: string): void;
@@ -0,0 +1,211 @@
1
+ /**
2
+ * テンプレートファイル生成
3
+ */
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ /**
7
+ * ランダムな8文字のハッシュを生成(URL推測を困難にする)
8
+ */
9
+ export function generateHash() {
10
+ const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
11
+ let hash = '';
12
+ const bytes = new Uint8Array(8);
13
+ crypto.getRandomValues(bytes);
14
+ for (const b of bytes) {
15
+ hash += chars[b % chars.length];
16
+ }
17
+ return hash;
18
+ }
19
+ /**
20
+ * ドメインからステージング用のデフォルトサブドメインを生成
21
+ * 例: example.com → staging-a3f9c2kp.example.com
22
+ */
23
+ export function generateStagingDomain(productionUrl, pagesProjectName) {
24
+ const hash = generateHash();
25
+ try {
26
+ const url = new URL(productionUrl);
27
+ return `staging-${hash}.${url.hostname}`;
28
+ }
29
+ catch {
30
+ return `${pagesProjectName}-staging-${hash}.pages.dev`;
31
+ }
32
+ }
33
+ export function generateConfigJson(config) {
34
+ return JSON.stringify({
35
+ contentRepo: config.contentRepo,
36
+ githubClientId: config.githubClientId,
37
+ fileUploadUrl: '/api/storage',
38
+ siteTitle: config.siteTitle,
39
+ productionUrl: config.productionUrl,
40
+ previewUrl: config.previewUrl,
41
+ }, null, 2);
42
+ }
43
+ export function generateWranglerToml(config) {
44
+ return `name = "${config.pagesProjectName}"
45
+ compatibility_date = "2024-01-01"
46
+
47
+ [[r2_buckets]]
48
+ binding = "R2"
49
+ bucket_name = "${config.r2BucketName}"
50
+
51
+ [vars]
52
+ R2_PUBLIC_URL = "${config.r2PublicUrl}"
53
+ `;
54
+ }
55
+ export function generateUsersIndex(config) {
56
+ const owner = config.ownerUsername;
57
+ return JSON.stringify({
58
+ users: [
59
+ {
60
+ githubUsername: owner,
61
+ displayName: owner,
62
+ isOwner: true,
63
+ permissions: {
64
+ canViewUsers: true,
65
+ canEditUsers: true,
66
+ canViewStaging: true,
67
+ canEditStaging: true,
68
+ canViewContentType: true,
69
+ canEditContentType: true,
70
+ canViewBuildRecipes: true,
71
+ canEditBuildRecipes: true,
72
+ canViewTemplates: true,
73
+ canEditTemplates: true,
74
+ canViewStorage: true,
75
+ canEditStorage: true,
76
+ canViewDeploy: true,
77
+ canEditDeploy: true,
78
+ },
79
+ },
80
+ ],
81
+ stagingBranches: [],
82
+ }, null, 2);
83
+ }
84
+ export function generateContentTypesJson() {
85
+ return JSON.stringify([], null, 2);
86
+ }
87
+ export function generateVersionJson() {
88
+ return JSON.stringify({ version: '0.0.0' }, null, 2);
89
+ }
90
+ export function generateRedirects() {
91
+ // 順序重要: 先にマッチしたルールが適用される
92
+ return [
93
+ '/admin/* /admin/index.html 200',
94
+ '/* /public/:splat 200',
95
+ '',
96
+ ].join('\n');
97
+ }
98
+ export function generateRoutesJson() {
99
+ return JSON.stringify({
100
+ version: 1,
101
+ include: ['/api/*', '/storage/*'],
102
+ exclude: [],
103
+ }, null, 2);
104
+ }
105
+ export function generateStorageFunction() {
106
+ return `interface Env {
107
+ R2: R2Bucket
108
+ }
109
+
110
+ export const onRequest: PagesFunction<Env> = async (context) => {
111
+ const path = context.params.path
112
+ const key = Array.isArray(path) ? path.join('/') : path
113
+
114
+ if (!key) {
115
+ return new Response('Not Found', { status: 404 })
116
+ }
117
+
118
+ const object = await context.env.R2.get(key)
119
+ if (!object) {
120
+ return new Response('Not Found', { status: 404 })
121
+ }
122
+
123
+ const headers = new Headers()
124
+ headers.set(
125
+ 'Content-Type',
126
+ object.httpMetadata?.contentType || 'application/octet-stream',
127
+ )
128
+ headers.set('Cache-Control', 'public, max-age=31536000, immutable')
129
+ headers.set('ETag', object.httpEtag)
130
+
131
+ return new Response(object.body, { status: 200, headers })
132
+ }
133
+ `;
134
+ }
135
+ export function generateViteConfig() {
136
+ return `import { defineConfig } from 'vite'
137
+ import { resolve } from 'path'
138
+
139
+ const templateAssets = resolve(__dirname, 'frelio-data/site/templates/assets')
140
+
141
+ export default defineConfig(({ command }) => ({
142
+ root: 'frelio-data/site/templates',
143
+ publicDir: command === 'serve' ? resolve(__dirname, 'public') : false,
144
+ build: {
145
+ outDir: resolve(__dirname, 'public'),
146
+ emptyOutDir: false,
147
+ rollupOptions: {
148
+ input: {
149
+ style: resolve(templateAssets, 'scss/style.scss'),
150
+ index: resolve(templateAssets, 'ts/index.ts'),
151
+ },
152
+ output: {
153
+ entryFileNames: 'assets/[name].js',
154
+ assetFileNames: 'assets/[name].[ext]',
155
+ },
156
+ },
157
+ },
158
+ server: {
159
+ open: '/index.html',
160
+ },
161
+ }))
162
+ `;
163
+ }
164
+ export function generatePackageJson(config) {
165
+ return JSON.stringify({
166
+ name: config.pagesProjectName,
167
+ private: true,
168
+ type: 'module',
169
+ scripts: {
170
+ dev: 'vite',
171
+ build: 'vite build',
172
+ preview: 'vite preview',
173
+ },
174
+ devDependencies: {
175
+ vite: '^6.0.0',
176
+ sass: '^1.80.0',
177
+ typescript: '^5.7.0',
178
+ },
179
+ }, null, 2);
180
+ }
181
+ export function generateTsConfig() {
182
+ return JSON.stringify({
183
+ compilerOptions: {
184
+ target: 'ES2022',
185
+ module: 'ESNext',
186
+ moduleResolution: 'Bundler',
187
+ lib: ['ES2022', 'DOM', 'DOM.Iterable'],
188
+ strict: true,
189
+ skipLibCheck: true,
190
+ esModuleInterop: true,
191
+ },
192
+ include: ['frelio-data/site/templates/assets/ts'],
193
+ }, null, 2);
194
+ }
195
+ /**
196
+ * ファイルを書き込む(ディレクトリがなければ作成)
197
+ */
198
+ export function writeFile(filePath, content) {
199
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
200
+ fs.writeFileSync(filePath, content, 'utf-8');
201
+ }
202
+ /**
203
+ * .gitkeep を置く
204
+ */
205
+ export function ensureDir(dirPath) {
206
+ fs.mkdirSync(dirPath, { recursive: true });
207
+ const gitkeep = path.join(dirPath, '.gitkeep');
208
+ if (!fs.existsSync(gitkeep)) {
209
+ fs.writeFileSync(gitkeep, '', 'utf-8');
210
+ }
211
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@c-time/frelio-cli",
3
+ "version": "0.1.0",
4
+ "description": "Frelio CMS setup CLI",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "frelio": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "templates"
13
+ ],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "engines": {
18
+ "node": ">=20.0.0"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "dev": "tsc --watch",
23
+ "prepublishOnly": "npm run build"
24
+ },
25
+ "dependencies": {
26
+ "commander": "^13.0.0",
27
+ "prompts": "^2.4.2",
28
+ "tar": "^7.0.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/prompts": "^2.4.9",
32
+ "@types/node": "^22.0.0",
33
+ "typescript": "^5.7.0"
34
+ }
35
+ }