@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 +18 -14
- package/dist/commands/init.js +4 -16
- package/dist/core/content-structure.d.ts +8 -14
- package/dist/core/content-structure.js +9 -112
- package/dist/core/index.d.ts +2 -3
- package/dist/core/index.js +3 -3
- package/dist/core/template-scaffold.d.ts +14 -0
- package/dist/core/template-scaffold.js +99 -0
- package/dist/core/workflows.js +181 -16
- package/dist/lib/github-release.d.ts +15 -0
- package/dist/lib/github-release.js +41 -0
- package/dist/lib/initial-content.js +87 -55
- package/dist/lib/template-renderer.d.ts +16 -0
- package/dist/lib/template-renderer.js +32 -0
- package/dist/lib/templates.d.ts +0 -8
- package/dist/lib/templates.js +0 -227
- package/package.json +2 -3
package/dist/core/workflows.js
CHANGED
|
@@ -66,7 +66,9 @@ jobs:
|
|
|
66
66
|
command: pages deploy dist --project-name=${config.adminPagesProjectName}
|
|
67
67
|
`;
|
|
68
68
|
case 'build-staging':
|
|
69
|
-
return
|
|
69
|
+
return `# staging ブランチ(デフォルト + カスタム)への push で発火し、SSG を実行する。
|
|
70
|
+
# カスタム staging(staging-*)は artifact アップロードなし(プレビュー専用)。
|
|
71
|
+
name: Build Staging
|
|
70
72
|
on:
|
|
71
73
|
push:
|
|
72
74
|
branches: [staging, 'staging-*']
|
|
@@ -83,6 +85,8 @@ jobs:
|
|
|
83
85
|
with:
|
|
84
86
|
node-version: 20
|
|
85
87
|
cache: 'npm'
|
|
88
|
+
|
|
89
|
+
# 前回のビルド状態を復元(差分ビルド用)
|
|
86
90
|
- name: Restore SSG cache
|
|
87
91
|
uses: actions/cache@v4
|
|
88
92
|
with:
|
|
@@ -92,9 +96,22 @@ jobs:
|
|
|
92
96
|
key: ssg-cache-\${{ github.ref_name }}-\${{ github.sha }}
|
|
93
97
|
restore-keys: |
|
|
94
98
|
ssg-cache-\${{ github.ref_name }}-
|
|
99
|
+
|
|
95
100
|
- run: npm ci
|
|
96
|
-
|
|
97
|
-
|
|
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 をアップロード(本番デプロイ用)
|
|
98
115
|
- name: Upload artifact
|
|
99
116
|
if: github.ref == 'refs/heads/staging'
|
|
100
117
|
uses: actions/upload-artifact@v4
|
|
@@ -102,6 +119,8 @@ jobs:
|
|
|
102
119
|
name: staging-build
|
|
103
120
|
path: public/
|
|
104
121
|
retention-days: 14
|
|
122
|
+
|
|
123
|
+
# Cloudflare Pages にブランチプレビューとしてデプロイ
|
|
105
124
|
- name: Deploy preview
|
|
106
125
|
uses: cloudflare/wrangler-action@v3
|
|
107
126
|
with:
|
|
@@ -110,7 +129,10 @@ jobs:
|
|
|
110
129
|
command: pages deploy public --project-name=${config.pagesProjectName} --branch=\${{ github.ref_name }}
|
|
111
130
|
`;
|
|
112
131
|
case 'promote-production':
|
|
113
|
-
return
|
|
132
|
+
return `# staging → main マージ(push to main)で発火。
|
|
133
|
+
# 最新の staging artifact をダウンロードし、Pages 本番にデプロイする。
|
|
134
|
+
# artifact 欠損時はフォールバックとしてフル SSG ビルドを実行する。
|
|
135
|
+
name: Promote to Production
|
|
114
136
|
on:
|
|
115
137
|
push:
|
|
116
138
|
branches: [main]
|
|
@@ -126,28 +148,101 @@ jobs:
|
|
|
126
148
|
- uses: actions/checkout@v4
|
|
127
149
|
with:
|
|
128
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 ダウンロード
|
|
129
173
|
- name: Download staging artifact
|
|
174
|
+
id: download-artifact
|
|
175
|
+
if: steps.find-run.outputs.run-id != ''
|
|
176
|
+
continue-on-error: true
|
|
130
177
|
uses: actions/download-artifact@v4
|
|
131
178
|
with:
|
|
132
179
|
name: staging-build
|
|
133
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 本番にデプロイ
|
|
134
215
|
- name: Deploy to production
|
|
135
216
|
uses: cloudflare/wrangler-action@v3
|
|
136
217
|
with:
|
|
137
218
|
apiToken: \${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
138
219
|
accountId: \${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
139
220
|
command: pages deploy public --project-name=${config.pagesProjectName} --branch=main
|
|
221
|
+
|
|
222
|
+
# デプロイタグ作成
|
|
140
223
|
- name: Create deploy tag
|
|
141
224
|
run: |
|
|
142
|
-
VERSION=\$(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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"
|
|
147
237
|
git push origin "\$TAG"
|
|
238
|
+
|
|
239
|
+
echo "::notice::Tagged as \$TAG"
|
|
148
240
|
`;
|
|
149
241
|
case 'direct-deploy':
|
|
150
|
-
return
|
|
242
|
+
return `# CMS の「直接デプロイ」ボタンから workflow_dispatch で発火。
|
|
243
|
+
# develop → staging マージ → SSG ビルド → artifact → staging プレビュー →
|
|
244
|
+
# staging → main マージ → 本番デプロイ → デプロイタグ付与
|
|
245
|
+
name: Direct Deploy
|
|
151
246
|
on:
|
|
152
247
|
workflow_dispatch:
|
|
153
248
|
|
|
@@ -155,26 +250,96 @@ permissions:
|
|
|
155
250
|
contents: write
|
|
156
251
|
|
|
157
252
|
jobs:
|
|
158
|
-
|
|
253
|
+
build:
|
|
159
254
|
runs-on: ubuntu-latest
|
|
160
255
|
steps:
|
|
161
256
|
- uses: actions/checkout@v4
|
|
162
257
|
with:
|
|
163
258
|
fetch-depth: 0
|
|
164
|
-
|
|
259
|
+
|
|
260
|
+
- name: Configure git
|
|
165
261
|
run: |
|
|
166
262
|
git config user.name "github-actions[bot]"
|
|
167
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: |
|
|
168
268
|
git checkout staging
|
|
169
269
|
git merge origin/develop --no-edit
|
|
170
270
|
git push origin staging
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
-
|
|
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
|
|
174
318
|
run: |
|
|
175
319
|
git checkout main
|
|
176
|
-
git merge
|
|
320
|
+
git merge --ff-only staging
|
|
177
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"
|
|
178
343
|
`;
|
|
179
344
|
}
|
|
180
345
|
}
|
|
@@ -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
|
+
}
|
|
@@ -111,8 +111,8 @@ export function generateInitialContent(projectDir) {
|
|
|
111
111
|
details: [
|
|
112
112
|
{
|
|
113
113
|
type: 'detail',
|
|
114
|
-
outputPath: 'news/{$this.slug}
|
|
115
|
-
templatePath: 'news/
|
|
114
|
+
outputPath: 'news/{$this.slug}.json',
|
|
115
|
+
templatePath: 'news/detail.html',
|
|
116
116
|
},
|
|
117
117
|
],
|
|
118
118
|
lists: [
|
|
@@ -206,8 +206,8 @@ function generateTemplates(projectDir) {
|
|
|
206
206
|
<meta charset="UTF-8">
|
|
207
207
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
208
208
|
<title>Frelio Demo</title>
|
|
209
|
-
<link rel="stylesheet" href="/
|
|
210
|
-
<script type="module" src="/
|
|
209
|
+
<link rel="stylesheet" href="/styles/index.css">
|
|
210
|
+
<script type="module" src="/scripts/index.js"></script>
|
|
211
211
|
</head>
|
|
212
212
|
<body>
|
|
213
213
|
<template data-gen-scope="" data-gen-include="_parts/header.htm"></template>
|
|
@@ -274,8 +274,8 @@ function generateTemplates(projectDir) {
|
|
|
274
274
|
</body>
|
|
275
275
|
</html>
|
|
276
276
|
`);
|
|
277
|
-
// about.html
|
|
278
|
-
writeFile(t('about.html'), `<!DOCTYPE html>
|
|
277
|
+
// about/index.html
|
|
278
|
+
writeFile(t('about/index.html'), `<!DOCTYPE html>
|
|
279
279
|
<html lang="ja">
|
|
280
280
|
<head>
|
|
281
281
|
<template data-gen-scope="" data-gen-include="_parts/head.htm"></template>
|
|
@@ -344,8 +344,8 @@ function generateTemplates(projectDir) {
|
|
|
344
344
|
</body>
|
|
345
345
|
</html>
|
|
346
346
|
`);
|
|
347
|
-
// contact.html
|
|
348
|
-
writeFile(t('contact.html'), `<!DOCTYPE html>
|
|
347
|
+
// contact/index.html
|
|
348
|
+
writeFile(t('contact/index.html'), `<!DOCTYPE html>
|
|
349
349
|
<html lang="ja">
|
|
350
350
|
<head>
|
|
351
351
|
<template data-gen-scope="" data-gen-include="_parts/head.htm"></template>
|
|
@@ -472,16 +472,16 @@ function generateTemplates(projectDir) {
|
|
|
472
472
|
</body>
|
|
473
473
|
</html>
|
|
474
474
|
`);
|
|
475
|
-
// news/
|
|
476
|
-
writeFile(t('news/
|
|
475
|
+
// news/detail.html
|
|
476
|
+
writeFile(t('news/detail.html'), `<!DOCTYPE html>
|
|
477
477
|
<html lang="ja">
|
|
478
478
|
<head>
|
|
479
479
|
<template data-gen-scope="" data-gen-include="_parts/head.htm"></template>
|
|
480
480
|
<meta charset="UTF-8">
|
|
481
481
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
482
482
|
<title>記事タイトル | Frelio Demo</title>
|
|
483
|
-
<link rel="stylesheet" href="/news/
|
|
484
|
-
<script type="module" src="/news/
|
|
483
|
+
<link rel="stylesheet" href="/news/styles/index.css">
|
|
484
|
+
<script type="module" src="/news/scripts/index.js"></script>
|
|
485
485
|
</head>
|
|
486
486
|
<body>
|
|
487
487
|
<template data-gen-scope="" data-gen-include="_parts/header.htm"></template>
|
|
@@ -535,10 +535,21 @@ function generateTemplates(projectDir) {
|
|
|
535
535
|
</body>
|
|
536
536
|
</html>
|
|
537
537
|
`);
|
|
538
|
+
// .gitkeep for images directories
|
|
539
|
+
const gitkeepDirs = [
|
|
540
|
+
'common/images',
|
|
541
|
+
'images',
|
|
542
|
+
'about/images',
|
|
543
|
+
'contact/images',
|
|
544
|
+
'news/images',
|
|
545
|
+
];
|
|
546
|
+
for (const dir of gitkeepDirs) {
|
|
547
|
+
writeFile(t(`${dir}/.gitkeep`), '');
|
|
548
|
+
}
|
|
538
549
|
}
|
|
539
550
|
// ========== SCSS ==========
|
|
540
551
|
function generateScss(projectDir) {
|
|
541
|
-
const s = (...p) => path.join(projectDir, 'frelio-data/site/templates/
|
|
552
|
+
const s = (...p) => path.join(projectDir, 'frelio-data/site/templates/common/styles', ...p);
|
|
542
553
|
// Foundation
|
|
543
554
|
writeFile(s('foundation/_variables.scss'), `// Colors
|
|
544
555
|
$color-primary: #0070f3;
|
|
@@ -1195,7 +1206,7 @@ button {
|
|
|
1195
1206
|
}
|
|
1196
1207
|
// ========== TypeScript ==========
|
|
1197
1208
|
function generateTypeScript(projectDir) {
|
|
1198
|
-
const ts = (...p) => path.join(projectDir, 'frelio-data/site/templates/
|
|
1209
|
+
const ts = (...p) => path.join(projectDir, 'frelio-data/site/templates/common/scripts', ...p);
|
|
1199
1210
|
writeFile(ts('features/mobile-nav.ts'), `export function initMobileNav(): void {
|
|
1200
1211
|
const toggle = document.querySelector<HTMLButtonElement>('.c-nav__toggle')
|
|
1201
1212
|
const list = document.querySelector<HTMLUListElement>('.c-nav__list')
|
|
@@ -1238,9 +1249,9 @@ function generateTypeScript(projectDir) {
|
|
|
1238
1249
|
}
|
|
1239
1250
|
// ========== Entry Points ==========
|
|
1240
1251
|
function generateEntries(projectDir) {
|
|
1241
|
-
const
|
|
1252
|
+
const t = (...p) => path.join(projectDir, 'frelio-data/site/templates', ...p);
|
|
1242
1253
|
// common
|
|
1243
|
-
writeFile(
|
|
1254
|
+
writeFile(t('common/styles/index.scss'), `// Foundation
|
|
1244
1255
|
@use 'foundation/variables' as *;
|
|
1245
1256
|
@use 'foundation/mixins' as *;
|
|
1246
1257
|
@use 'foundation/reset';
|
|
@@ -1260,55 +1271,48 @@ function generateEntries(projectDir) {
|
|
|
1260
1271
|
// Element
|
|
1261
1272
|
@use 'element/e-heading';
|
|
1262
1273
|
`);
|
|
1263
|
-
writeFile(
|
|
1264
|
-
import { initMobileNav } from '
|
|
1265
|
-
import { initSmoothScroll } from '
|
|
1274
|
+
writeFile(t('common/scripts/index.ts'), `import '../styles/index.scss'
|
|
1275
|
+
import { initMobileNav } from './features/mobile-nav'
|
|
1276
|
+
import { initSmoothScroll } from './features/smooth-scroll'
|
|
1266
1277
|
|
|
1267
1278
|
document.addEventListener('DOMContentLoaded', () => {
|
|
1268
1279
|
initMobileNav()
|
|
1269
1280
|
initSmoothScroll()
|
|
1270
1281
|
})
|
|
1271
1282
|
`);
|
|
1272
|
-
// home
|
|
1273
|
-
writeFile(
|
|
1283
|
+
// home (root level — / maps to templates root)
|
|
1284
|
+
writeFile(t('styles/index.scss'), `@use 'foundation/variables' as *;
|
|
1274
1285
|
@use 'foundation/mixins' as *;
|
|
1275
1286
|
|
|
1276
1287
|
@use 'project/p-hero';
|
|
1277
1288
|
@use 'project/p-news-list';
|
|
1278
1289
|
`);
|
|
1279
|
-
writeFile(
|
|
1290
|
+
writeFile(t('scripts/index.ts'), `import '../styles/index.scss'
|
|
1280
1291
|
`);
|
|
1281
1292
|
// about
|
|
1282
|
-
writeFile(
|
|
1293
|
+
writeFile(t('about/styles/index.scss'), `@use 'foundation/variables' as *;
|
|
1283
1294
|
@use 'foundation/mixins' as *;
|
|
1284
1295
|
|
|
1285
1296
|
@use 'project/p-about';
|
|
1286
1297
|
`);
|
|
1287
|
-
writeFile(
|
|
1298
|
+
writeFile(t('about/scripts/index.ts'), `import '../styles/index.scss'
|
|
1288
1299
|
`);
|
|
1289
1300
|
// contact
|
|
1290
|
-
writeFile(
|
|
1301
|
+
writeFile(t('contact/styles/index.scss'), `@use 'foundation/variables' as *;
|
|
1291
1302
|
@use 'foundation/mixins' as *;
|
|
1292
1303
|
|
|
1293
1304
|
@use 'project/p-contact';
|
|
1294
1305
|
`);
|
|
1295
|
-
writeFile(
|
|
1306
|
+
writeFile(t('contact/scripts/index.ts'), `import '../styles/index.scss'
|
|
1296
1307
|
`);
|
|
1297
|
-
// news
|
|
1298
|
-
writeFile(
|
|
1308
|
+
// news (list + detail share the same scripts/styles)
|
|
1309
|
+
writeFile(t('news/styles/index.scss'), `@use 'foundation/variables' as *;
|
|
1299
1310
|
@use 'foundation/mixins' as *;
|
|
1300
1311
|
|
|
1301
1312
|
@use 'project/p-news-list';
|
|
1302
|
-
`);
|
|
1303
|
-
writeFile(e('news/scripts/index.ts'), `import '../styles/index.scss'
|
|
1304
|
-
`);
|
|
1305
|
-
// news/detail
|
|
1306
|
-
writeFile(e('news/detail/styles/index.scss'), `@use 'foundation/variables' as *;
|
|
1307
|
-
@use 'foundation/mixins' as *;
|
|
1308
|
-
|
|
1309
1313
|
@use 'project/p-article';
|
|
1310
1314
|
`);
|
|
1311
|
-
writeFile(
|
|
1315
|
+
writeFile(t('news/scripts/index.ts'), `import '../styles/index.scss'
|
|
1312
1316
|
`);
|
|
1313
1317
|
}
|
|
1314
1318
|
// ========== Build Scripts ==========
|
|
@@ -2071,13 +2075,13 @@ Frelio(ヘッドレス CMS)で構築されたサイトリポジトリ。
|
|
|
2071
2075
|
## プロジェクト構成
|
|
2072
2076
|
|
|
2073
2077
|
- \`frelio-data/\` — CMS データ(コンテンツタイプ、コンテンツ、テンプレート、レシピ)
|
|
2074
|
-
- \`site/templates
|
|
2075
|
-
- \`site/templates/
|
|
2076
|
-
- \`site/templates/
|
|
2077
|
-
- \`
|
|
2078
|
+
- \`site/templates/\` — テンプレート(配置 = 出力先 URL 構造)
|
|
2079
|
+
- \`site/templates/common/styles/\` — 共有 SCSS パーシャル(FLOCSS 亜種)
|
|
2080
|
+
- \`site/templates/common/scripts/\` — 共有 TypeScript(features/)
|
|
2081
|
+
- \`site/data/data-json/\` — SSG 中間データ(git 追跡対象)
|
|
2082
|
+
- \`public/\` — SSG 出力(HTML + ビルド済みアセット、git 管理外)
|
|
2078
2083
|
- \`functions/storage/\` — R2 ファイル配信(/storage/*)
|
|
2079
2084
|
- \`scripts/\` — ビルドスクリプト(tsx)
|
|
2080
|
-
- \`public/images/\` — 静的ファイル(画像等、git 追跡対象)
|
|
2081
2085
|
|
|
2082
2086
|
CMS 管理画面関連(\`admin/\`, \`functions/api/\`, \`workers/\`, \`wrangler.toml\`, \`_redirects\`)は
|
|
2083
2087
|
\`npx @frelio/cli update\` で追加・更新される。
|
|
@@ -2086,7 +2090,7 @@ CMS 管理画面関連(\`admin/\`, \`functions/api/\`, \`workers/\`, \`wrangle
|
|
|
2086
2090
|
|
|
2087
2091
|
\`\`\`bash
|
|
2088
2092
|
npm run dev # Vite dev server(テンプレートプレビュー + コンテンツ監視)
|
|
2089
|
-
npm run build # SCSS/TS ビルド(ページ別エントリー)
|
|
2093
|
+
npm run build # 静的アセットコピー + SCSS/TS ビルド(ページ別エントリー)
|
|
2090
2094
|
npm run generate # data-json 生成(差分ビルド)
|
|
2091
2095
|
npm run generate:full # data-json 生成(フルリビルド)
|
|
2092
2096
|
npm run generate:html # HTML 生成(data-json → public/)
|
|
@@ -2108,23 +2112,50 @@ npx @frelio/cli add-staging # カスタムステージング追加
|
|
|
2108
2112
|
5. sitemap.xml 生成 (npm run generate:sitemap)
|
|
2109
2113
|
\`\`\`
|
|
2110
2114
|
|
|
2111
|
-
##
|
|
2115
|
+
## テンプレート構造(= URL 構造)
|
|
2112
2116
|
|
|
2113
|
-
|
|
2117
|
+
テンプレートの配置がそのまま出力先の URL パスになる。各ページに \`styles/index.scss\` と \`scripts/index.ts\` がある。
|
|
2114
2118
|
|
|
2115
2119
|
\`\`\`
|
|
2116
|
-
|
|
2117
|
-
├──
|
|
2118
|
-
├──
|
|
2119
|
-
├──
|
|
2120
|
-
├──
|
|
2121
|
-
|
|
2122
|
-
|
|
2120
|
+
frelio-data/site/templates/
|
|
2121
|
+
├── _parts/ — 共通パーツ(head.htm, header.htm, footer.htm)
|
|
2122
|
+
├── common/ — 全ページ共通
|
|
2123
|
+
│ ├── scripts/ — JS 初期化 + features/
|
|
2124
|
+
│ ├── styles/ — SCSS パーシャル(FLOCSS: foundation/, layout/, component/, element/, project/)
|
|
2125
|
+
│ └── images/ — favicon, logo 等
|
|
2126
|
+
├── index.html — / (ホーム)
|
|
2127
|
+
├── scripts/index.ts — ホーム用 JS
|
|
2128
|
+
├── styles/index.scss — ホーム用 CSS(p-hero, p-news-list)
|
|
2129
|
+
├── images/ — ホーム画像
|
|
2130
|
+
├── about/ — /about/
|
|
2131
|
+
│ ├── index.html
|
|
2132
|
+
│ ├── scripts/index.ts
|
|
2133
|
+
│ ├── styles/index.scss(p-about)
|
|
2134
|
+
│ └── images/
|
|
2135
|
+
├── contact/ — /contact/
|
|
2136
|
+
│ ├── index.html
|
|
2137
|
+
│ ├── scripts/index.ts
|
|
2138
|
+
│ ├── styles/index.scss(p-contact)
|
|
2139
|
+
│ └── images/
|
|
2140
|
+
└── news/ — /news/
|
|
2141
|
+
├── index.html — 一覧テンプレート
|
|
2142
|
+
├── detail.html — 詳細テンプレート(レシピで news/{slug}.html に展開)
|
|
2143
|
+
├── scripts/index.ts — 一覧・詳細で共有
|
|
2144
|
+
├── styles/index.scss(p-news-list, p-article)
|
|
2145
|
+
└── images/
|
|
2123
2146
|
\`\`\`
|
|
2124
2147
|
|
|
2125
2148
|
- \`_parts/head.htm\` で common の CSS/JS を読み込み
|
|
2126
2149
|
- 各ページテンプレートでページ固有の CSS/JS を読み込み
|
|
2127
2150
|
|
|
2151
|
+
### スラッグ展開
|
|
2152
|
+
|
|
2153
|
+
テンプレートファイル名と出力パスの対応はレシピ(\`build-data-recipe.json\`)で制御する。
|
|
2154
|
+
gentl の規約ではなく、レシピの書き方次第。
|
|
2155
|
+
|
|
2156
|
+
- **ファイルベース**: \`news/detail.html\` → \`news/{slug}.html\`(現在の設定)
|
|
2157
|
+
- **ディレクトリベース**: \`news/_detail/index.html\` → \`news/{slug}/index.html\`(別方式、必要に応じて変更可)
|
|
2158
|
+
|
|
2128
2159
|
## CSS 記法ルール(FLOCSS 亜種・厳格)
|
|
2129
2160
|
|
|
2130
2161
|
- **プレフィックス**: \`l-\`(layout)、\`c-\`(component)、\`p-\`(project)、\`e-\`(element)のみ
|
|
@@ -2145,16 +2176,17 @@ assets/entries/
|
|
|
2145
2176
|
|
|
2146
2177
|
## TypeScript ルール
|
|
2147
2178
|
|
|
2148
|
-
- 共通の初期化ロジックは \`
|
|
2149
|
-
- 各機能は \`
|
|
2150
|
-
- ページ固有の JS が必要な場合は \`
|
|
2179
|
+
- 共通の初期化ロジックは \`common/scripts/index.ts\` に集約
|
|
2180
|
+
- 各機能は \`common/scripts/features/\` にファイル分離
|
|
2181
|
+
- ページ固有の JS が必要な場合は \`{page}/scripts/index.ts\` に追加
|
|
2151
2182
|
|
|
2152
2183
|
## テンプレート規約
|
|
2153
2184
|
|
|
2154
2185
|
- テンプレートエンジン: gentl(\`data-gen-*\` 属性ベース)
|
|
2155
2186
|
- テンプレートは valid HTML(そのままブラウザで開ける)
|
|
2156
2187
|
- 共通パーツ: \`_parts/*.htm\`(head, header, footer)
|
|
2157
|
-
- ページテンプレート:
|
|
2188
|
+
- ページテンプレート: \`{page}/index.html\`(ホームは \`index.html\`)
|
|
2189
|
+
- 詳細テンプレート: \`{page}/detail.html\`(レシピでスラッグ展開)
|
|
2158
2190
|
|
|
2159
2191
|
## Cloudflare Pages 構成
|
|
2160
2192
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* テンプレート変数置換
|
|
3
|
+
*
|
|
4
|
+
* {{variable}} 形式のプレースホルダーを値に置換する。
|
|
5
|
+
* GitHub Actions の ${{ expression }} とは衝突しない。
|
|
6
|
+
*/
|
|
7
|
+
import type { ProjectConfig } from './templates.js';
|
|
8
|
+
export type TemplateVariables = Record<string, string>;
|
|
9
|
+
/**
|
|
10
|
+
* テンプレート文字列内の {{variable}} を置換する
|
|
11
|
+
*/
|
|
12
|
+
export declare function renderTemplate(content: string, vars: TemplateVariables): string;
|
|
13
|
+
/**
|
|
14
|
+
* ProjectConfig からテンプレート変数マップを生成する
|
|
15
|
+
*/
|
|
16
|
+
export declare function projectConfigToVars(config: ProjectConfig): TemplateVariables;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* テンプレート変数置換
|
|
3
|
+
*
|
|
4
|
+
* {{variable}} 形式のプレースホルダーを値に置換する。
|
|
5
|
+
* GitHub Actions の ${{ expression }} とは衝突しない。
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* テンプレート文字列内の {{variable}} を置換する
|
|
9
|
+
*/
|
|
10
|
+
export function renderTemplate(content, vars) {
|
|
11
|
+
return content.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
12
|
+
return key in vars ? vars[key] : match;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* ProjectConfig からテンプレート変数マップを生成する
|
|
17
|
+
*/
|
|
18
|
+
export function projectConfigToVars(config) {
|
|
19
|
+
return {
|
|
20
|
+
contentRepo: config.contentRepo,
|
|
21
|
+
githubClientId: config.githubClientId,
|
|
22
|
+
siteTitle: config.siteTitle,
|
|
23
|
+
productionUrl: config.productionUrl,
|
|
24
|
+
previewUrl: config.previewUrl,
|
|
25
|
+
fileUploadUrl: '/api/storage',
|
|
26
|
+
pagesProjectName: config.pagesProjectName,
|
|
27
|
+
adminPagesProjectName: config.adminPagesProjectName,
|
|
28
|
+
r2BucketName: config.r2BucketName,
|
|
29
|
+
r2PublicUrl: config.r2PublicUrl,
|
|
30
|
+
ownerUsername: config.ownerUsername,
|
|
31
|
+
};
|
|
32
|
+
}
|