@brightsideoy/kama 0.1.0-alpha.18 ā 0.1.0-alpha.180
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 +169 -0
- package/bin/cli.js +675 -193
- package/content/definitions/block-layouts/three-column.yaml +20 -0
- package/content/definitions/block-layouts/two-column.yaml +29 -0
- package/content/definitions/blocks/button.yaml +24 -0
- package/content/definitions/blocks/header.yaml +13 -0
- package/content/definitions/blocks/header_image.yaml +21 -0
- package/content/definitions/blocks/hero.yaml +28 -0
- package/content/definitions/blocks/html.yaml +13 -0
- package/content/definitions/blocks/image_card.yaml +56 -0
- package/content/definitions/blocks/single_image.yaml +27 -0
- package/content/definitions/blocks/text.yaml +15 -0
- package/content/definitions/channels/ecommerce.yaml +28 -0
- package/content/definitions/channels/website.yaml +41 -0
- package/content/definitions/field-sets/access.yaml +27 -0
- package/content/definitions/field-sets/channel-base.yaml +50 -0
- package/content/definitions/field-sets/channel-seo.yaml +21 -0
- package/content/definitions/field-sets/comments.yaml +11 -0
- package/content/definitions/field-sets/content-block.yaml +41 -0
- package/content/definitions/field-sets/page-base.yaml +44 -0
- package/content/definitions/field-sets/page-seo.yaml +34 -0
- package/content/definitions/field-sets/page-social.yaml +78 -0
- package/content/definitions/field-sets/schema-type.yaml +43 -0
- package/content/definitions/field-sets/tree-base.yaml +31 -0
- package/content/definitions/field-sets/visibility.yaml +8 -0
- package/content/definitions/node-layouts/content.yml +12 -0
- package/content/definitions/node-layouts/landing-page.yml +27 -0
- package/content/definitions/node-layouts/single-column.yml +43 -0
- package/content/definitions/node-layouts/two-column.yml +48 -0
- package/content/definitions/nodes/address.yaml +40 -0
- package/content/definitions/nodes/article.yaml +37 -0
- package/content/definitions/nodes/event.yaml +55 -0
- package/content/definitions/nodes/external-link.yaml +29 -0
- package/content/definitions/nodes/page-skeleton.yaml +48 -0
- package/content/definitions/nodes/page.yaml +54 -0
- package/content/definitions/nodes/person.yaml +37 -0
- package/content/definitions/nodes/product.yaml +89 -0
- package/content/definitions/trees/navigation.yaml +24 -0
- package/definitions/editor/button.ts +11 -0
- package/definitions/editor/default.ts +22 -0
- package/definitions/editor/header.ts +36 -0
- package/definitions/editor/minimal.ts +15 -0
- package/definitions/validators/iban.ts +12 -0
- package/definitions/validators/magic-word.ts +7 -0
- package/dist/assets/index-C98UkpDr.js +164 -0
- package/dist/assets/index-CGVNOd3T.css +1 -0
- package/dist/dist-fWm21byp.js +8987 -0
- package/dist/index.html +15 -0
- package/dist/index.js +8150 -1991
- package/dist/schema-compiler.js +48 -0
- package/documentation/SCHEMAS.md +182 -0
- package/package.json +19 -17
- package/schema.sql +206 -0
- package/templates/astro-starter/astro.config.mjs +27 -0
- package/templates/astro-starter/package.json.template +20 -0
- package/templates/astro-starter/public/robots.txt +23 -0
- package/templates/astro-starter/src/components/blocks/BlockRenderer.astro +134 -0
- package/templates/astro-starter/src/components/blocks/ColumnLayout.astro +67 -0
- package/templates/astro-starter/src/components/blocks/HeaderImage.astro +132 -0
- package/templates/astro-starter/src/components/blocks/Hero.astro +218 -0
- package/templates/astro-starter/src/components/blocks/HtmlBlock.astro +25 -0
- package/templates/astro-starter/src/components/blocks/ImageBlock.astro +92 -0
- package/templates/astro-starter/src/components/blocks/TextBlock.astro +15 -0
- package/templates/astro-starter/src/components/global/Breadcrumb.astro +67 -0
- package/templates/astro-starter/src/components/global/Footer.astro +89 -0
- package/templates/astro-starter/src/components/global/Header.astro +177 -0
- package/templates/astro-starter/src/components/global/MetaHead.astro +266 -0
- package/templates/astro-starter/src/components/global/NavigationMenu.astro +402 -0
- package/templates/astro-starter/src/components/ui/KamaImage.astro +172 -0
- package/templates/astro-starter/src/layouts/SkeletonLayout.astro +56 -0
- package/templates/astro-starter/src/layouts/StandardLayout.astro +62 -0
- package/templates/astro-starter/src/lib/cms.ts +197 -0
- package/templates/astro-starter/src/lib/media/kama-image-service.ts +56 -0
- package/templates/astro-starter/src/lib/media/media-proxy.ts +129 -0
- package/templates/astro-starter/src/lib/media/media-url.ts +98 -0
- package/templates/astro-starter/src/middleware.ts +478 -0
- package/templates/astro-starter/src/pages/404.astro +46 -0
- package/templates/astro-starter/src/pages/[...slug].astro +237 -0
- package/templates/astro-starter/src/pages/llms-full.txt.ts +171 -0
- package/templates/astro-starter/src/pages/llms.txt.ts +113 -0
- package/templates/astro-starter/src/pages/media/[...path].ts +14 -0
- package/templates/astro-starter/src/pages/partials/footer.astro +69 -0
- package/templates/astro-starter/src/pages/partials/nav.astro +141 -0
- package/templates/astro-starter/src/styles/standard-theme.css +157 -0
- package/templates/astro-starter/src/utils/tiptap.ts +113 -0
- package/templates/astro-starter/tsconfig.json +0 -0
- package/templates/kama-worker/index.js +19 -0
- package/templates/kama-worker/server.js +98 -0
- package/wrangler.json +22 -0
package/bin/cli.js
CHANGED
|
@@ -1,168 +1,423 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
|
-
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import { join } from 'node:path';
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, cpSync, unlinkSync, appendFileSync } from 'node:fs';
|
|
4
|
+
import { join, dirname, basename, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
6
|
import * as readline from 'node:readline/promises';
|
|
6
7
|
|
|
8
|
+
// šÆ Always use the absolute newest ecosystem
|
|
9
|
+
const ASTRO_LATEST = "latest";
|
|
10
|
+
const ASTRO_NODE_LATEST = "latest";
|
|
11
|
+
const ASTRO_CF_LATEST = "latest";
|
|
12
|
+
const WRANGLER_LATEST = "latest";
|
|
13
|
+
|
|
14
|
+
// Dynamically resolve current package version
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = dirname(__filename);
|
|
17
|
+
let currentKamaVersion = 'alpha';
|
|
18
|
+
try {
|
|
19
|
+
const pkgPath = join(__dirname, '../package.json');
|
|
20
|
+
if (existsSync(pkgPath)) {
|
|
21
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
22
|
+
if (pkg.version) currentKamaVersion = `^${pkg.version}`;
|
|
23
|
+
}
|
|
24
|
+
} catch (e) {
|
|
25
|
+
console.warn("ā ļø Could not detect Kama version, defaulting to 'alpha'");
|
|
26
|
+
}
|
|
27
|
+
|
|
7
28
|
const rl = readline.createInterface({
|
|
8
29
|
input: process.stdin,
|
|
9
30
|
output: process.stdout,
|
|
10
31
|
});
|
|
11
32
|
|
|
12
|
-
|
|
13
|
-
console.log('\nš„ --- KAMA
|
|
33
|
+
function showHelp() {
|
|
34
|
+
console.log('\nš„ --- KAMA ENGINE CLI --- š„\n');
|
|
35
|
+
console.log('Usage: npx kama <command> [options]\n');
|
|
36
|
+
console.log('Commands:');
|
|
37
|
+
console.log(' init Kickstart a new Monorepo (CMS Worker + Optional Astro Site)');
|
|
38
|
+
console.log(' add site Add a new Astro Frontend Site to the current workspace (sites/)');
|
|
39
|
+
console.log(' bundle Compile and bundle YAML schemas into bundled-definitions.js for Edge deployment');
|
|
40
|
+
console.log(' promote Promote the current Local workspace to Cloudflare Edge');
|
|
41
|
+
console.log(' help, -h Show this help message\n');
|
|
42
|
+
|
|
43
|
+
console.log('Workspace Scripts (Run from project root):');
|
|
44
|
+
console.log(' npm update Update to latest KAMA version');
|
|
45
|
+
console.log(' npm run dev Boot up the CMS backend and Astro frontend concurrently');
|
|
46
|
+
console.log(' npm run deploy:cms Deploy the backend worker and database to Cloudflare');
|
|
47
|
+
console.log(' npm run deploy:site Deploy the Astro frontend site to Cloudflare\n');
|
|
48
|
+
|
|
49
|
+
console.log('Examples:');
|
|
50
|
+
console.log(' npx kama init');
|
|
51
|
+
console.log(' npx kama add site');
|
|
52
|
+
console.log(' npx kama bundle\n');
|
|
53
|
+
}
|
|
14
54
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
55
|
+
async function main() {
|
|
56
|
+
const args = process.argv.slice(2);
|
|
57
|
+
const command = args[0]?.toLowerCase();
|
|
58
|
+
const subCommand = args[1]?.toLowerCase();
|
|
59
|
+
|
|
60
|
+
if (command === 'help' || command === '--help' || command === '-h') {
|
|
61
|
+
showHelp();
|
|
62
|
+
rl.close();
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
18
65
|
|
|
19
|
-
|
|
66
|
+
console.log('\nš„ --- KAMA ENGINE CLI --- š„\n');
|
|
20
67
|
|
|
21
|
-
if (
|
|
68
|
+
if (command === 'init') {
|
|
69
|
+
await createFullWorkspace();
|
|
70
|
+
} else if (command === 'add' && subCommand === 'site') {
|
|
22
71
|
await createNewSiteOnly();
|
|
72
|
+
} else if (command === 'promote') {
|
|
73
|
+
await promoteLocalToCloudflare();
|
|
74
|
+
} else if (command === 'bundle') {
|
|
75
|
+
await bundleWorkspaceSchemas();
|
|
23
76
|
} else {
|
|
24
|
-
|
|
77
|
+
console.log('Available Commands:');
|
|
78
|
+
console.log(' 1) npx kama init - Kickstart new Monorepo (CMS + Optional Site)');
|
|
79
|
+
console.log(' 2) npx kama add site - Add new Astro Site to current workspace (sites/)');
|
|
80
|
+
console.log(' 3) npx kama bundle - Compile and bundle YAML schemas for Edge deployment');
|
|
81
|
+
console.log(' 4) npx kama promote - Promote current Local workspace to Cloudflare Edge');
|
|
82
|
+
console.log(' 5) Help - Show all commands and usage');
|
|
83
|
+
|
|
84
|
+
const choice = (await rl.question('\nSelect choice [1/2/3/4/5]: ')).trim();
|
|
85
|
+
|
|
86
|
+
if (choice === '5') {
|
|
87
|
+
showHelp();
|
|
88
|
+
} else if (choice === '4') {
|
|
89
|
+
await promoteLocalToCloudflare();
|
|
90
|
+
} else if (choice === '3') {
|
|
91
|
+
await bundleWorkspaceSchemas();
|
|
92
|
+
} else if (choice === '2') {
|
|
93
|
+
await createNewSiteOnly();
|
|
94
|
+
} else {
|
|
95
|
+
await createFullWorkspace();
|
|
96
|
+
}
|
|
25
97
|
}
|
|
26
98
|
|
|
27
99
|
rl.close();
|
|
28
100
|
}
|
|
29
101
|
|
|
30
102
|
/**
|
|
31
|
-
*
|
|
103
|
+
* Helper to update astro.config.mjs for Cloudflare
|
|
104
|
+
*/
|
|
105
|
+
function convertAstroConfigToCloudflare(siteDir) {
|
|
106
|
+
const configPath = join(siteDir, 'astro.config.mjs');
|
|
107
|
+
if (existsSync(configPath)) {
|
|
108
|
+
let content = readFileSync(configPath, 'utf-8');
|
|
109
|
+
content = content
|
|
110
|
+
.replace("import node from '@astrojs/node';", "import cloudflare from '@astrojs/cloudflare';")
|
|
111
|
+
.replace('adapter: node({ mode: "standalone" })', 'adapter: cloudflare({ imageService: process.env.NODE_ENV === "production" ? "cloudflare" : "passthrough" })');
|
|
112
|
+
writeFileSync(configPath, content, 'utf-8');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* COMMAND: npx kama init
|
|
118
|
+
* Kickstart full Monorepo Workspace
|
|
32
119
|
*/
|
|
33
120
|
async function createFullWorkspace() {
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const siteDomain = (await rl.question('First Website Domain (e.g., www.example.com): ')) || 'www.example.com';
|
|
121
|
+
const cwdName = basename(process.cwd());
|
|
122
|
+
const hasExistingPkg = existsSync(join(process.cwd(), 'package.json'));
|
|
37
123
|
|
|
38
|
-
const
|
|
39
|
-
const
|
|
124
|
+
const defaultDirPrompt = hasExistingPkg ? '.' : cwdName;
|
|
125
|
+
const inputName = await rl.question(`Project folder name (use '.' for current dir) [${defaultDirPrompt}]: `);
|
|
40
126
|
|
|
41
|
-
|
|
42
|
-
|
|
127
|
+
const chosenFolder = inputName.trim() || defaultDirPrompt;
|
|
128
|
+
const isCurrentDir = chosenFolder === '.';
|
|
43
129
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
let isAuthenticated = false;
|
|
130
|
+
const projName = isCurrentDir ? cwdName : chosenFolder;
|
|
131
|
+
const rootDir = isCurrentDir ? process.cwd() : join(process.cwd(), projName);
|
|
47
132
|
|
|
48
|
-
console.log(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
133
|
+
console.log('\nš Choose your Deployment Architecture:');
|
|
134
|
+
console.log(' 1) VPS / Local Server (Node.js, SQLite, Sharp cropping)');
|
|
135
|
+
console.log(' 2) Cloudflare Edge (Workers, D1, R2, CF Image Resizing)');
|
|
136
|
+
const envChoice = await rl.question('\nChoice [1/2]: ');
|
|
137
|
+
const isCloudflare = envChoice.trim() === '2';
|
|
138
|
+
|
|
139
|
+
const createSiteInput = await rl.question('\nš Do you want to scaffold an initial Astro frontend site? (y/n) [y]: ');
|
|
140
|
+
const shouldCreateSite = createSiteInput.trim().toLowerCase() !== 'n';
|
|
141
|
+
|
|
142
|
+
let siteFolderName = 'kama-starter';
|
|
143
|
+
let siteDomain = 'localhost:4321';
|
|
144
|
+
let cmsDomain = 'localhost:8787';
|
|
145
|
+
|
|
146
|
+
if (shouldCreateSite) {
|
|
147
|
+
siteFolderName = (await rl.question('Astro package/folder name (e.g., my-website) [kama-starter]: ')).trim() || 'kama-starter';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (isCloudflare) {
|
|
151
|
+
cmsDomain = (await rl.question('\nCMS Admin Domain (e.g., admin.yourdomain.com): ')).trim();
|
|
152
|
+
if (shouldCreateSite) {
|
|
153
|
+
siteDomain = (await rl.question('First Website Domain (e.g., yourdomain.com): ')).trim();
|
|
62
154
|
}
|
|
63
155
|
}
|
|
64
156
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
const d1Output = execSync(`npx wrangler d1 create ${dbName}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
157
|
+
const cmsProtocol = isCloudflare ? 'https://' : 'http://';
|
|
158
|
+
const cmsFullUrl = cmsDomain ? `${cmsProtocol}${cmsDomain.replace(/^https?:\/\//, '')}` : 'http://localhost:8787';
|
|
69
159
|
|
|
70
|
-
|
|
71
|
-
|
|
160
|
+
const workerName = `${projName}-worker`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
161
|
+
let dbName = `${projName}-db`.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
162
|
+
const bucketName = `${projName}-media-prod`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
72
163
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
} else {
|
|
77
|
-
console.log(`ā ļø Could not parse the database ID from Wrangler. You may need to update wrangler.json manually.`);
|
|
78
|
-
}
|
|
79
|
-
} catch (error) {
|
|
80
|
-
console.log(`ā ļø Auto-creation failed. We'll leave a placeholder.`);
|
|
81
|
-
}
|
|
164
|
+
if (!isCurrentDir) {
|
|
165
|
+
console.log(`\nš Creating workspace folder at ./${projName}...`);
|
|
166
|
+
mkdirSync(rootDir, { recursive: true });
|
|
82
167
|
} else {
|
|
83
|
-
console.log(
|
|
168
|
+
console.log(`\nš Initializing workspace in current directory (${rootDir})...`);
|
|
84
169
|
}
|
|
85
|
-
// ------------------------------
|
|
86
170
|
|
|
87
|
-
// 1. Root package.json
|
|
88
171
|
writeFileSync(
|
|
89
|
-
join(rootDir, '
|
|
90
|
-
|
|
91
|
-
name: projName,
|
|
92
|
-
private: true,
|
|
93
|
-
type: 'module',
|
|
94
|
-
workspaces: ['cms-worker', 'sites/*'],
|
|
95
|
-
scripts: {
|
|
96
|
-
'dev:cms': 'npm run dev -w cms-worker',
|
|
97
|
-
'deploy:cms': 'npm run deploy -w cms-worker',
|
|
98
|
-
},
|
|
99
|
-
}, null, 2)
|
|
172
|
+
join(rootDir, '.gitignore'),
|
|
173
|
+
`node_modules/\n.env\n.DS_Store\nkama-worker/.dev.vars\nkama-worker/.env\nkama-worker/.wrangler/\nkama-worker/public/\nkama-worker/*.db\nsites/*/.env\nsites/*/.astro/\nsites/*/dist/\nkama-worker/bundled-definitions.js\n`
|
|
100
174
|
);
|
|
101
175
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
176
|
+
console.log(`\nš Copying default content definitions...`);
|
|
177
|
+
const sourceDefsDir = join(__dirname, '../content/definitions');
|
|
178
|
+
const targetDefsDir = join(rootDir, 'content', 'definitions');
|
|
179
|
+
if (existsSync(sourceDefsDir)) {
|
|
180
|
+
cpSync(sourceDefsDir, targetDefsDir, { recursive: true });
|
|
181
|
+
} else {
|
|
182
|
+
console.warn(`ā ļø Could not find default content definitions at ${sourceDefsDir}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let d1DatabaseId = '00000000-0000-0000-0000-000000000000';
|
|
186
|
+
let isAuthenticated = false;
|
|
187
|
+
|
|
188
|
+
if (isCloudflare) {
|
|
189
|
+
console.log(`\nš Checking Cloudflare authentication...`);
|
|
190
|
+
try {
|
|
191
|
+
// Pass stdio: 'ignore' so child process doesn't drain parent stdin stream!
|
|
192
|
+
execSync('npx wrangler whoami', { stdio: 'ignore' });
|
|
193
|
+
isAuthenticated = true;
|
|
194
|
+
console.log(`ā
Logged in to Cloudflare.`);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.log(`ā ļø You are not logged in to Cloudflare.`);
|
|
197
|
+
console.log(`š Launching browser for Cloudflare login...`);
|
|
198
|
+
try {
|
|
199
|
+
execSync('npx wrangler login', { stdio: 'ignore' });
|
|
200
|
+
isAuthenticated = true;
|
|
201
|
+
console.log(`ā
Successfully logged in!`);
|
|
202
|
+
} catch (loginError) {
|
|
203
|
+
console.log(`ā Login failed or was cancelled.`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (isAuthenticated) {
|
|
208
|
+
try {
|
|
209
|
+
console.log(`\nšļø Checking existing Cloudflare D1 databases...`);
|
|
210
|
+
const listOutput = execSync(`npx wrangler d1 list --json`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
211
|
+
const jsonStart = listOutput.indexOf('[');
|
|
212
|
+
const jsonEnd = listOutput.lastIndexOf(']') + 1;
|
|
213
|
+
let databases = [];
|
|
214
|
+
|
|
215
|
+
if (jsonStart !== -1 && jsonEnd !== -1) {
|
|
216
|
+
databases = JSON.parse(listOutput.substring(jsonStart, jsonEnd));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const existingDb = databases.find(db => db.name === dbName);
|
|
220
|
+
|
|
221
|
+
if (existingDb) {
|
|
222
|
+
console.log(`ā ļø Database '${dbName}' already exists on your account.`);
|
|
223
|
+
const useExisting = await rl.question(`Do you want to link this existing database? (y/n) [y]: `);
|
|
224
|
+
|
|
225
|
+
if (useExisting.toLowerCase() !== 'n') {
|
|
226
|
+
d1DatabaseId = existingDb.uuid;
|
|
227
|
+
console.log(`ā
Linked existing database ID: ${d1DatabaseId}`);
|
|
228
|
+
} else {
|
|
229
|
+
dbName = (await rl.question(`Enter a new database name: `)).trim();
|
|
230
|
+
console.log(`\nšļø Creating new database '${dbName}'...`);
|
|
231
|
+
const d1Output = execSync(`npx wrangler d1 create ${dbName}`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
232
|
+
const match = d1Output.match(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/);
|
|
233
|
+
if (match) {
|
|
234
|
+
d1DatabaseId = match[0];
|
|
235
|
+
console.log(`ā
Success! Database ID bound: ${d1DatabaseId}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
console.log(`\nšļø Attempting to auto-create Cloudflare D1 Database '${dbName}'...`);
|
|
240
|
+
const d1Output = execSync(`npx wrangler d1 create ${dbName}`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
241
|
+
const match = d1Output.match(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/);
|
|
242
|
+
if (match) {
|
|
243
|
+
d1DatabaseId = match[0];
|
|
244
|
+
console.log(`ā
Success! Database ID bound: ${d1DatabaseId}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.log(`ā ļø Database operation failed. We'll leave a placeholder.`);
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
console.log(`ā ļø Skipping database creation. We'll leave a placeholder in wrangler.json.`);
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
console.log(`\nš» Local VPS mode selected. Skipping Cloudflare authentication and domain setup...`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const rootScripts = {
|
|
258
|
+
'dev:cms': 'npm run dev -w kama-worker',
|
|
259
|
+
'deploy:cms': 'npm run deploy -w kama-worker',
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const rootPkg = {
|
|
263
|
+
name: projName,
|
|
264
|
+
private: true,
|
|
265
|
+
type: 'module',
|
|
266
|
+
workspaces: ['kama-worker', 'sites/*'],
|
|
267
|
+
scripts: rootScripts,
|
|
268
|
+
dependencies: {
|
|
269
|
+
'@brightsideoy/kama': currentKamaVersion,
|
|
270
|
+
},
|
|
271
|
+
devDependencies: {}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
if (isCloudflare) {
|
|
275
|
+
rootPkg.devDependencies['wrangler'] = WRANGLER_LATEST;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (shouldCreateSite) {
|
|
279
|
+
rootPkg.devDependencies['concurrently'] = '^9.0.0';
|
|
280
|
+
rootPkg.scripts['dev:site'] = `npm run dev -w sites/${siteFolderName}`;
|
|
281
|
+
rootPkg.scripts['predev'] = 'npx kama bundle';
|
|
282
|
+
rootPkg.scripts['dev'] = 'npx concurrently "npm run dev:cms" "npm run dev:site"';
|
|
283
|
+
rootPkg.scripts['deploy:site'] = `npm run deploy -w sites/${siteFolderName}`;
|
|
284
|
+
} else {
|
|
285
|
+
rootPkg.scripts['predev'] = 'npx kama bundle';
|
|
286
|
+
rootPkg.scripts['dev'] = 'npm run dev:cms';
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
writeFileSync(join(rootDir, 'package.json'), JSON.stringify(rootPkg, null, 2));
|
|
290
|
+
|
|
291
|
+
console.log(`\nāļø Configuring CMS Worker '${workerName}' (${cmsDomain || 'localhost:8787'})...`);
|
|
292
|
+
const cmsDir = join(rootDir, 'kama-worker');
|
|
105
293
|
mkdirSync(cmsDir, { recursive: true });
|
|
106
294
|
|
|
107
|
-
writeFileSync(
|
|
108
|
-
join(cmsDir, 'package.json'),
|
|
109
|
-
JSON.stringify({
|
|
110
|
-
name: 'kama-worker',
|
|
111
|
-
type: 'module',
|
|
112
|
-
version: '1.0.0',
|
|
113
|
-
scripts: {
|
|
114
|
-
dev: 'wrangler dev',
|
|
115
|
-
deploy: 'wrangler deploy',
|
|
116
|
-
},
|
|
117
|
-
dependencies: {
|
|
118
|
-
'@brightsideoy/kama': 'latest',
|
|
119
|
-
},
|
|
120
|
-
devDependencies: {
|
|
121
|
-
wrangler: '^4.127.1',
|
|
122
|
-
tsx: '^4.19.2',
|
|
123
|
-
},
|
|
124
|
-
}, null, 2)
|
|
125
|
-
);
|
|
295
|
+
writeFileSync(join(cmsDir, 'seed.sql'), '-- Kama Auto-Generated Seed File\nSELECT 1;\n');
|
|
126
296
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
297
|
+
const emptyDefs = `{ nodes: {}, trees: {}, blocks: {}, nodeLayouts: {}, blockLayouts: {}, channels: {} }`;
|
|
298
|
+
writeFileSync(join(cmsDir, 'bundled-definitions.js'), `const defs = ${emptyDefs};\nexport const BUNDLED_DEFINITIONS = defs;\nexport default defs;\n`);
|
|
299
|
+
|
|
300
|
+
if (isCloudflare) {
|
|
301
|
+
const templateIndexPath = join(__dirname, '../templates/kama-worker/index.js');
|
|
302
|
+
if (existsSync(templateIndexPath)) {
|
|
303
|
+
cpSync(templateIndexPath, join(cmsDir, 'index.js'));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
writeFileSync(
|
|
307
|
+
join(cmsDir, 'package.json'),
|
|
308
|
+
JSON.stringify({
|
|
309
|
+
name: workerName,
|
|
310
|
+
type: 'module',
|
|
311
|
+
version: '1.0.0',
|
|
312
|
+
scripts: {
|
|
313
|
+
"schema:local": `wrangler d1 execute ${dbName} --local --file=../node_modules/@brightsideoy/kama/schema.sql > /dev/null 2>&1`,
|
|
314
|
+
"schema:remote": `wrangler d1 execute ${dbName} --remote --file=../node_modules/@brightsideoy/kama/schema.sql`,
|
|
315
|
+
"seed:local": `wrangler d1 execute ${dbName} --local --file=seed.sql > /dev/null 2>&1`,
|
|
316
|
+
"seed:remote": `wrangler d1 execute ${dbName} --remote --file=seed.sql`,
|
|
317
|
+
"dev": "npm run schema:local && npm run seed:local && wrangler dev --port 8787 --inspector-port 9239",
|
|
318
|
+
"deploy": "npm run schema:remote && npm run seed:remote && wrangler deploy"
|
|
319
|
+
},
|
|
320
|
+
dependencies: {
|
|
321
|
+
'@brightsideoy/kama': currentKamaVersion,
|
|
322
|
+
'@libsql/client': '^0.14.0'
|
|
323
|
+
},
|
|
324
|
+
devDependencies: {
|
|
325
|
+
"wrangler": WRANGLER_LATEST
|
|
326
|
+
},
|
|
327
|
+
}, null, 2)
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
const cmsWranglerConfig = {
|
|
331
|
+
$schema: '../node_modules/wrangler/config-schema.json',
|
|
332
|
+
name: workerName,
|
|
333
|
+
main: 'index.js',
|
|
334
|
+
compatibility_date: '2024-09-23',
|
|
134
335
|
compatibility_flags: ['nodejs_compat'],
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}],
|
|
147
|
-
|
|
148
|
-
|
|
336
|
+
dev: { host: 'localhost', port: 8787 },
|
|
337
|
+
cache: { enabled: true },
|
|
338
|
+
assets: { directory: '../node_modules/@brightsideoy/kama/dist', binding: 'ASSETS' },
|
|
339
|
+
images: { binding: 'IMAGES' },
|
|
340
|
+
vars: {
|
|
341
|
+
CDN_DOMAIN: cmsDomain || 'localhost:8787',
|
|
342
|
+
PUBLIC_FRONTEND_URL: (shouldCreateSite) ? (siteDomain && siteDomain !== 'localhost:4321' ? `https://${siteDomain}` : 'http://localhost:4321') : 'http://localhost:4321',
|
|
343
|
+
CLOUDFLARE_ZONE_ID: "REPLACE_WITH_YOUR_32_CHAR_ZONE_ID",
|
|
344
|
+
KAMA_RUNTIME: 'cloudflare'
|
|
345
|
+
},
|
|
346
|
+
d1_databases: [{ binding: 'DB', database_name: dbName, database_id: d1DatabaseId }],
|
|
347
|
+
r2_buckets: [{ binding: 'MEDIA_BUCKET', bucket_name: bucketName }],
|
|
348
|
+
routes: cmsDomain ? [{ pattern: cmsDomain, custom_domain: true }] : [{ pattern: "", custom_domain: true }]
|
|
349
|
+
};
|
|
149
350
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
351
|
+
writeFileSync(join(cmsDir, 'wrangler.json'), JSON.stringify(cmsWranglerConfig, null, 2));
|
|
352
|
+
|
|
353
|
+
writeFileSync(join(cmsDir, '.dev.vars'), `# Cloudflare Edge Cache Purging Secrets
|
|
354
|
+
CLOUDFLARE_API_TOKEN=cfut_YOUR_CUSTOM_PURGE_TOKEN
|
|
355
|
+
|
|
356
|
+
# Local Development Overrides
|
|
357
|
+
CDN_DOMAIN=
|
|
358
|
+
PUBLIC_FRONTEND_URL=http://localhost:4321
|
|
359
|
+
`);
|
|
360
|
+
} else {
|
|
361
|
+
const templateServerPath = join(__dirname, '../templates/kama-worker/server.js');
|
|
362
|
+
if (existsSync(templateServerPath)) {
|
|
363
|
+
cpSync(templateServerPath, join(cmsDir, 'server.js'));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
writeFileSync(
|
|
367
|
+
join(cmsDir, 'package.json'),
|
|
368
|
+
JSON.stringify({
|
|
369
|
+
name: workerName,
|
|
370
|
+
type: 'module',
|
|
371
|
+
version: '1.0.0',
|
|
372
|
+
scripts: {
|
|
373
|
+
"schema:local": "sqlite3 kama.db < ../node_modules/@brightsideoy/kama/schema.sql",
|
|
374
|
+
"seed:local": "sqlite3 kama.db < seed.sql",
|
|
375
|
+
"dev": "npm run schema:local && npm run seed:local && tsx server.js",
|
|
376
|
+
"start": "node server.js"
|
|
377
|
+
},
|
|
378
|
+
dependencies: {
|
|
379
|
+
'@brightsideoy/kama': currentKamaVersion,
|
|
380
|
+
'@hono/node-server': '^1.13.0',
|
|
381
|
+
'@libsql/client': '^0.14.0',
|
|
382
|
+
'sharp': '^0.33.5',
|
|
383
|
+
'dotenv': '^16.4.5'
|
|
384
|
+
},
|
|
385
|
+
devDependencies: {
|
|
386
|
+
"tsx": "^4.19.0"
|
|
387
|
+
},
|
|
388
|
+
}, null, 2)
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
writeFileSync(join(cmsDir, '.env'), `PORT=8787\nKAMA_RUNTIME=node\nDB_PATH=./kama.db\nLOCAL_MEDIA_PATH=./public/media\nPUBLIC_CMS_URL=http://127.0.0.1:8787\nPUBLIC_API_URL=http://127.0.0.1:8787\nPUBLIC_FRONTEND_URL=http://127.0.0.1:4321\n`);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
await bundleWorkspaceSchemas(rootDir);
|
|
395
|
+
|
|
396
|
+
if (shouldCreateSite) {
|
|
397
|
+
await generateAstroSite(rootDir, siteFolderName, siteDomain, cmsFullUrl, workerName, isCloudflare);
|
|
398
|
+
} else {
|
|
399
|
+
console.log(`\nā¹ļø Skipping Astro site creation. You can add a site later by running 'npx kama add site' inside this workspace.`);
|
|
400
|
+
}
|
|
153
401
|
|
|
154
402
|
console.log(`\n⨠Workspace ready! Next steps:`);
|
|
155
|
-
console.log(` 1. cd ${projName}`);
|
|
403
|
+
if (!isCurrentDir) console.log(` 1. cd ${projName}`);
|
|
156
404
|
console.log(` 2. npm install`);
|
|
157
|
-
|
|
158
|
-
|
|
405
|
+
|
|
406
|
+
if (isCloudflare) {
|
|
407
|
+
if (d1DatabaseId === '00000000-0000-0000-0000-000000000000') {
|
|
408
|
+
console.log(` 3. Run 'npx wrangler login' and create a D1 database manually.`);
|
|
409
|
+
}
|
|
410
|
+
console.log(` 4. Update CLOUDFLARE_ZONE_ID in kama-worker/wrangler.json`);
|
|
411
|
+
console.log(` 5. Securely store your purge token: cd kama-worker && npx wrangler secret put CLOUDFLARE_API_TOKEN`);
|
|
412
|
+
console.log(` 6. Run 'npm run dev' from the workspace root to start everything.`);
|
|
159
413
|
} else {
|
|
160
|
-
console.log(` 3.
|
|
414
|
+
console.log(` 3. Run 'npm run dev' from the workspace root to start the local backend and frontend.`);
|
|
415
|
+
console.log(`\nš” Note: You can deploy to Cloudflare later by running 'npx kama promote'.`);
|
|
161
416
|
}
|
|
162
417
|
}
|
|
163
418
|
|
|
164
419
|
/**
|
|
165
|
-
*
|
|
420
|
+
* COMMAND: npx kama add site
|
|
166
421
|
*/
|
|
167
422
|
async function createNewSiteOnly() {
|
|
168
423
|
const rootDir = process.cwd();
|
|
@@ -172,96 +427,323 @@ async function createNewSiteOnly() {
|
|
|
172
427
|
return;
|
|
173
428
|
}
|
|
174
429
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const cmsDomain = (await rl.question('CMS API Domain (e.g., cms.example.com): ')) || 'cms.example.com';
|
|
430
|
+
let targetWorkerName = 'kama-worker';
|
|
431
|
+
let detectedCmsDomain = '';
|
|
178
432
|
|
|
179
|
-
|
|
433
|
+
const cmsWranglerPath = join(rootDir, 'kama-worker', 'wrangler.json');
|
|
434
|
+
if (existsSync(cmsWranglerPath)) {
|
|
435
|
+
try {
|
|
436
|
+
const cmsWrangler = JSON.parse(readFileSync(cmsWranglerPath, 'utf-8'));
|
|
437
|
+
if (cmsWrangler.name) targetWorkerName = cmsWrangler.name;
|
|
438
|
+
if (cmsWrangler.vars?.CDN_DOMAIN) detectedCmsDomain = cmsWrangler.vars.CDN_DOMAIN;
|
|
439
|
+
else if (cmsWrangler.routes?.[0]?.pattern) detectedCmsDomain = cmsWrangler.routes[0].pattern;
|
|
440
|
+
} catch (e) { }
|
|
441
|
+
}
|
|
180
442
|
|
|
181
|
-
console.log(
|
|
443
|
+
console.log('\nš Deployment Mode:');
|
|
444
|
+
console.log(' 1) VPS / Local Server (Node.js)');
|
|
445
|
+
console.log(' 2) Cloudflare Edge');
|
|
446
|
+
const envChoice = await rl.question('\nChoice [1/2]: ');
|
|
447
|
+
const isCloudflare = envChoice.trim() === '2';
|
|
448
|
+
|
|
449
|
+
const siteFolder = (await rl.question('Astro package/folder name (e.g., my-new-site) [kama-starter]: ')).trim() || 'kama-starter';
|
|
450
|
+
let siteDomain = 'localhost:4321';
|
|
451
|
+
let cmsFullUrl = 'http://localhost:8787';
|
|
452
|
+
|
|
453
|
+
if (isCloudflare) {
|
|
454
|
+
siteDomain = (await rl.question('Website Domain (e.g., brand.com): ')).trim();
|
|
455
|
+
if (detectedCmsDomain) {
|
|
456
|
+
cmsFullUrl = detectedCmsDomain.startsWith('http') ? detectedCmsDomain : `https://${detectedCmsDomain.replace(/^https?:\/\//, '')}`;
|
|
457
|
+
} else {
|
|
458
|
+
const cmsDomainInput = (await rl.question('CMS API Domain (e.g., admin.brand.com): ')).trim();
|
|
459
|
+
cmsFullUrl = `https://${cmsDomainInput.replace(/^https?:\/\//, '')}`;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
await generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, targetWorkerName, isCloudflare);
|
|
464
|
+
|
|
465
|
+
console.log(`\n⨠Site created in sites/${siteFolder}!`);
|
|
466
|
+
console.log(`š¦ Run 'npm install' at workspace root.`);
|
|
182
467
|
}
|
|
183
468
|
|
|
184
469
|
/**
|
|
185
|
-
*
|
|
470
|
+
* COMMAND: npx kama bundle
|
|
186
471
|
*/
|
|
187
|
-
async function
|
|
188
|
-
|
|
472
|
+
async function bundleWorkspaceSchemas(targetRootDir) {
|
|
473
|
+
let rootDir = targetRootDir || process.cwd();
|
|
474
|
+
if (basename(rootDir) === 'kama-worker') rootDir = resolve(rootDir, '..');
|
|
189
475
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
476
|
+
try {
|
|
477
|
+
let compilerPath = join(__dirname, '../dist/schema-compiler.js');
|
|
478
|
+
if (!existsSync(compilerPath)) compilerPath = join(__dirname, '../scripts/schema-compiler.ts');
|
|
479
|
+
await import(pathToFileURL(compilerPath).href);
|
|
480
|
+
} catch (e) {
|
|
481
|
+
console.error(`ā Schema compilation failed:`, e.message || e);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
195
484
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
scripts: {
|
|
204
|
-
dev: 'astro dev',
|
|
205
|
-
build: 'astro build',
|
|
206
|
-
preview: 'astro preview',
|
|
207
|
-
deploy: 'astro build && wrangler deploy',
|
|
208
|
-
},
|
|
209
|
-
dependencies: {
|
|
210
|
-
'@astrojs/cloudflare': '^14.2.5',
|
|
211
|
-
'@brightsideoy/kama': 'latest',
|
|
212
|
-
astro: '^7.2.2',
|
|
213
|
-
wrangler: '^4.127.1',
|
|
214
|
-
},
|
|
215
|
-
}, null, 2)
|
|
216
|
-
);
|
|
485
|
+
/**
|
|
486
|
+
* COMMAND: npx kama promote
|
|
487
|
+
*/
|
|
488
|
+
async function promoteLocalToCloudflare() {
|
|
489
|
+
const rootDir = process.cwd();
|
|
490
|
+
const workerDir = join(rootDir, 'kama-worker');
|
|
491
|
+
const rootPkgPath = join(rootDir, 'package.json');
|
|
217
492
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
493
|
+
if (!existsSync(workerDir) || !existsSync(rootPkgPath)) {
|
|
494
|
+
console.error('ā Error: Could not find kama-worker folder or package.json. Run this command inside your workspace root.');
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
console.log('\nš --- UPGRADING WORKSPACE TO CLOUDFLARE EDGE --- š\n');
|
|
499
|
+
|
|
500
|
+
try {
|
|
501
|
+
execSync('npx wrangler whoami', { stdio: 'ignore' });
|
|
502
|
+
console.log('ā
Logged in to Cloudflare.');
|
|
503
|
+
} catch (e) {
|
|
504
|
+
console.log('š Launching browser for Cloudflare login...');
|
|
505
|
+
try { execSync('npx wrangler login', { stdio: 'ignore' }); }
|
|
506
|
+
catch (loginError) { console.log('ā Cloudflare login failed.'); return; }
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const cmsDomain = (await rl.question('\nCMS Admin Domain (e.g., admin.yourdomain.com): ')).trim();
|
|
510
|
+
|
|
511
|
+
const sitesFolderDir = join(rootDir, 'sites');
|
|
512
|
+
let siteDomain = '';
|
|
513
|
+
let siteDir = null;
|
|
514
|
+
if (existsSync(sitesFolderDir)) {
|
|
515
|
+
const sites = readdirSync(sitesFolderDir);
|
|
516
|
+
if (sites.length > 0) {
|
|
517
|
+
siteDir = join(sitesFolderDir, sites[0]);
|
|
518
|
+
siteDomain = (await rl.question('Frontend Website Domain (e.g., yourdomain.com): ')).trim();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const projName = basename(rootDir);
|
|
523
|
+
let dbName = `${projName}-db`.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
524
|
+
let d1DatabaseId = '00000000-0000-0000-0000-000000000000';
|
|
525
|
+
|
|
526
|
+
try {
|
|
527
|
+
const d1Output = execSync(`npx wrangler d1 create ${dbName}`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
528
|
+
const match = d1Output.match(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/);
|
|
529
|
+
if (match) d1DatabaseId = match[0];
|
|
530
|
+
} catch (err) { }
|
|
531
|
+
|
|
532
|
+
const rootPkg = JSON.parse(readFileSync(rootPkgPath, 'utf-8'));
|
|
533
|
+
rootPkg.devDependencies = rootPkg.devDependencies || {};
|
|
534
|
+
rootPkg.devDependencies['wrangler'] = WRANGLER_LATEST;
|
|
535
|
+
writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, 2));
|
|
536
|
+
|
|
537
|
+
const workerName = `${projName}-worker`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
538
|
+
const bucketName = `${projName}-media-prod`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
539
|
+
const workerWranglerPath = join(workerDir, 'wrangler.json');
|
|
540
|
+
|
|
541
|
+
let currentConfig = existsSync(workerWranglerPath)
|
|
542
|
+
? JSON.parse(readFileSync(workerWranglerPath, 'utf-8'))
|
|
543
|
+
: {
|
|
544
|
+
$schema: '../node_modules/wrangler/config-schema.json',
|
|
545
|
+
name: workerName,
|
|
546
|
+
main: 'index.js',
|
|
547
|
+
compatibility_date: '2024-09-23',
|
|
224
548
|
compatibility_flags: ['nodejs_compat'],
|
|
225
|
-
assets: {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
);
|
|
549
|
+
assets: { directory: '../node_modules/@brightsideoy/kama/dist', binding: 'ASSETS' },
|
|
550
|
+
images: { binding: 'IMAGES' },
|
|
551
|
+
vars: { CLOUDFLARE_ZONE_ID: "REPLACE_WITH_YOUR_32_CHAR_ZONE_ID", KAMA_RUNTIME: 'cloudflare' }
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
currentConfig.dev = currentConfig.dev || { host: 'localhost', port: 8787 };
|
|
555
|
+
currentConfig.cache = { enabled: true };
|
|
556
|
+
currentConfig.vars.CDN_DOMAIN = cmsDomain;
|
|
557
|
+
if (siteDomain) currentConfig.vars.PUBLIC_FRONTEND_URL = `https://${siteDomain}`;
|
|
558
|
+
currentConfig.routes = [{ pattern: cmsDomain, custom_domain: true }];
|
|
559
|
+
currentConfig.d1_databases = [{ binding: 'DB', database_name: dbName, database_id: d1DatabaseId }];
|
|
560
|
+
currentConfig.r2_buckets = [{ binding: 'MEDIA_BUCKET', bucket_name: bucketName }];
|
|
561
|
+
|
|
562
|
+
writeFileSync(workerWranglerPath, JSON.stringify(currentConfig, null, 2));
|
|
563
|
+
|
|
564
|
+
if (!existsSync(join(workerDir, '.dev.vars'))) {
|
|
565
|
+
writeFileSync(join(workerDir, '.dev.vars'), `CLOUDFLARE_API_TOKEN=cfut_YOUR_CUSTOM_PURGE_TOKEN\nCDN_DOMAIN=\nPUBLIC_FRONTEND_URL=http://localhost:4321\n`);
|
|
566
|
+
}
|
|
239
567
|
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
remotePatterns: [
|
|
248
|
-
{ protocol: 'http', hostname: 'localhost', port: '3000' },
|
|
249
|
-
{ protocol: 'https', hostname: '${cmsDomain}' }
|
|
250
|
-
]
|
|
251
|
-
},
|
|
252
|
-
build: {
|
|
253
|
-
inlineStylesheets: 'always',
|
|
254
|
-
},
|
|
255
|
-
adapter: cloudflare({
|
|
256
|
-
imageService: 'cloudflare',
|
|
257
|
-
})
|
|
258
|
-
});`;
|
|
259
|
-
writeFileSync(join(siteDir, 'astro.config.mjs'), astroConfig);
|
|
568
|
+
const workerIndexPath = join(workerDir, 'index.js');
|
|
569
|
+
if (!existsSync(workerIndexPath)) {
|
|
570
|
+
const templateIndexPath = join(__dirname, '../templates/kama-worker/index.js');
|
|
571
|
+
if (existsSync(templateIndexPath)) {
|
|
572
|
+
cpSync(templateIndexPath, workerIndexPath);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
260
575
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
576
|
+
const workerPkgPath = join(workerDir, 'package.json');
|
|
577
|
+
if (existsSync(workerPkgPath)) {
|
|
578
|
+
const workerPkg = JSON.parse(readFileSync(workerPkgPath, 'utf-8'));
|
|
579
|
+
workerPkg.scripts.dev = "npm run schema:local && npm run seed:local && wrangler dev --port 8787 --inspector-port 9239";
|
|
580
|
+
workerPkg.scripts.deploy = "npm run schema:remote && npm run seed:remote && wrangler deploy";
|
|
581
|
+
workerPkg.scripts["schema:remote"] = `wrangler d1 execute ${dbName} --remote --file=../node_modules/@brightsideoy/kama/schema.sql`;
|
|
582
|
+
workerPkg.scripts["seed:remote"] = `wrangler d1 execute ${dbName} --remote --file=seed.sql`;
|
|
583
|
+
workerPkg.devDependencies = workerPkg.devDependencies || {};
|
|
584
|
+
workerPkg.devDependencies.wrangler = WRANGLER_LATEST;
|
|
585
|
+
writeFileSync(workerPkgPath, JSON.stringify(workerPkg, null, 2));
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (siteDir && siteDomain) {
|
|
589
|
+
let sessionId = "REPLACE_WITH_YOUR_SESSION_KV_ID";
|
|
590
|
+
let htmlCacheId = "REPLACE_WITH_YOUR_HTML_CACHE_KV_ID";
|
|
591
|
+
const prefix = basename(siteDir).replace(/[^a-zA-Z0-9]/g, '_').toUpperCase();
|
|
592
|
+
|
|
593
|
+
try {
|
|
594
|
+
const sessionOut = execSync(`npx wrangler kv namespace create "SESSION_${prefix}"`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
595
|
+
if (sessionOut.match(/"id":\s*"([a-f0-9]{32})"/)) sessionId = sessionOut.match(/"id":\s*"([a-f0-9]{32})"/)[1];
|
|
596
|
+
const cacheOut = execSync(`npx wrangler kv namespace create "HTML_CACHE_${prefix}"`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
597
|
+
if (cacheOut.match(/"id":\s*"([a-f0-9]{32})"/)) htmlCacheId = cacheOut.match(/"id":\s*"([a-f0-9]{32})"/)[1];
|
|
598
|
+
} catch (err) { }
|
|
599
|
+
|
|
600
|
+
const siteWranglerPath = join(siteDir, 'wrangler.json');
|
|
601
|
+
const siteConfig = {
|
|
602
|
+
$schema: "node_modules/wrangler/config-schema.json",
|
|
603
|
+
name: `${basename(siteDir).toLowerCase().replace(/[^a-z0-9-]/g, '-')}-site`,
|
|
604
|
+
compatibility_date: "2024-09-23",
|
|
605
|
+
compatibility_flags: ["nodejs_compat"],
|
|
606
|
+
dev: { host: 'localhost', port: 4321 },
|
|
607
|
+
cache: { enabled: true },
|
|
608
|
+
assets: { directory: "./dist/client", binding: "ASSETS", run_worker_first: true },
|
|
609
|
+
kv_namespaces: [{ binding: "SESSION", id: sessionId }, { binding: "HTML_CACHE", id: htmlCacheId }],
|
|
610
|
+
services: [{ binding: "CMS_WORKER", service: currentConfig.name }],
|
|
611
|
+
routes: [{ pattern: siteDomain, zone_name: siteDomain, custom_domain: true, enabled: true, previews_enabled: false }],
|
|
612
|
+
vars: { PUBLIC_CMS_URL: `https://${cmsDomain}`, PUBLIC_API_URL: `https://${cmsDomain}` }
|
|
613
|
+
};
|
|
614
|
+
writeFileSync(siteWranglerPath, JSON.stringify(siteConfig, null, 2));
|
|
615
|
+
|
|
616
|
+
const sitePkgPath = join(siteDir, 'package.json');
|
|
617
|
+
if (existsSync(sitePkgPath)) {
|
|
618
|
+
const sitePkg = JSON.parse(readFileSync(sitePkgPath, 'utf-8'));
|
|
619
|
+
sitePkg.dependencies = sitePkg.dependencies || {};
|
|
620
|
+
sitePkg.dependencies['astro'] = ASTRO_LATEST;
|
|
621
|
+
|
|
622
|
+
if (sitePkg.dependencies['@astrojs/node']) delete sitePkg.dependencies['@astrojs/node'];
|
|
623
|
+
sitePkg.dependencies['@astrojs/cloudflare'] = ASTRO_CF_LATEST;
|
|
624
|
+
|
|
625
|
+
sitePkg.devDependencies = sitePkg.devDependencies || {};
|
|
626
|
+
sitePkg.devDependencies['wrangler'] = WRANGLER_LATEST;
|
|
627
|
+
|
|
628
|
+
writeFileSync(sitePkgPath, JSON.stringify(sitePkg, null, 2));
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
convertAstroConfigToCloudflare(siteDir);
|
|
632
|
+
|
|
633
|
+
const tsConfigPath = join(siteDir, 'tsconfig.json');
|
|
634
|
+
if (existsSync(tsConfigPath)) {
|
|
635
|
+
const tsConfig = JSON.parse(readFileSync(tsConfigPath, 'utf-8'));
|
|
636
|
+
tsConfig.compilerOptions = tsConfig.compilerOptions || {};
|
|
637
|
+
tsConfig.compilerOptions.types = ["@cloudflare/workers-types"];
|
|
638
|
+
writeFileSync(tsConfigPath, JSON.stringify(tsConfig, null, 2));
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
console.log(`\nā
Workspace successfully upgraded to Cloudflare Edge!`);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Helper: Copies the Astro starter template and hydrates runtime tokens
|
|
647
|
+
*/
|
|
648
|
+
async function generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, workerName = 'kama-worker', isCloudflare = false) {
|
|
649
|
+
console.log(`\nš Scaffolding Astro site (${siteFolder}) mapped to ${siteDomain} (Service: ${workerName})...`);
|
|
650
|
+
|
|
651
|
+
const siteDir = join(rootDir, 'sites', siteFolder);
|
|
652
|
+
const templateDir = join(__dirname, '../templates/astro-starter');
|
|
653
|
+
|
|
654
|
+
if (!existsSync(templateDir)) {
|
|
655
|
+
console.error(`ā Error: Astro template not found at ${templateDir}`);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
cpSync(templateDir, siteDir, { recursive: true });
|
|
660
|
+
|
|
661
|
+
const tsConfig = {
|
|
662
|
+
extends: "astro/tsconfigs/strict",
|
|
663
|
+
compilerOptions: { baseUrl: ".", paths: { "@/*": ["src/*"] } }
|
|
664
|
+
};
|
|
665
|
+
if (isCloudflare) tsConfig.compilerOptions.types = ["@cloudflare/workers-types"];
|
|
666
|
+
writeFileSync(join(siteDir, 'tsconfig.json'), JSON.stringify(tsConfig, null, 2));
|
|
667
|
+
|
|
668
|
+
const templatePkgPath = join(siteDir, 'package.json.template');
|
|
669
|
+
if (existsSync(templatePkgPath)) {
|
|
670
|
+
const rawContent = readFileSync(templatePkgPath, 'utf-8')
|
|
671
|
+
.replace(/\{\{SITE_NAME\}\}/g, siteFolder)
|
|
672
|
+
.replace(/\{\{KAMA_VERSION\}\}/g, currentKamaVersion);
|
|
673
|
+
|
|
674
|
+
const sitePkg = JSON.parse(rawContent);
|
|
675
|
+
|
|
676
|
+
sitePkg.dependencies = sitePkg.dependencies || {};
|
|
677
|
+
sitePkg.dependencies['astro'] = ASTRO_LATEST;
|
|
678
|
+
|
|
679
|
+
if (!isCloudflare) {
|
|
680
|
+
if (sitePkg.dependencies['@astrojs/cloudflare']) delete sitePkg.dependencies['@astrojs/cloudflare'];
|
|
681
|
+
if (sitePkg.dependencies['wrangler']) delete sitePkg.dependencies['wrangler'];
|
|
682
|
+
if (sitePkg.devDependencies && sitePkg.devDependencies['wrangler']) delete sitePkg.devDependencies['wrangler'];
|
|
683
|
+
|
|
684
|
+
sitePkg.dependencies['@astrojs/node'] = ASTRO_NODE_LATEST;
|
|
685
|
+
} else {
|
|
686
|
+
sitePkg.dependencies['@astrojs/cloudflare'] = ASTRO_CF_LATEST;
|
|
687
|
+
if (sitePkg.dependencies['wrangler']) delete sitePkg.dependencies['wrangler'];
|
|
688
|
+
sitePkg.devDependencies = sitePkg.devDependencies || {};
|
|
689
|
+
sitePkg.devDependencies['wrangler'] = WRANGLER_LATEST;
|
|
690
|
+
|
|
691
|
+
convertAstroConfigToCloudflare(siteDir);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
writeFileSync(join(siteDir, 'package.json'), JSON.stringify(sitePkg, null, 2));
|
|
695
|
+
unlinkSync(templatePkgPath);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const siteLabel = siteFolder.split(/[-_]/).map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
|
|
699
|
+
const safeSiteLabel = siteLabel.replace(/'/g, "''");
|
|
700
|
+
const channelKey = siteFolder.replace(/[^a-z0-9_-]/gi, '-').toLowerCase();
|
|
701
|
+
const treeKey = 'main-tree';
|
|
702
|
+
const safeWorkerName = `${siteFolder.toLowerCase().replace(/[^a-z0-9-]/g, '-')}-site`;
|
|
703
|
+
|
|
704
|
+
if (isCloudflare) {
|
|
705
|
+
let sessionId = "REPLACE_WITH_YOUR_SESSION_KV_ID";
|
|
706
|
+
let htmlCacheId = "REPLACE_WITH_YOUR_HTML_CACHE_KV_ID";
|
|
707
|
+
|
|
708
|
+
try {
|
|
709
|
+
const prefix = siteFolder.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase();
|
|
710
|
+
const sessionOut = execSync(`npx wrangler kv namespace create "SESSION_${prefix}"`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
711
|
+
if (sessionOut.match(/"id":\s*"([a-f0-9]{32})"/)) sessionId = sessionOut.match(/"id":\s*"([a-f0-9]{32})"/)[1];
|
|
712
|
+
const cacheOut = execSync(`npx wrangler kv namespace create "HTML_CACHE_${prefix}"`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
713
|
+
if (cacheOut.match(/"id":\s*"([a-f0-9]{32})"/)) htmlCacheId = cacheOut.match(/"id":\s*"([a-f0-9]{32})"/)[1];
|
|
714
|
+
} catch (err) { }
|
|
715
|
+
|
|
716
|
+
const siteConfig = {
|
|
717
|
+
$schema: "node_modules/wrangler/config-schema.json",
|
|
718
|
+
name: safeWorkerName,
|
|
719
|
+
compatibility_date: "2024-09-23",
|
|
720
|
+
compatibility_flags: ["nodejs_compat"],
|
|
721
|
+
dev: { host: "localhost", port: 4321 },
|
|
722
|
+
cache: { enabled: true },
|
|
723
|
+
assets: { directory: "./dist/client", binding: "ASSETS", run_worker_first: true },
|
|
724
|
+
kv_namespaces: [{ binding: "SESSION", id: sessionId }, { binding: "HTML_CACHE", id: htmlCacheId }],
|
|
725
|
+
services: [{ binding: "CMS_WORKER", service: workerName }],
|
|
726
|
+
vars: { PUBLIC_CMS_URL: cmsFullUrl, PUBLIC_API_URL: cmsFullUrl, PUBLIC_CHANNEL_ID: channelKey, PUBLIC_TREE_ID: treeKey, PUBLIC_HOME_SLUG: "home", PUBLIC_SITE_NAME: siteLabel }
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
if (siteDomain !== 'localhost:4321') siteConfig.routes = [{ pattern: siteDomain, custom_domain: true, enabled: true, previews_enabled: false }];
|
|
730
|
+
writeFileSync(join(siteDir, 'wrangler.json'), JSON.stringify(siteConfig, null, 2));
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const seedSql = `
|
|
734
|
+
-- Seed data for site: ${safeSiteLabel}
|
|
735
|
+
INSERT OR IGNORE INTO channels (key, label, channelType, defaultLanguage, isHidden, dataJson) VALUES ('${channelKey}', '${safeSiteLabel}', 'website', 'en', 0, '{}');
|
|
736
|
+
INSERT OR IGNORE INTO trees (key, label, treeType, channelId, isHidden, dataJson) VALUES ('${treeKey}', 'Main Tree', 'navigation', (SELECT id FROM channels WHERE key = '${channelKey}'), 0, '{}');
|
|
737
|
+
INSERT OR IGNORE INTO channel_trees (channelId, treeId, key) VALUES ((SELECT id FROM channels WHERE key = '${channelKey}'), (SELECT id FROM trees WHERE key = '${treeKey}'), 'main');
|
|
738
|
+
INSERT INTO content_nodes (contentType, title, slug, language, status, dataJson) SELECT 'page', 'Welcome to ${safeSiteLabel}', 'home', 'en', 'published', '{"titleSuffix":"ā ${safeSiteLabel}","layoutId":"single-column","subpageLayoutId":"two-column","logoText":"${safeSiteLabel}"}' WHERE NOT EXISTS (SELECT 1 FROM content_nodes WHERE slug = 'home' AND title = 'Welcome to ${safeSiteLabel}');
|
|
739
|
+
INSERT OR IGNORE INTO node_channels (nodeId, channelId) VALUES ((SELECT id FROM content_nodes WHERE slug = 'home' AND title = 'Welcome to ${safeSiteLabel}' ORDER BY id DESC LIMIT 1), (SELECT id FROM channels WHERE key = '${channelKey}'));
|
|
740
|
+
INSERT OR IGNORE INTO tree_nodes (treeId, contentId, parentId, sortOrder) VALUES ((SELECT id FROM trees WHERE key = '${treeKey}'), (SELECT id FROM content_nodes WHERE slug = 'home' AND title = 'Welcome to ${safeSiteLabel}' ORDER BY id DESC LIMIT 1), NULL, 0);
|
|
741
|
+
`;
|
|
742
|
+
|
|
743
|
+
const workerDir = join(rootDir, 'kama-worker');
|
|
744
|
+
if (existsSync(workerDir)) appendFileSync(join(workerDir, 'seed.sql'), seedSql);
|
|
745
|
+
|
|
746
|
+
writeFileSync(join(siteDir, '.env'), `# Local Development Overrides\nPUBLIC_CMS_URL="http://127.0.0.1:8787"\nPUBLIC_API_URL="http://127.0.0.1:8787"\nPUBLIC_CHANNEL_ID="${channelKey}"\nPUBLIC_TREE_ID="${treeKey}"\nPUBLIC_HOME_SLUG="home"\nPUBLIC_SITE_NAME="${siteLabel.replace(/"/g, '\\"')}"\n`);
|
|
265
747
|
}
|
|
266
748
|
|
|
267
749
|
main().catch(console.error);
|