@brightsideoy/kama 0.1.0-alpha.110 → 0.1.0-alpha.112

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 CHANGED
@@ -87,40 +87,61 @@ At the heart of KAMA is the Schema Engine. Instead of complex database migration
87
87
 
88
88
  ## Getting Started
89
89
 
90
- KAMA comes with a scaffolding CLI to instantly spin up a full monorepo (Backend + Astro Frontend).
90
+ KAMA comes with an interactive executable CLI to instantly spin up a full monorepo (Backend Worker + optional Astro Frontend), add new sites to existing workspaces, or promote local setups to Cloudflare Edge.
91
91
 
92
- ## 1. Initialize a Workspace
92
+ ### 1. Initialize a Workspace\*\*
93
+
94
+ Run the KAMA CLI to start the workspace wizard:
93
95
 
94
96
  ```bash
95
- npx @brightsideoy/kama@alpha
97
+ npx kama init
96
98
  ```
97
99
 
98
- Select **"Kickstart new KAMA Monorepo"**. The CLI will automatically:
100
+ The CLI will prompt you for your setup preferences and automatically:
99
101
 
100
- - Authenticate with Cloudflare (if deploying to the Edge).
101
- - Create a new D1 database (or local SQLite equivalent).
102
- - Bind your R2 media buckets.
103
- - Setup the Worker backend and Astro frontend.
102
+ - Scaffold the `kama-worker` engine and content definition directories (`content/definitions/`).
103
+ - Authenticate with Cloudflare and auto-provision a remote D1 database + R2 bucket bindings (if deploying to Edge).
104
+ - Scaffold a fully configured Astro frontend site inside `sites/` with built-in edge-caching middleware.
105
+ - Configure local SQLite fallback environments for completely offline development.
104
106
 
105
- ## 2. Local Development
107
+ ### 2. Local Development
106
108
 
107
- Start the backend and frontend simultaneously on your local machine:
109
+ Start the local CMS dev server from your workspace root:
108
110
 
109
111
  ```bash
110
112
  npm run dev:cms
111
113
  ```
112
114
 
113
- This bundles your local YAML schemas, syncs the local SQLite/D1 database, and starts the Wrangler dev server on your localhost.
115
+ This command automatically compiles your YAML/JSON schemas into runtime JavaScript definitions, syncs your local SQLite database, and launches the Miniflare worker environment on `http://localhost:8787`.
116
+
117
+ ### 3. Management Commands\*\*
118
+
119
+ You can run CLI commands anytime inside your workspace.
120
+
121
+ #### Add a New Astro Frontend Site
122
+
123
+ ```bash
124
+ npx kama add site
125
+ ```
126
+
127
+ #### Promote a Local Workspace to Cloudflare Edge
128
+
129
+ Convert a local offline workspace to Cloudflare Edge (auto-links domains & D1 databases):
114
130
 
115
- ## 3. Deploy
131
+ ```bash
132
+ npx kama promote
133
+ ```
116
134
 
117
- Push your API, schemas, and database migrations to Cloudflare's global network, or package it for your virtual server:
135
+ ### 4. Deploy
136
+
137
+ Deploy your CMS engine, content definitions, and database migrations to Cloudflare Edge:
118
138
 
119
139
  ```bash
120
140
  npm run deploy:cms
121
141
  ```
122
142
 
123
- <br>
143
+ > **Note:** For self-hosted virtual servers (VPS) or Docker environments, you can run `npm run dev:cms` under PM2/Docker as a standalone `workerd` server.
144
+ > <br>
124
145
 
125
146
  ## Directory Structure Overview
126
147
 
@@ -134,7 +155,7 @@ my-kama-workspace/
134
155
  │ ├── nodes/ # e.g., page.yaml, product.yaml
135
156
  │ ├── blocks/ # e.g., hero.yaml, image_card.yaml
136
157
  │ └── node-layouts/ # e.g., two-column.yaml
137
- ├── cms-worker/ # 👉 BACKEND PIPELINE
158
+ ├── kama-worker/ # 👉 BACKEND PIPELINE
138
159
  │ ├── index.js # Worker entrypoint (ES Module)
139
160
  │ ├── wrangler.json # Bindings (D1, R2, Vars)
140
161
  │ └── package.json
package/bin/cli.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { execSync } from 'node:child_process';
3
- import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
3
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from 'node:fs';
4
4
  import { join, dirname } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import * as readline from 'node:readline/promises';
7
7
 
8
- // Dynamically resolve the current CLI version to inject into package.json files
8
+ // Dynamically resolve current package version
9
9
  const __filename = fileURLToPath(import.meta.url);
10
10
  const __dirname = dirname(__filename);
11
11
  let currentKamaVersion = 'alpha';
@@ -25,25 +25,42 @@ const rl = readline.createInterface({
25
25
  });
26
26
 
27
27
  async function main() {
28
- console.log('\n🔥 --- KAMA SITE GENERATOR --- 🔥\n');
28
+ const args = process.argv.slice(2);
29
+ const command = args[0]?.toLowerCase();
30
+ const subCommand = args[1]?.toLowerCase();
29
31
 
30
- console.log('Select mode:');
31
- console.log(' 1) Kickstart new Kama Monorepo (CMS + Optional Site)');
32
- console.log(' 2) Add a new Astro Site to current workspace (sites/)');
32
+ console.log('\n🔥 --- KAMA ENGINE CLI --- 🔥\n');
33
33
 
34
- const choice = await rl.question('\nChoice [1/2]: ');
35
-
36
- if (choice.trim() === '2') {
34
+ if (command === 'init') {
35
+ await createFullWorkspace();
36
+ } else if (command === 'add' && subCommand === 'site') {
37
37
  await createNewSiteOnly();
38
+ } else if (command === 'promote') {
39
+ await promoteLocalToCloudflare();
38
40
  } else {
39
- await createFullWorkspace();
41
+ // Interactive menu when invoked without arguments or unrecognized command
42
+ console.log('Available Commands:');
43
+ console.log(' 1) npx kama init - Kickstart new Monorepo (CMS + Optional Site)');
44
+ console.log(' 2) npx kama add site - Add new Astro Site to current workspace (sites/)');
45
+ console.log(' 3) npx kama promote - Promote current Local workspace to Cloudflare Edge');
46
+
47
+ const choice = await rl.question('\nSelect choice [1/2/3]: ');
48
+
49
+ if (choice.trim() === '3') {
50
+ await promoteLocalToCloudflare();
51
+ } else if (choice.trim() === '2') {
52
+ await createNewSiteOnly();
53
+ } else {
54
+ await createFullWorkspace();
55
+ }
40
56
  }
41
57
 
42
58
  rl.close();
43
59
  }
44
60
 
45
61
  /**
46
- * MODE 1: Kickstart full Monorepo Workspace
62
+ * COMMAND: npx kama init
63
+ * Kickstart full Monorepo Workspace
47
64
  */
48
65
  async function createFullWorkspace() {
49
66
  const projName = (await rl.question('Project folder name [kama-workspace]: ')) || 'kama-workspace';
@@ -54,14 +71,12 @@ async function createFullWorkspace() {
54
71
  const envChoice = await rl.question('\nChoice [1/2]: ');
55
72
  const isCloudflare = envChoice.trim() === '2';
56
73
 
57
- // 🎯 Ask if user wants an Astro frontend site scaffolded
58
74
  const createSiteInput = await rl.question('\n🚀 Do you want to scaffold an initial Astro frontend site? (y/n) [y]: ');
59
75
  const shouldCreateSite = createSiteInput.trim().toLowerCase() !== 'n';
60
76
 
61
77
  let cmsDomain = 'localhost:8787';
62
78
  let siteDomain = 'localhost:4321';
63
79
 
64
- // Only ask for domains if deploying to Cloudflare Edge
65
80
  if (isCloudflare) {
66
81
  cmsDomain = (await rl.question('\nCMS Admin Domain (e.g., admin.t3cloud.eu): ')) || 'admin.t3cloud.eu';
67
82
  if (shouldCreateSite) {
@@ -69,13 +84,10 @@ async function createFullWorkspace() {
69
84
  }
70
85
  }
71
86
 
72
- // Standardize scheme for localhost vs custom edge domains
73
87
  const cmsProtocol = isCloudflare ? 'https://' : 'http://';
74
88
  const cmsFullUrl = `${cmsProtocol}${cmsDomain.replace(/^https?:\/\//, '')}`;
75
-
76
89
  const rootDir = join(process.cwd(), projName);
77
90
 
78
- // 🎯 Derive dynamic worker, database, and bucket names from project folder name
79
91
  const workerName = `${projName}-worker`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
80
92
  let dbName = `${projName}-db`.replace(/[^a-zA-Z0-9_-]/g, '_');
81
93
  const bucketName = `${projName}-media-prod`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
@@ -83,22 +95,21 @@ async function createFullWorkspace() {
83
95
  console.log(`\n📁 Creating workspace folder at ./${projName}...`);
84
96
  mkdirSync(rootDir, { recursive: true });
85
97
 
86
- // 🎯 Generate root .gitignore so auto-generated bundles and secrets stay out of git
87
98
  writeFileSync(
88
99
  join(rootDir, '.gitignore'),
89
100
  `node_modules/
90
101
  .env
91
102
  .DS_Store
92
- cms-worker/.dev.vars
93
- cms-worker/bundled-definitions.js
94
- cms-worker/.wrangler/
103
+ kama-worker/.dev.vars
104
+ kama-worker/bundled-definitions.js
105
+ kama-worker/bundled-definitions.ts
106
+ kama-worker/.wrangler/
95
107
  sites/*/.env
96
108
  sites/*/.astro/
97
109
  sites/*/dist/
98
110
  `
99
111
  );
100
112
 
101
- // 🎯 Scaffold full schema directory structure
102
113
  const schemaCategories = [
103
114
  'block-layouts',
104
115
  'blocks',
@@ -113,10 +124,8 @@ sites/*/dist/
113
124
  mkdirSync(join(rootDir, 'content', 'definitions', category), { recursive: true });
114
125
  }
115
126
 
116
- // 🎯 Populate starter example YAML files
117
127
  scaffoldSampleSchemas(rootDir);
118
128
 
119
- // --- AUTO D1 CREATION MAGIC ---
120
129
  let d1DatabaseId = '00000000-0000-0000-0000-000000000000';
121
130
  let isAuthenticated = false;
122
131
 
@@ -188,20 +197,18 @@ sites/*/dist/
188
197
  } else {
189
198
  console.log(`\n💻 Local mode selected. Skipping Cloudflare authentication and domain setup...`);
190
199
  }
191
- // ------------------------------
192
200
 
193
- // 🎯 1. Root package.json (includes 'sites/*' wildcard in workspaces so user can add sites later)
194
201
  writeFileSync(
195
202
  join(rootDir, 'package.json'),
196
203
  JSON.stringify({
197
204
  name: projName,
198
205
  private: true,
199
206
  type: 'module',
200
- workspaces: ['cms-worker', 'sites/*'],
207
+ workspaces: ['kama-worker', 'sites/*'],
201
208
  scripts: {
202
209
  'bundle:schemas': 'node node_modules/@brightsideoy/kama/scripts/bundle-definitions.js',
203
- 'dev:cms': 'npm run bundle:schemas && npm run dev -w cms-worker',
204
- 'deploy:cms': 'npm run bundle:schemas && npm run deploy -w cms-worker',
210
+ 'dev:cms': 'npm run bundle:schemas && npm run dev -w kama-worker',
211
+ 'deploy:cms': 'npm run bundle:schemas && npm run deploy -w kama-worker',
205
212
  },
206
213
  dependencies: {
207
214
  '@brightsideoy/kama': currentKamaVersion,
@@ -209,16 +216,13 @@ sites/*/dist/
209
216
  }, null, 2)
210
217
  );
211
218
 
212
- // 🎯 2. Setup CMS Worker with Dynamic Names
213
219
  console.log(`\n☁️ Configuring CMS Worker '${workerName}' (${cmsDomain})...`);
214
- const cmsDir = join(rootDir, 'cms-worker');
220
+ const cmsDir = join(rootDir, 'kama-worker');
215
221
  mkdirSync(cmsDir, { recursive: true });
216
222
 
217
- // 🎯 Properly wrap and inject BUNDLED_DEFINITIONS from LOCAL workspace, not node_modules
218
223
  const cmsWorkerIndexContent = `import kamaWorker from '@brightsideoy/kama';
219
224
  import * as bundledModule from './bundled-definitions.js';
220
225
 
221
- // Resolve BUNDLED_DEFINITIONS across all module resolution formats
222
226
  const BUNDLED_DEFINITIONS =
223
227
  bundledModule.BUNDLED_DEFINITIONS ||
224
228
  bundledModule.default?.BUNDLED_DEFINITIONS ||
@@ -237,10 +241,7 @@ export default {
237
241
  };
238
242
  `;
239
243
 
240
- writeFileSync(
241
- join(cmsDir, 'index.js'),
242
- cmsWorkerIndexContent
243
- );
244
+ writeFileSync(join(cmsDir, 'index.js'), cmsWorkerIndexContent);
244
245
 
245
246
  writeFileSync(
246
247
  join(cmsDir, 'package.json'),
@@ -259,13 +260,12 @@ export default {
259
260
  '@brightsideoy/kama': currentKamaVersion,
260
261
  },
261
262
  devDependencies: {
262
- wrangler: '^3.80.0',
263
+ wrangler: '^4.0.0',
263
264
  tsx: '^4.19.2',
264
265
  },
265
266
  }, null, 2)
266
267
  );
267
268
 
268
- // 🎯 Fixed CMS wrangler.json
269
269
  const cmsWranglerConfig = {
270
270
  $schema: '../node_modules/wrangler/config-schema.json',
271
271
  name: workerName,
@@ -299,23 +299,18 @@ export default {
299
299
  cmsWranglerConfig.routes = [{ pattern: cmsDomain, custom_domain: true }];
300
300
  }
301
301
 
302
- writeFileSync(
303
- join(cmsDir, 'wrangler.json'),
304
- JSON.stringify(cmsWranglerConfig, null, 2)
305
- );
302
+ writeFileSync(join(cmsDir, 'wrangler.json'), JSON.stringify(cmsWranglerConfig, null, 2));
306
303
 
307
- // 🎯 Fixed .dev.vars
308
304
  writeFileSync(
309
305
  join(cmsDir, '.dev.vars'),
310
306
  `# Cloudflare Edge Cache Purging Secrets\nCLOUDFLARE_API_TOKEN=${isCloudflare ? 'cfut_YOUR_CUSTOM_PURGE_TOKEN' : 'local_token'}\n`
311
307
  );
312
308
 
313
- // 🎯 3. Setup Astro Site (ONLY if requested)
314
309
  if (shouldCreateSite) {
315
310
  const siteFolderName = isCloudflare ? siteDomain.split('.')[0] : 'web';
316
311
  await generateAstroSite(rootDir, siteFolderName, siteDomain, cmsFullUrl, workerName, isCloudflare);
317
312
  } else {
318
- console.log(`\nℹ️ Skipping Astro site creation. You can add a site later by running 'npx create-kama' (Mode 2) inside this workspace.`);
313
+ console.log(`\nℹ️ Skipping Astro site creation. You can add a site later by running 'npx kama add site' inside this workspace.`);
319
314
  }
320
315
 
321
316
  console.log(`\n✨ Workspace ready! Next steps:`);
@@ -326,17 +321,18 @@ export default {
326
321
  if (d1DatabaseId === '00000000-0000-0000-0000-000000000000' || d1DatabaseId === 'REPLACE_WITH_YOUR_D1_ID') {
327
322
  console.log(` 3. Run 'npx wrangler login' and create a D1 database manually.`);
328
323
  }
329
- console.log(` 4. Update CLOUDFLARE_ZONE_ID in cms-worker/wrangler.json with your actual Zone ID`);
330
- console.log(` 5. Securely store your purge token: cd cms-worker && npx wrangler secret put CLOUDFLARE_API_TOKEN`);
324
+ console.log(` 4. Update CLOUDFLARE_ZONE_ID in kama-worker/wrangler.json with your actual Zone ID`);
325
+ console.log(` 5. Securely store your purge token: cd kama-worker && npx wrangler secret put CLOUDFLARE_API_TOKEN`);
331
326
  console.log(` 6. Run 'npm run dev:cms' from the workspace root to start the backend.`);
332
327
  } else {
333
328
  console.log(` 3. Run 'npm run dev:cms' from the workspace root to start the local SQLite backend.`);
334
- console.log(`\n💡 Note: You can deploy to Cloudflare later by updating 'cms-worker/wrangler.json' with real IDs.`);
329
+ console.log(`\n💡 Note: You can deploy to Cloudflare later by running 'npx kama promote'.`);
335
330
  }
336
331
  }
337
332
 
338
333
  /**
339
- * MODE 2: Add Site to existing workspace
334
+ * COMMAND: npx kama add site
335
+ * Add Astro Site to existing workspace
340
336
  */
341
337
  async function createNewSiteOnly() {
342
338
  const rootDir = process.cwd();
@@ -347,7 +343,7 @@ async function createNewSiteOnly() {
347
343
  }
348
344
 
349
345
  let targetWorkerName = 'kama-worker';
350
- const cmsWranglerPath = join(rootDir, 'cms-worker', 'wrangler.json');
346
+ const cmsWranglerPath = join(rootDir, 'kama-worker', 'wrangler.json');
351
347
  if (existsSync(cmsWranglerPath)) {
352
348
  try {
353
349
  const cmsWrangler = JSON.parse(readFileSync(cmsWranglerPath, 'utf-8'));
@@ -378,12 +374,88 @@ async function createNewSiteOnly() {
378
374
  }
379
375
 
380
376
  /**
381
- * Helper: Scaffolds initial starter schemas for all category folders
377
+ * COMMAND: npx kama promote
378
+ * Promote current Local workspace to Cloudflare Edge
382
379
  */
380
+ async function promoteLocalToCloudflare() {
381
+ const rootDir = process.cwd();
382
+ const workerWranglerPath = join(rootDir, 'kama-worker', 'wrangler.json');
383
+
384
+ if (!existsSync(workerWranglerPath)) {
385
+ console.error('❌ Error: Could not find kama-worker/wrangler.json. Run this command inside your workspace root.');
386
+ return;
387
+ }
388
+
389
+ console.log('\n🌐 --- PROMOTING WORKSPACE TO CLOUDFLARE EDGE --- 🌐\n');
390
+
391
+ try {
392
+ execSync('npx wrangler whoami', { stdio: 'pipe' });
393
+ console.log('✅ Logged in to Cloudflare.');
394
+ } catch (e) {
395
+ console.log('🌐 Launching browser for Cloudflare login...');
396
+ try {
397
+ execSync('npx wrangler login', { stdio: 'inherit' });
398
+ } catch (loginError) {
399
+ console.log('❌ Cloudflare login failed or was cancelled.');
400
+ return;
401
+ }
402
+ }
403
+
404
+ const cmsDomain = (await rl.question('\nCMS Admin Domain (e.g., admin.t3cloud.eu): ')) || 'admin.t3cloud.eu';
405
+
406
+ const sitesFolderDir = join(rootDir, 'sites');
407
+ let siteDomain = '';
408
+ if (existsSync(sitesFolderDir)) {
409
+ siteDomain = await rl.question('Frontend Website Domain (e.g., kama.t3cloud.eu): ');
410
+ }
411
+
412
+ const currentConfig = JSON.parse(readFileSync(workerWranglerPath, 'utf-8'));
413
+ const dbName = currentConfig.d1_databases?.[0]?.database_name || 'kama_db';
414
+
415
+ console.log(`\n🗄️ Provisioning remote Cloudflare D1 Database '${dbName}'...`);
416
+ let d1DatabaseId = '00000000-0000-0000-0000-000000000000';
417
+
418
+ try {
419
+ const d1Output = execSync(`npx wrangler d1 create ${dbName}`, { encoding: 'utf-8', stdio: 'pipe' });
420
+ 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}/);
421
+ if (match) d1DatabaseId = match[0];
422
+ } catch (err) {
423
+ console.log('⚠️ Could not auto-create D1 (it may already exist). Please verify on your Cloudflare dashboard.');
424
+ }
425
+
426
+ currentConfig.vars.CDN_DOMAIN = cmsDomain;
427
+ if (siteDomain) {
428
+ currentConfig.vars.PUBLIC_FRONTEND_URL = `https://${siteDomain}`;
429
+ }
430
+ currentConfig.routes = [{ pattern: cmsDomain, custom_domain: true }];
431
+ if (currentConfig.d1_databases?.[0]) {
432
+ currentConfig.d1_databases[0].database_id = d1DatabaseId;
433
+ }
434
+
435
+ writeFileSync(workerWranglerPath, JSON.stringify(currentConfig, null, 2));
436
+
437
+ if (siteDomain && existsSync(sitesFolderDir)) {
438
+ const sites = readdirSync(sitesFolderDir);
439
+ if (sites.length > 0) {
440
+ const siteWranglerPath = join(sitesFolderDir, sites[0], 'wrangler.json');
441
+ if (existsSync(siteWranglerPath)) {
442
+ const siteConfig = JSON.parse(readFileSync(siteWranglerPath, 'utf-8'));
443
+ siteConfig.routes = [{ pattern: siteDomain, custom_domain: true }];
444
+ siteConfig.vars.PUBLIC_CMS_URL = `https://${cmsDomain}`;
445
+ siteConfig.vars.PUBLIC_API_URL = `https://${cmsDomain}`;
446
+ writeFileSync(siteWranglerPath, JSON.stringify(siteConfig, null, 2));
447
+ }
448
+ }
449
+ }
450
+
451
+ console.log(`\n✅ Workspace successfully configured for Cloudflare Edge!`);
452
+ console.log(`\nNext steps:`);
453
+ console.log(` 1. Run 'npm run deploy:cms' from the root to publish your backend and database.`);
454
+ }
455
+
383
456
  function scaffoldSampleSchemas(rootDir) {
384
457
  const defsDir = join(rootDir, 'content', 'definitions');
385
458
 
386
- // 1. Channels
387
459
  writeFileSync(join(defsDir, 'channels', 'website.yaml'), `id: website
388
460
  label: Main Website
389
461
  defaultLanguage: en
@@ -392,7 +464,6 @@ languages:
392
464
  - fi
393
465
  `);
394
466
 
395
- // 2. Nodes
396
467
  writeFileSync(join(defsDir, 'nodes', 'page.yaml'), `id: page
397
468
  label: Page
398
469
  inTree: true
@@ -431,7 +502,6 @@ tabs:
431
502
  linkedTo: title
432
503
  `);
433
504
 
434
- // 3. Blocks
435
505
  writeFileSync(join(defsDir, 'blocks', 'hero.yaml'), `id: hero
436
506
  label: Hero Section
437
507
  fields:
@@ -452,7 +522,6 @@ fields:
452
522
  type: richtext
453
523
  `);
454
524
 
455
- // 4. Block Layouts
456
525
  writeFileSync(join(defsDir, 'block-layouts', 'two-column.yaml'), `id: two-column
457
526
  label: Two Column Layout
458
527
  regions:
@@ -462,7 +531,6 @@ regions:
462
531
  label: Right Column
463
532
  `);
464
533
 
465
- // 5. Node Layouts
466
534
  writeFileSync(join(defsDir, 'node-layouts', 'single-column.yml'), `id: single-column
467
535
  label: Single Column Page
468
536
  regions:
@@ -470,7 +538,6 @@ regions:
470
538
  label: Main Content Area
471
539
  `);
472
540
 
473
- // 6. Field Sets
474
541
  writeFileSync(join(defsDir, 'field-sets', 'page-base.yaml'), `id: page-base
475
542
  label: Base Page Fields
476
543
  fields:
@@ -482,7 +549,6 @@ fields:
482
549
  type: text
483
550
  `);
484
551
 
485
- // 7. Trees
486
552
  writeFileSync(join(defsDir, 'trees', 'navigation.yaml'), `id: navigation
487
553
  label: Main Navigation Tree
488
554
  maxDepth: 3
@@ -491,9 +557,6 @@ allowedNodeTypes:
491
557
  `);
492
558
  }
493
559
 
494
- /**
495
- * Helper: Scaffolds the Astro site with middleware, wrangler.json, & slug page
496
- */
497
560
  async function generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, workerName = 'kama-worker', isCloudflare = false) {
498
561
  console.log(`\n🚀 Scaffolding Astro site (${siteFolder}) mapped to ${siteDomain} (Service: ${workerName})...`);
499
562
 
@@ -520,7 +583,7 @@ async function generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, wo
520
583
  '@astrojs/cloudflare': '^14.2.5',
521
584
  '@brightsideoy/kama': currentKamaVersion,
522
585
  astro: '^7.2.0',
523
- wrangler: '^3.80.0',
586
+ wrangler: '^4.0.0',
524
587
  },
525
588
  }, null, 2)
526
589
  );
@@ -582,7 +645,6 @@ export default defineConfig({
582
645
  `PUBLIC_CMS_URL="${cmsFullUrl}"\nPUBLIC_API_URL="${cmsFullUrl}"\n`
583
646
  );
584
647
 
585
- // 🎯 Scaffold the Base Layout
586
648
  writeFileSync(
587
649
  join(siteDir, 'src/layouts/Layout.astro'),
588
650
  `---
@@ -606,7 +668,6 @@ const { title } = Astro.props;
606
668
  </html>`
607
669
  );
608
670
 
609
- // 🎯 Scaffold the Dynamic Slug Page
610
671
  writeFileSync(
611
672
  join(siteDir, 'src/pages/[...slug].astro'),
612
673
  `---
@@ -614,10 +675,6 @@ import Layout from '../layouts/Layout.astro';
614
675
 
615
676
  const { slug } = Astro.params;
616
677
  const cmsUrl = import.meta.env.PUBLIC_CMS_URL;
617
-
618
- // Example Data Fetch: Replace with actual KAMA fetch logic
619
- // const response = await fetch(\`\${cmsUrl}/api/pages/\${slug || 'home'}\`);
620
- // const pageData = await response.json();
621
678
  ---
622
679
  <Layout title="Kama Generated Site">
623
680
  \t<main>
@@ -627,7 +684,6 @@ const cmsUrl = import.meta.env.PUBLIC_CMS_URL;
627
684
  </Layout>`
628
685
  );
629
686
 
630
- // 🎯 Scaffold the Full Edge-Caching Middleware
631
687
  writeFileSync(
632
688
  join(siteDir, 'src/middleware.ts'),
633
689
  `import { defineMiddleware } from "astro:middleware";
@@ -639,7 +695,6 @@ export const onRequest = defineMiddleware(async (context, next) => {
639
695
  const workerEnv = cfContext?.env || (env as any);
640
696
  const HTML_CACHE = workerEnv?.HTML_CACHE;
641
697
 
642
- // 🎯 1. CACHE PURGE ENDPOINT
643
698
  if (pathname === "/api/purge" || pathname === "/api/purge/") {
644
699
  if (context.request.method !== "POST") return new Response("Method Not Allowed", { status: 405 });
645
700
 
@@ -657,12 +712,11 @@ export const onRequest = defineMiddleware(async (context, next) => {
657
712
  }));
658
713
  }
659
714
 
660
- // Surgical delete
661
715
  if (Array.isArray(body.deleted_urls) && body.deleted_urls.length > 0) {
662
716
  await Promise.all(body.deleted_urls.map(async (u: string) => {
663
717
  const path = new URL(u).pathname.replace(/\\/$/, "") || "/";
664
718
  const [siteVer, pageVer] = await Promise.all([
665
- HTML_CACHE.get("site_version"), HTML_CACHE.get(\`v:\${path}\`)
719
+ HTML_CACHE.get("site_version"), HTML_CACHE.get(\`v:\${cleanLookupPath}\`)
666
720
  ]);
667
721
  if (siteVer || pageVer) {
668
722
  await HTML_CACHE.delete(\`\${path}:v:\${siteVer || "1"}:\${pageVer || "1"}\`);
@@ -672,7 +726,6 @@ export const onRequest = defineMiddleware(async (context, next) => {
672
726
  }
673
727
  }
674
728
 
675
- // Astro Worker Cache layer flush
676
729
  if (typeof cfContext?.cache?.purge === 'function') await cfContext.cache.purge({ purgeEverything: true });
677
730
 
678
731
  return new Response(JSON.stringify({ success: true }), { status: 200 });
@@ -681,7 +734,6 @@ export const onRequest = defineMiddleware(async (context, next) => {
681
734
  }
682
735
  }
683
736
 
684
- // 🎯 2. KV HTML STATIC LOOKUP
685
737
  const isWarmer = context.request.headers.get("X-Cache-Warmer") === "true";
686
738
  const isHtmlPage = !pathname.includes('.') && !pathname.startsWith('/api/');
687
739
  const cleanLookupPath = pathname.replace(/\\/$/, "") || "/";
@@ -713,7 +765,6 @@ export const onRequest = defineMiddleware(async (context, next) => {
713
765
 
714
766
  const response = await next();
715
767
 
716
- // 🎯 3. KV HTML STATIC WRITE
717
768
  if (isHtmlPage && HTML_CACHE && response.status === 200 && response.headers.get("content-type")?.includes("text/html")) {
718
769
  const htmlText = await response.clone().text();
719
770
  const writeTask = (async () => {