create-bike-blog 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.
- package/index.js +154 -0
- package/package.json +11 -0
- package/sync.js +104 -0
- package/templates/astro.config.mjs +17 -0
- package/templates/blog/config.yml.tpl +24 -0
- package/templates/blog/pages/about.md +4 -0
- package/templates/env.tpl +4 -0
- package/templates/flake.nix.tpl +8 -0
- package/templates/gitattributes +1 -0
- package/templates/github/workflows/ci.yml.tpl +56 -0
- package/templates/github/workflows/deploy.yml.tpl +106 -0
- package/templates/github/workflows/update.yml.tpl +59 -0
- package/templates/gitignore +6 -0
- package/templates/package.json.tpl +22 -0
- package/templates/scripts/setup.js +748 -0
- package/templates/src/content.config.ts +3 -0
- package/templates/src/middleware.ts +3 -0
- package/templates/tsconfig.json +9 -0
- package/templates/wrangler.jsonc.tpl +34 -0
package/index.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
import readline from 'node:readline';
|
|
7
|
+
|
|
8
|
+
// --- Template engine ---
|
|
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
|
+
// --- File operations ---
|
|
17
|
+
|
|
18
|
+
function copyTemplate(templateDir, destDir, vars) {
|
|
19
|
+
for (const entry of fs.readdirSync(templateDir, { withFileTypes: true })) {
|
|
20
|
+
const srcPath = path.join(templateDir, entry.name);
|
|
21
|
+
const destName = entry.name.replace(/\.tpl$/, '');
|
|
22
|
+
const destPath = path.join(destDir, destName);
|
|
23
|
+
|
|
24
|
+
if (entry.isDirectory()) {
|
|
25
|
+
fs.mkdirSync(destPath, { recursive: true });
|
|
26
|
+
copyTemplate(srcPath, destPath, vars);
|
|
27
|
+
} else {
|
|
28
|
+
let content = fs.readFileSync(srcPath, 'utf-8');
|
|
29
|
+
if (entry.name.endsWith('.tpl')) {
|
|
30
|
+
content = renderTemplate(content, vars);
|
|
31
|
+
}
|
|
32
|
+
fs.writeFileSync(destPath, content);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// --- Prompts ---
|
|
38
|
+
|
|
39
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
40
|
+
const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
41
|
+
|
|
42
|
+
// --- Main ---
|
|
43
|
+
|
|
44
|
+
async function main() {
|
|
45
|
+
const args = process.argv.slice(2);
|
|
46
|
+
|
|
47
|
+
if (args.length < 2 || args.includes('--help') || args.includes('-h')) {
|
|
48
|
+
console.log(`
|
|
49
|
+
Usage: npx create-bike-blog <folder> <domain>
|
|
50
|
+
|
|
51
|
+
Example: npx create-bike-blog bike-blog eljojo.bike
|
|
52
|
+
`);
|
|
53
|
+
process.exit(args.includes('--help') || args.includes('-h') ? 0 : 1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const folder = args[0];
|
|
57
|
+
const domain = args[1];
|
|
58
|
+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
59
|
+
const destDir = path.resolve(folder);
|
|
60
|
+
|
|
61
|
+
if (fs.existsSync(destDir)) {
|
|
62
|
+
console.error(`\n Error: ${folder}/ already exists.\n`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(`
|
|
67
|
+
Welcome to whereto.bike!
|
|
68
|
+
|
|
69
|
+
Here's how setting up your blog works:
|
|
70
|
+
|
|
71
|
+
1. Bootstrap (you are here)
|
|
72
|
+
Creating your project in ${folder}/
|
|
73
|
+
|
|
74
|
+
2. Edit
|
|
75
|
+
Customize your config, write your about page, add some rides
|
|
76
|
+
|
|
77
|
+
3. npm run setup
|
|
78
|
+
Connects GitHub and Cloudflare so deploys work automatically
|
|
79
|
+
|
|
80
|
+
4. git push
|
|
81
|
+
Your blog is live at ${domain}!
|
|
82
|
+
`);
|
|
83
|
+
|
|
84
|
+
// Create project directory
|
|
85
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
86
|
+
|
|
87
|
+
const folderName = path.basename(destDir);
|
|
88
|
+
const vars = {
|
|
89
|
+
FOLDER: folderName,
|
|
90
|
+
DOMAIN: domain,
|
|
91
|
+
TIMEZONE: timezone,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Copy and render templates
|
|
95
|
+
const templateDir = new URL('./templates', import.meta.url).pathname;
|
|
96
|
+
copyTemplate(templateDir, destDir, vars);
|
|
97
|
+
|
|
98
|
+
// Rename dotfiles (npm strips .gitignore from published packages)
|
|
99
|
+
const renames = { gitignore: '.gitignore', gitattributes: '.gitattributes', env: '.env' };
|
|
100
|
+
for (const [from, to] of Object.entries(renames)) {
|
|
101
|
+
const src = path.join(destDir, from);
|
|
102
|
+
const dest = path.join(destDir, to);
|
|
103
|
+
if (fs.existsSync(src)) fs.renameSync(src, dest);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Move github/ to .github/
|
|
107
|
+
const githubSrc = path.join(destDir, 'github');
|
|
108
|
+
const githubDest = path.join(destDir, '.github');
|
|
109
|
+
if (fs.existsSync(githubSrc)) {
|
|
110
|
+
fs.renameSync(githubSrc, githubDest);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
console.log(` Files created in ${folder}/\n`);
|
|
115
|
+
|
|
116
|
+
// Install dependencies
|
|
117
|
+
const install = await ask(' Install dependencies? (npm install) [Y/n] ');
|
|
118
|
+
if (install.toLowerCase() !== 'n') {
|
|
119
|
+
try {
|
|
120
|
+
execSync('npm install', { cwd: destDir, stdio: 'inherit' });
|
|
121
|
+
} catch {
|
|
122
|
+
console.error('\n npm install failed. You can retry manually: cd ' + folder + ' && npm install\n');
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
console.log(`\n Skipped. Run it yourself:\n\n cd ${folder} && npm install\n`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Initialize git repo
|
|
129
|
+
const gitInit = await ask(' Initialize git repo? (git init + first commit) [Y/n] ');
|
|
130
|
+
if (gitInit.toLowerCase() !== 'n') {
|
|
131
|
+
execSync('git init', { cwd: destDir, stdio: 'pipe' });
|
|
132
|
+
execSync('git add -A', { cwd: destDir, stdio: 'pipe' });
|
|
133
|
+
execSync('git commit -m "initial blog scaffold"', { cwd: destDir, stdio: 'pipe' });
|
|
134
|
+
console.log('\n ✓ Git repo initialized with first commit.\n');
|
|
135
|
+
} else {
|
|
136
|
+
console.log(`\n Skipped. Run it yourself:\n\n cd ${folder} && git init && git add -A && git commit -m "initial blog scaffold"\n`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log(` Next steps:
|
|
140
|
+
|
|
141
|
+
cd ${folder}
|
|
142
|
+
# edit blog/config.yml and blog/pages/about.md
|
|
143
|
+
npm run setup
|
|
144
|
+
`);
|
|
145
|
+
|
|
146
|
+
rl.close();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Only run when invoked directly (not when imported for tests)
|
|
150
|
+
const isDirectRun = process.argv[1] &&
|
|
151
|
+
(process.argv[1].endsWith('/index.js') || process.argv[1].endsWith('/create-bike-blog'));
|
|
152
|
+
if (isDirectRun) {
|
|
153
|
+
main();
|
|
154
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-bike-blog",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold a new whereto.bike cycling blog",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-bike-blog": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": ["index.js", "sync.js", "templates/"],
|
|
10
|
+
"license": "AGPL-3.0"
|
|
11
|
+
}
|
package/sync.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Syncs template files from the whereto-bike package into your blog repo.
|
|
5
|
+
* Run from the blog repo: npm run sync
|
|
6
|
+
*
|
|
7
|
+
* Updates:
|
|
8
|
+
* - .github/workflows/ — CI/deploy/update actions (rendered from .tpl templates)
|
|
9
|
+
* - src/middleware.ts — re-export of package middleware
|
|
10
|
+
* - scripts/setup.js — interactive setup helper
|
|
11
|
+
* - astro.config.mjs — Astro config
|
|
12
|
+
* - tsconfig.json — TypeScript config
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import fs from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
|
|
18
|
+
export function renderTemplate(content, vars) {
|
|
19
|
+
return content.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
20
|
+
return key in vars ? vars[key] : match;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Read a top-level scalar from a simple YAML file. */
|
|
25
|
+
function readYamlField(filePath, field) {
|
|
26
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
27
|
+
const re = new RegExp(`^${field}:\\s*["']?([^"'\\n]+?)["']?\\s*$`, 'm');
|
|
28
|
+
const match = content.match(re);
|
|
29
|
+
return match ? match[1] : '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function syncFile(srcPath, destPath, vars) {
|
|
33
|
+
let content = fs.readFileSync(srcPath, 'utf-8');
|
|
34
|
+
if (srcPath.endsWith('.tpl')) {
|
|
35
|
+
content = renderTemplate(content, vars);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const existing = fs.existsSync(destPath) ? fs.readFileSync(destPath, 'utf-8') : null;
|
|
39
|
+
if (existing !== content) {
|
|
40
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
41
|
+
fs.writeFileSync(destPath, content);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// --- Main execution (only when run directly, not when imported) ---
|
|
48
|
+
const isMain = process.argv[1] && new URL(`file://${process.argv[1]}`).href === import.meta.url;
|
|
49
|
+
|
|
50
|
+
if (isMain) {
|
|
51
|
+
const cwd = process.cwd();
|
|
52
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf-8'));
|
|
53
|
+
|
|
54
|
+
const configPath = path.join(cwd, 'blog', 'config.yml');
|
|
55
|
+
if (!fs.existsSync(configPath)) {
|
|
56
|
+
console.error(` Error: blog/config.yml not found. Is this a bike blog repo?`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const vars = {
|
|
61
|
+
FOLDER: pkg.name,
|
|
62
|
+
DOMAIN: readYamlField(configPath, 'domain'),
|
|
63
|
+
TIMEZONE: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const templateRoot = new URL('./templates', import.meta.url).pathname;
|
|
67
|
+
let updated = 0;
|
|
68
|
+
|
|
69
|
+
// --- Sync CI workflows (.tpl rendered) ---
|
|
70
|
+
const workflowDir = path.join(templateRoot, 'github', 'workflows');
|
|
71
|
+
const outWorkflowDir = path.join(cwd, '.github', 'workflows');
|
|
72
|
+
for (const file of fs.readdirSync(workflowDir)) {
|
|
73
|
+
const outName = file.replace(/\.tpl$/, '');
|
|
74
|
+
if (syncFile(path.join(workflowDir, file), path.join(outWorkflowDir, outName), vars)) {
|
|
75
|
+
console.log(` updated .github/workflows/${outName}`);
|
|
76
|
+
updated++;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// --- Sync source files (copied as-is, no templating) ---
|
|
81
|
+
const sourceFiles = [
|
|
82
|
+
'src/middleware.ts',
|
|
83
|
+
'src/content.config.ts',
|
|
84
|
+
'scripts/setup.js',
|
|
85
|
+
'astro.config.mjs',
|
|
86
|
+
'tsconfig.json',
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
for (const rel of sourceFiles) {
|
|
90
|
+
const srcPath = path.join(templateRoot, rel);
|
|
91
|
+
if (!fs.existsSync(srcPath)) continue;
|
|
92
|
+
if (syncFile(srcPath, path.join(cwd, rel), vars)) {
|
|
93
|
+
console.log(` updated ${rel}`);
|
|
94
|
+
updated++;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// --- Summary ---
|
|
99
|
+
if (updated === 0) {
|
|
100
|
+
console.log(' Everything is up to date.');
|
|
101
|
+
} else {
|
|
102
|
+
console.log(`\n ${updated} file(s) updated. Review and commit the changes.`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Auto-generated by `npm run sync` — local changes will be overwritten.
|
|
2
|
+
// Astro configuration wired to the whereto-bike platform.
|
|
3
|
+
import 'dotenv/config';
|
|
4
|
+
import { defineConfig } from 'astro/config';
|
|
5
|
+
import { getAdapter } from 'whereto-bike/lib/adapter';
|
|
6
|
+
import preact from '@astrojs/preact';
|
|
7
|
+
import { wheretoBike, cspConfig } from 'whereto-bike';
|
|
8
|
+
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
site: process.env.SITE_URL,
|
|
11
|
+
adapter: await getAdapter(process.env.RUNTIME),
|
|
12
|
+
security: cspConfig(),
|
|
13
|
+
integrations: [
|
|
14
|
+
preact(),
|
|
15
|
+
...wheretoBike({ consumerRoot: import.meta.dirname }),
|
|
16
|
+
],
|
|
17
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
instance_type: blog
|
|
2
|
+
name: "My Bike Blog"
|
|
3
|
+
display_name: {{DOMAIN}}
|
|
4
|
+
tagline: "bike rides and adventures"
|
|
5
|
+
description: "A cycling blog — rides, tours, and adventures."
|
|
6
|
+
domain: {{DOMAIN}}
|
|
7
|
+
cdn_url: https://cdn.{{DOMAIN}}
|
|
8
|
+
videos_cdn_url: https://cdn.{{DOMAIN}}
|
|
9
|
+
timezone: {{TIMEZONE}}
|
|
10
|
+
locale: en
|
|
11
|
+
locales: [en]
|
|
12
|
+
author:
|
|
13
|
+
name: ""
|
|
14
|
+
email: ""
|
|
15
|
+
url: https://{{DOMAIN}}
|
|
16
|
+
center:
|
|
17
|
+
lat: 0
|
|
18
|
+
lng: 0
|
|
19
|
+
bounds:
|
|
20
|
+
north: 90
|
|
21
|
+
south: -90
|
|
22
|
+
east: 180
|
|
23
|
+
west: -180
|
|
24
|
+
place_categories: {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*.gpx filter=lfs diff=lfs merge=lfs -text
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Auto-generated by `npm run sync` — local changes will be overwritten.
|
|
2
|
+
# Builds the site on pull requests to verify nothing is broken.
|
|
3
|
+
name: CI
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ci-${{ github.head_ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
build:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v6
|
|
18
|
+
|
|
19
|
+
- name: LFS cache period
|
|
20
|
+
id: lfs-period
|
|
21
|
+
run: echo "period=$(( $(date +%s) / 1209600 ))" >> "$GITHUB_OUTPUT"
|
|
22
|
+
|
|
23
|
+
- name: Restore LFS cache
|
|
24
|
+
uses: actions/cache@v5
|
|
25
|
+
with:
|
|
26
|
+
path: .git/lfs
|
|
27
|
+
key: lfs-${{ steps.lfs-period.outputs.period }}
|
|
28
|
+
restore-keys: lfs-
|
|
29
|
+
|
|
30
|
+
- name: Pull LFS files
|
|
31
|
+
run: git lfs pull
|
|
32
|
+
|
|
33
|
+
- uses: actions/setup-node@v6
|
|
34
|
+
with:
|
|
35
|
+
node-version: 22
|
|
36
|
+
cache: 'npm'
|
|
37
|
+
|
|
38
|
+
- run: npm ci
|
|
39
|
+
|
|
40
|
+
- name: Generate map styles
|
|
41
|
+
run: npx tsx node_modules/whereto-bike/scripts/build-map-style.ts
|
|
42
|
+
|
|
43
|
+
- name: Restore Astro content cache
|
|
44
|
+
uses: actions/cache@v5
|
|
45
|
+
with:
|
|
46
|
+
path: .astro
|
|
47
|
+
key: astro-${{ hashFiles('blog/**') }}
|
|
48
|
+
restore-keys: astro-
|
|
49
|
+
|
|
50
|
+
- name: Build site
|
|
51
|
+
run: npx astro build
|
|
52
|
+
env:
|
|
53
|
+
CONTENT_DIR: .
|
|
54
|
+
CITY: blog
|
|
55
|
+
SITE_URL: https://{{DOMAIN}}
|
|
56
|
+
NODE_OPTIONS: '--max-old-space-size=4096'
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Auto-generated by `npm run sync` — local changes will be overwritten.
|
|
2
|
+
# Builds and deploys the site to Cloudflare Workers on push to main.
|
|
3
|
+
name: Deploy
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: deploy
|
|
12
|
+
cancel-in-progress: false
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
deploy:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
environment: production
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v6
|
|
20
|
+
|
|
21
|
+
- name: LFS cache period
|
|
22
|
+
id: lfs-period
|
|
23
|
+
run: echo "period=$(( $(date +%s) / 1209600 ))" >> "$GITHUB_OUTPUT"
|
|
24
|
+
|
|
25
|
+
- name: Restore LFS cache
|
|
26
|
+
uses: actions/cache@v5
|
|
27
|
+
with:
|
|
28
|
+
path: .git/lfs
|
|
29
|
+
key: lfs-${{ steps.lfs-period.outputs.period }}
|
|
30
|
+
restore-keys: lfs-
|
|
31
|
+
|
|
32
|
+
- name: Pull LFS files
|
|
33
|
+
run: git lfs pull
|
|
34
|
+
|
|
35
|
+
- uses: actions/setup-node@v6
|
|
36
|
+
with:
|
|
37
|
+
node-version: 22
|
|
38
|
+
cache: 'npm'
|
|
39
|
+
|
|
40
|
+
- run: npm ci
|
|
41
|
+
|
|
42
|
+
- name: Record build start time
|
|
43
|
+
run: echo "BUILD_START=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_ENV"
|
|
44
|
+
|
|
45
|
+
- name: Generate map styles
|
|
46
|
+
run: npx tsx node_modules/whereto-bike/scripts/build-map-style.ts
|
|
47
|
+
|
|
48
|
+
- name: Restore map cache
|
|
49
|
+
uses: actions/cache@v5
|
|
50
|
+
with:
|
|
51
|
+
path: public/maps
|
|
52
|
+
key: maps-${{ hashFiles('blog/rides/**/*.gpx', 'package-lock.json') }}
|
|
53
|
+
restore-keys: maps-
|
|
54
|
+
|
|
55
|
+
- name: Restore Astro content cache
|
|
56
|
+
uses: actions/cache@v5
|
|
57
|
+
with:
|
|
58
|
+
path: .astro
|
|
59
|
+
key: astro-${{ hashFiles('blog/**') }}
|
|
60
|
+
restore-keys: astro-
|
|
61
|
+
|
|
62
|
+
- name: Generate map thumbnails
|
|
63
|
+
run: npx tsx node_modules/whereto-bike/scripts/generate-maps.ts
|
|
64
|
+
env:
|
|
65
|
+
CONTENT_DIR: .
|
|
66
|
+
CITY: blog
|
|
67
|
+
GOOGLE_MAPS_STATIC_API_KEY: ${{ secrets.GOOGLE_MAPS_STATIC_API_KEY }}
|
|
68
|
+
|
|
69
|
+
- name: Build site
|
|
70
|
+
run: npx astro build
|
|
71
|
+
env:
|
|
72
|
+
CONTENT_DIR: .
|
|
73
|
+
CITY: blog
|
|
74
|
+
SITE_URL: https://{{DOMAIN}}
|
|
75
|
+
NODE_OPTIONS: '--max-old-space-size=4096'
|
|
76
|
+
|
|
77
|
+
- name: Prepare wrangler config
|
|
78
|
+
run: |
|
|
79
|
+
rm -f dist/server/wrangler.json
|
|
80
|
+
node -e "
|
|
81
|
+
const fs = require('fs');
|
|
82
|
+
const raw = fs.readFileSync('wrangler.jsonc', 'utf8');
|
|
83
|
+
const stripped = raw.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
84
|
+
const c = JSON.parse(stripped);
|
|
85
|
+
c.main = './dist/server/entry.mjs';
|
|
86
|
+
c.assets.directory = './dist/client';
|
|
87
|
+
fs.writeFileSync('wrangler.jsonc', JSON.stringify(c, null, 2));
|
|
88
|
+
"
|
|
89
|
+
|
|
90
|
+
- name: Run D1 migrations
|
|
91
|
+
run: npx wrangler d1 migrations apply DB --config wrangler.jsonc --remote
|
|
92
|
+
env:
|
|
93
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
94
|
+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
95
|
+
|
|
96
|
+
- name: Deploy to Cloudflare Workers
|
|
97
|
+
run: npx wrangler deploy --config wrangler.jsonc
|
|
98
|
+
env:
|
|
99
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
100
|
+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
101
|
+
|
|
102
|
+
- name: Clear stale content edits cache
|
|
103
|
+
run: npx wrangler d1 execute DB --config wrangler.jsonc --remote --command "DELETE FROM content_edits WHERE updated_at < '${{ env.BUILD_START }}'"
|
|
104
|
+
env:
|
|
105
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
106
|
+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Auto-generated by `npm run sync` — local changes will be overwritten.
|
|
2
|
+
# Weekly: updates whereto-bike, syncs templates, commits and pushes if changed.
|
|
3
|
+
name: Update
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
schedule:
|
|
7
|
+
- cron: '0 9 * * 1' # Every Monday at 9am UTC
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
update:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v6
|
|
15
|
+
|
|
16
|
+
- name: LFS cache period
|
|
17
|
+
id: lfs-period
|
|
18
|
+
run: echo "period=$(( $(date +%s) / 1209600 ))" >> "$GITHUB_OUTPUT"
|
|
19
|
+
|
|
20
|
+
- name: Restore LFS cache
|
|
21
|
+
uses: actions/cache@v5
|
|
22
|
+
with:
|
|
23
|
+
path: .git/lfs
|
|
24
|
+
key: lfs-${{ steps.lfs-period.outputs.period }}
|
|
25
|
+
restore-keys: lfs-
|
|
26
|
+
|
|
27
|
+
- name: Pull LFS files
|
|
28
|
+
run: git lfs pull
|
|
29
|
+
|
|
30
|
+
- uses: actions/setup-node@v6
|
|
31
|
+
with:
|
|
32
|
+
node-version: 22
|
|
33
|
+
cache: 'npm'
|
|
34
|
+
|
|
35
|
+
- run: npm ci
|
|
36
|
+
|
|
37
|
+
- name: Update whereto-bike
|
|
38
|
+
run: npm update whereto-bike
|
|
39
|
+
|
|
40
|
+
- name: Sync workflow templates
|
|
41
|
+
run: npm run sync
|
|
42
|
+
|
|
43
|
+
- name: Check for changes
|
|
44
|
+
id: changes
|
|
45
|
+
run: |
|
|
46
|
+
if git diff --quiet; then
|
|
47
|
+
echo "updated=false" >> "$GITHUB_OUTPUT"
|
|
48
|
+
else
|
|
49
|
+
echo "updated=true" >> "$GITHUB_OUTPUT"
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
- name: Commit and push
|
|
53
|
+
if: steps.changes.outputs.updated == 'true'
|
|
54
|
+
run: |
|
|
55
|
+
git config user.name "github-actions[bot]"
|
|
56
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
57
|
+
git add package-lock.json .github/workflows/
|
|
58
|
+
git commit -m "chore: update whereto-bike"
|
|
59
|
+
git push
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{FOLDER}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "astro dev",
|
|
7
|
+
"build": "astro build",
|
|
8
|
+
"preview": "astro preview",
|
|
9
|
+
"setup": "node scripts/setup.js",
|
|
10
|
+
"sync": "node node_modules/whereto-bike/sync.js"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@astrojs/cloudflare": "^13.0.0-beta.11",
|
|
14
|
+
"@astrojs/node": "^10.0.0-beta.6",
|
|
15
|
+
"@astrojs/preact": "^4.1.3",
|
|
16
|
+
"astro": "^6.0.0-beta.20",
|
|
17
|
+
"whereto-bike": "^0.0.1",
|
|
18
|
+
"dotenv": "^17.3.1",
|
|
19
|
+
"preact": "^10.28.4",
|
|
20
|
+
"sass": "^1.97.3"
|
|
21
|
+
}
|
|
22
|
+
}
|