@brightsideoy/kama 0.1.0-alpha.113 → 0.1.0-alpha.115

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.
Files changed (60) hide show
  1. package/bin/cli.js +172 -379
  2. package/content/definitions/blocks/button.yaml +7 -8
  3. package/content/definitions/blocks/header.yaml +7 -9
  4. package/content/definitions/blocks/header_image.yaml +14 -15
  5. package/content/definitions/blocks/hero.yaml +22 -21
  6. package/content/definitions/blocks/image_card.yaml +46 -47
  7. package/content/definitions/blocks/single_image.yaml +19 -20
  8. package/content/definitions/blocks/text.yaml +8 -9
  9. package/content/definitions/channels/ecommerce.yaml +14 -16
  10. package/content/definitions/channels/website.yaml +20 -21
  11. package/content/definitions/field-sets/access.yaml +3 -5
  12. package/content/definitions/field-sets/channel-base.yaml +25 -25
  13. package/content/definitions/field-sets/content-block.yaml +36 -41
  14. package/content/definitions/field-sets/page-base.yaml +40 -45
  15. package/content/definitions/field-sets/page-meta.yaml +1 -4
  16. package/content/definitions/field-sets/page-social.yaml +6 -8
  17. package/content/definitions/field-sets/tree-base.yaml +25 -25
  18. package/content/definitions/nodes/address.yaml +29 -21
  19. package/content/definitions/nodes/article.yaml +24 -19
  20. package/content/definitions/nodes/event.yaml +32 -26
  21. package/content/definitions/nodes/external-link.yaml +20 -17
  22. package/content/definitions/nodes/page.yaml +38 -35
  23. package/content/definitions/nodes/person.yaml +29 -27
  24. package/content/definitions/nodes/product.yaml +52 -49
  25. package/content/definitions/trees/navigation.yaml +26 -21
  26. package/content/definitions/trees/social-links.yaml +5 -2
  27. package/dist/assets/index-8T39DEP3.js +162 -0
  28. package/dist/assets/index-B9oh2_TL.css +1 -0
  29. package/dist/index.html +2 -2
  30. package/dist/index.js +6492 -4249
  31. package/package.json +5 -6
  32. package/schema.sql +5 -13
  33. package/templates/astro-starter/astro.config.mjs +40 -0
  34. package/templates/astro-starter/package.json.template +20 -0
  35. package/templates/astro-starter/src/components/blocks/BlockRenderer.astro +125 -0
  36. package/templates/astro-starter/src/components/blocks/ColumnLayout.astro +66 -0
  37. package/templates/astro-starter/src/components/blocks/HeaderImage.astro +131 -0
  38. package/templates/astro-starter/src/components/blocks/Hero.astro +218 -0
  39. package/templates/astro-starter/src/components/blocks/ImageBlock.astro +160 -0
  40. package/templates/astro-starter/src/components/blocks/TextBlock.astro +13 -0
  41. package/templates/astro-starter/src/components/global/Breadcrumb.astro +62 -0
  42. package/templates/astro-starter/src/components/global/Footer.astro +48 -0
  43. package/templates/astro-starter/src/components/global/Header.astro +92 -0
  44. package/templates/astro-starter/src/components/global/NavigationMenu.astro +450 -0
  45. package/templates/astro-starter/src/components/ui/KamaImage.astro +148 -0
  46. package/templates/astro-starter/src/kama-image-service.ts +41 -0
  47. package/templates/astro-starter/src/layouts/Layout.astro +280 -0
  48. package/templates/astro-starter/src/lib/cms.ts +149 -0
  49. package/templates/astro-starter/src/lib/media-url.ts +34 -0
  50. package/templates/astro-starter/src/middleware.ts +306 -0
  51. package/templates/astro-starter/src/pages/404.astro +46 -0
  52. package/templates/astro-starter/src/pages/[...slug].astro +234 -0
  53. package/templates/astro-starter/src/pages/media/[...path].ts +113 -0
  54. package/templates/astro-starter/tsconfig.json +0 -0
  55. package/test-local.sh +76 -0
  56. package/dist/assets/index-10GEw0KY.css +0 -1
  57. package/dist/assets/index-THJ6BqPP.js +0 -1394
  58. package/kama-worker/bundled-definitions.js +0 -2394
  59. package/kama-worker/bundled-definitions.ts +0 -2395
  60. package/scripts/bundle-definitions.js +0 -224
package/bin/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { execSync } from 'node:child_process';
3
- import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from 'node:fs';
4
- import { join, dirname, basename } from 'node:path';
3
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, cpSync, unlinkSync, statSync } from 'node:fs';
4
+ import { join, dirname, basename, relative, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import * as readline from 'node:readline/promises';
7
7
 
@@ -37,17 +37,21 @@ async function main() {
37
37
  await createNewSiteOnly();
38
38
  } else if (command === 'promote') {
39
39
  await promoteLocalToCloudflare();
40
+ } else if (command === 'bundle') {
41
+ await bundleWorkspaceSchemas();
40
42
  } else {
41
- // Interactive menu when invoked without arguments or unrecognized command
42
43
  console.log('Available Commands:');
43
44
  console.log(' 1) npx kama init - Kickstart new Monorepo (CMS + Optional Site)');
44
45
  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
+ console.log(' 3) npx kama bundle - Bundle YAML schemas for Edge deployment');
47
+ console.log(' 4) npx kama promote - Promote current Local workspace to Cloudflare Edge');
46
48
 
47
- const choice = await rl.question('\nSelect choice [1/2/3]: ');
49
+ const choice = await rl.question('\nSelect choice [1/2/3/4]: ');
48
50
 
49
- if (choice.trim() === '3') {
51
+ if (choice.trim() === '4') {
50
52
  await promoteLocalToCloudflare();
53
+ } else if (choice.trim() === '3') {
54
+ await bundleWorkspaceSchemas();
51
55
  } else if (choice.trim() === '2') {
52
56
  await createNewSiteOnly();
53
57
  } else {
@@ -101,6 +105,8 @@ async function createFullWorkspace() {
101
105
  let dbName = `${projName}-db`.replace(/[^a-zA-Z0-9_-]/g, '_');
102
106
  const bucketName = `${projName}-media-prod`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
103
107
 
108
+ const siteFolderName = isCloudflare ? siteDomain.split('.')[0] : 'web';
109
+
104
110
  if (!isCurrentDir) {
105
111
  console.log(`\n📁 Creating workspace folder at ./${projName}...`);
106
112
  mkdirSync(rootDir, { recursive: true });
@@ -114,31 +120,23 @@ async function createFullWorkspace() {
114
120
  .env
115
121
  .DS_Store
116
122
  kama-worker/.dev.vars
117
- kama-worker/bundled-definitions.js
118
- kama-worker/bundled-definitions.ts
119
123
  kama-worker/.wrangler/
120
124
  sites/*/.env
121
125
  sites/*/.astro/
122
126
  sites/*/dist/
127
+ kama-worker/workspace-schemas.js
123
128
  `
124
129
  );
125
130
 
126
- const schemaCategories = [
127
- 'block-layouts',
128
- 'blocks',
129
- 'channels',
130
- 'field-sets',
131
- 'node-layouts',
132
- 'nodes',
133
- 'trees'
134
- ];
135
-
136
- for (const category of schemaCategories) {
137
- mkdirSync(join(rootDir, 'content', 'definitions', category), { recursive: true });
131
+ console.log(`\n📄 Copying default content definitions...`);
132
+ const sourceDefsDir = join(__dirname, '../content/definitions');
133
+ const targetDefsDir = join(rootDir, 'content', 'definitions');
134
+ if (existsSync(sourceDefsDir)) {
135
+ cpSync(sourceDefsDir, targetDefsDir, { recursive: true });
136
+ } else {
137
+ console.warn(`⚠️ Could not find default content definitions at ${sourceDefsDir}`);
138
138
  }
139
139
 
140
- scaffoldSampleSchemas(rootDir);
141
-
142
140
  let d1DatabaseId = '00000000-0000-0000-0000-000000000000';
143
141
  let isAuthenticated = false;
144
142
 
@@ -164,7 +162,6 @@ sites/*/dist/
164
162
  try {
165
163
  console.log(`\n🗄️ Checking existing Cloudflare D1 databases...`);
166
164
  const listOutput = execSync(`npx wrangler d1 list --json`, { encoding: 'utf-8', stdio: 'pipe' });
167
-
168
165
  const jsonStart = listOutput.indexOf('[');
169
166
  const jsonEnd = listOutput.lastIndexOf(']') + 1;
170
167
  let databases = [];
@@ -211,46 +208,63 @@ sites/*/dist/
211
208
  console.log(`\n💻 Local mode selected. Skipping Cloudflare authentication and domain setup...`);
212
209
  }
213
210
 
211
+ const rootScripts = {
212
+ 'dev:cms': 'npm run dev -w kama-worker',
213
+ 'deploy:cms': 'npm run deploy -w kama-worker',
214
+ };
215
+
216
+ const rootPkg = {
217
+ name: projName,
218
+ private: true,
219
+ type: 'module',
220
+ workspaces: ['kama-worker', 'sites/*'],
221
+ scripts: rootScripts,
222
+ dependencies: {
223
+ '@brightsideoy/kama': currentKamaVersion,
224
+ },
225
+ devDependencies: {
226
+ 'wrangler': '^4.130.0' // 🎯 UPDATED TO V4
227
+ }
228
+ };
229
+
230
+ const bundleScript = 'npx kama bundle || node ./node_modules/@brightsideoy/kama/bin/cli.js bundle';
231
+
232
+ if (shouldCreateSite) {
233
+ rootPkg.devDependencies['concurrently'] = '^9.0.0';
234
+ rootPkg.scripts['dev:site'] = `npm run dev -w sites/${siteFolderName}`;
235
+
236
+ // 🎯 Inject schema bundling hook with fallback execution path
237
+ rootPkg.scripts['predev'] = bundleScript;
238
+ rootPkg.scripts['dev'] = 'npx concurrently "npm run dev:cms" "npm run dev:site"';
239
+ rootPkg.scripts['deploy:site'] = `npm run deploy -w sites/${siteFolderName}`;
240
+ } else {
241
+ rootPkg.scripts['predev'] = bundleScript;
242
+ rootPkg.scripts['dev'] = 'npm run dev:cms';
243
+ }
244
+
214
245
  writeFileSync(
215
246
  join(rootDir, 'package.json'),
216
- JSON.stringify({
217
- name: projName,
218
- private: true,
219
- type: 'module',
220
- workspaces: ['kama-worker', 'sites/*'],
221
- scripts: {
222
- 'bundle:schemas': 'node node_modules/@brightsideoy/kama/scripts/bundle-definitions.js',
223
- 'dev:cms': 'npm run bundle:schemas && npm run dev -w kama-worker',
224
- 'deploy:cms': 'npm run bundle:schemas && npm run deploy -w kama-worker',
225
- },
226
- dependencies: {
227
- '@brightsideoy/kama': currentKamaVersion,
228
- }
229
- }, null, 2)
247
+ JSON.stringify(rootPkg, null, 2)
230
248
  );
231
249
 
232
250
  console.log(`\n☁️ Configuring CMS Worker '${workerName}' (${cmsDomain})...`);
233
251
  const cmsDir = join(rootDir, 'kama-worker');
234
252
  mkdirSync(cmsDir, { recursive: true });
235
253
 
254
+ // 🎯 Worker entry point now attempts to load the workspace schemas
236
255
  const cmsWorkerIndexContent = `import kamaWorker from '@brightsideoy/kama';
237
- import * as bundledModule from './bundled-definitions.js';
238
-
239
- const BUNDLED_DEFINITIONS =
240
- bundledModule.BUNDLED_DEFINITIONS ||
241
- bundledModule.default?.BUNDLED_DEFINITIONS ||
242
- bundledModule.default ||
243
- bundledModule;
256
+ import customSchemas from './workspace-schemas.js';
244
257
 
245
258
  export default {
246
259
  async fetch(request, env, ctx) {
247
- const customEnv = {
248
- ...env,
249
- BUNDLED_DEFINITIONS,
250
- };
251
-
252
- return kamaWorker.fetch(request, customEnv, ctx);
253
- },
260
+ if (typeof kamaWorker === 'function') {
261
+ return kamaWorker(request, env, ctx, customSchemas);
262
+ }
263
+ if (kamaWorker && typeof kamaWorker.fetch === 'function') {
264
+ return kamaWorker.fetch(request, env, ctx, customSchemas);
265
+ }
266
+ return new Response('Kama Engine Worker Ready', { status: 200 });
267
+ }
254
268
  };
255
269
  `;
256
270
 
@@ -263,9 +277,9 @@ export default {
263
277
  type: 'module',
264
278
  version: '1.0.0',
265
279
  scripts: {
266
- "bundle:schemas": "node ../node_modules/@brightsideoy/kama/scripts/bundle-definitions.js",
267
- "schema:local": `npm run bundle:schemas && wrangler d1 execute ${dbName} --local --file=../node_modules/@brightsideoy/kama/schema.sql`,
268
- "schema:remote": `npm run bundle:schemas && wrangler d1 execute ${dbName} --remote --file=../node_modules/@brightsideoy/kama/schema.sql`,
280
+ // 🎯 SILENCED SQL ARRAYS ON LOCAL DEV
281
+ "schema:local": `wrangler d1 execute ${dbName} --local --file=../node_modules/@brightsideoy/kama/schema.sql > /dev/null 2>&1`,
282
+ "schema:remote": `wrangler d1 execute ${dbName} --remote --file=../node_modules/@brightsideoy/kama/schema.sql`,
269
283
  "dev": "npm run schema:local && wrangler dev",
270
284
  "deploy": "npm run schema:remote && wrangler deploy"
271
285
  },
@@ -273,8 +287,8 @@ export default {
273
287
  '@brightsideoy/kama': currentKamaVersion,
274
288
  },
275
289
  devDependencies: {
276
- wrangler: '^4.0.0',
277
- tsx: '^4.19.2',
290
+ "wrangler": "^4.130.0", // 🎯 UPDATED TO V4
291
+ "tsx": "^4.19.2",
278
292
  },
279
293
  }, null, 2)
280
294
  );
@@ -319,8 +333,10 @@ export default {
319
333
  `# Cloudflare Edge Cache Purging Secrets\nCLOUDFLARE_API_TOKEN=${isCloudflare ? 'cfut_YOUR_CUSTOM_PURGE_TOKEN' : 'local_token'}\n`
320
334
  );
321
335
 
336
+ // Attempt an initial bundle right away so it exists before the user runs anything
337
+ await bundleWorkspaceSchemas(rootDir);
338
+
322
339
  if (shouldCreateSite) {
323
- const siteFolderName = isCloudflare ? siteDomain.split('.')[0] : 'web';
324
340
  await generateAstroSite(rootDir, siteFolderName, siteDomain, cmsFullUrl, workerName, isCloudflare);
325
341
  } else {
326
342
  console.log(`\nℹ️ Skipping Astro site creation. You can add a site later by running 'npx kama add site' inside this workspace.`);
@@ -340,9 +356,9 @@ export default {
340
356
  }
341
357
  console.log(` 4. Update CLOUDFLARE_ZONE_ID in kama-worker/wrangler.json with your actual Zone ID`);
342
358
  console.log(` 5. Securely store your purge token: cd kama-worker && npx wrangler secret put CLOUDFLARE_API_TOKEN`);
343
- console.log(` 6. Run 'npm run dev:cms' from the workspace root to start the backend.`);
359
+ console.log(` 6. Run 'npm run dev' from the workspace root to start everything.`);
344
360
  } else {
345
- console.log(` 3. Run 'npm run dev:cms' from the workspace root to start the local SQLite backend.`);
361
+ console.log(` 3. Run 'npm run dev' from the workspace root to start the local backend and frontend.`);
346
362
  console.log(`\n💡 Note: You can deploy to Cloudflare later by running 'npx kama promote'.`);
347
363
  }
348
364
  }
@@ -390,6 +406,63 @@ async function createNewSiteOnly() {
390
406
  console.log(`\n✨ Site created in sites/${siteFolder}! Run 'npm install' at workspace root.`);
391
407
  }
392
408
 
409
+ /**
410
+ * COMMAND: npx kama bundle
411
+ * Reads YAML from workspace and builds a static module for the worker
412
+ */
413
+ async function bundleWorkspaceSchemas(targetRootDir) {
414
+ const rootDir = targetRootDir || process.cwd();
415
+ const defsDir = join(rootDir, 'content', 'definitions');
416
+ const workerDir = join(rootDir, 'kama-worker');
417
+
418
+ if (!existsSync(defsDir)) {
419
+ console.warn(`\n⚠️ Skipping schema bundling: 'content/definitions' folder not found at ${defsDir}`);
420
+ return;
421
+ }
422
+
423
+ console.log(`\n📦 Bundling workspace YAML schemas for Edge deployment...`);
424
+
425
+ function crawlDirectory(dir, baseDir, result = {}) {
426
+ if (!existsSync(dir)) return result;
427
+ const items = readdirSync(dir);
428
+ for (const item of items) {
429
+ const fullPath = join(dir, item);
430
+ if (existsSync(fullPath) && statSync(fullPath).isDirectory()) {
431
+ crawlDirectory(fullPath, baseDir, result);
432
+ } else if (fullPath.endsWith('.yaml') || fullPath.endsWith('.yml')) {
433
+ const relativePath = './' + relative(baseDir, fullPath).replace(/\\/g, '/');
434
+ result[relativePath] = readFileSync(fullPath, 'utf-8');
435
+ }
436
+ }
437
+ return result;
438
+ }
439
+
440
+ const rawGlobs = {
441
+ nodes: crawlDirectory(join(defsDir, 'nodes'), defsDir),
442
+ trees: crawlDirectory(join(defsDir, 'trees'), defsDir),
443
+ blocks: crawlDirectory(join(defsDir, 'blocks'), defsDir),
444
+ nodeLayouts: crawlDirectory(join(defsDir, 'node-layouts'), defsDir),
445
+ blockLayouts: crawlDirectory(join(defsDir, 'block-layouts'), defsDir),
446
+ fieldSets: crawlDirectory(join(defsDir, 'field-sets'), defsDir),
447
+ channels: crawlDirectory(join(defsDir, 'channels'), defsDir),
448
+ };
449
+
450
+ const fileContent = `// AUTO-GENERATED BUNDLE - DO NOT EDIT
451
+ // This file is generated by 'npx kama bundle' prior to local dev or deployment.
452
+
453
+ export default ${JSON.stringify(rawGlobs, null, 2)};
454
+ `;
455
+
456
+ if (!existsSync(workerDir)) {
457
+ mkdirSync(workerDir, { recursive: true });
458
+ }
459
+
460
+ const outPath = join(workerDir, 'workspace-schemas.js');
461
+ writeFileSync(outPath, fileContent, 'utf-8');
462
+
463
+ console.log(`✅ Schema bundle generated successfully at ${outPath}`);
464
+ }
465
+
393
466
  /**
394
467
  * COMMAND: npx kama promote
395
468
  * Promote current Local workspace to Cloudflare Edge
@@ -470,342 +543,62 @@ async function promoteLocalToCloudflare() {
470
543
  console.log(` 1. Run 'npm run deploy:cms' from the root to publish your backend and database.`);
471
544
  }
472
545
 
473
- function scaffoldSampleSchemas(rootDir) {
474
- const defsDir = join(rootDir, 'content', 'definitions');
475
-
476
- writeFileSync(join(defsDir, 'channels', 'website.yaml'), `id: website
477
- label: Main Website
478
- defaultLanguage: en
479
- languages:
480
- - en
481
- - fi
482
- `);
483
-
484
- writeFileSync(join(defsDir, 'nodes', 'page.yaml'), `id: page
485
- label: Page
486
- inTree: true
487
- inVault: false
488
- hasCanvas: true
489
- tabs:
490
- settings:
491
- label: Settings
492
- fields:
493
- title:
494
- label: Page Title
495
- type: text
496
- required: true
497
- slug:
498
- label: URL Slug
499
- type: slug
500
- linkedTo: title
501
- `);
502
-
503
- writeFileSync(join(defsDir, 'nodes', 'article.yaml'), `id: article
504
- label: Article
505
- inTree: false
506
- inVault: true
507
- hasCanvas: true
508
- tabs:
509
- settings:
510
- label: Settings
511
- fields:
512
- title:
513
- label: Title
514
- type: text
515
- required: true
516
- slug:
517
- label: Slug
518
- type: slug
519
- linkedTo: title
520
- `);
521
-
522
- writeFileSync(join(defsDir, 'blocks', 'hero.yaml'), `id: hero
523
- label: Hero Section
524
- fields:
525
- heading:
526
- label: Heading
527
- type: text
528
- required: true
529
- subheading:
530
- label: Subheading
531
- type: text
532
- `);
533
-
534
- writeFileSync(join(defsDir, 'blocks', 'text.yaml'), `id: text
535
- label: Text Block
536
- fields:
537
- content:
538
- label: Content
539
- type: richtext
540
- `);
541
-
542
- writeFileSync(join(defsDir, 'block-layouts', 'two-column.yaml'), `id: two-column
543
- label: Two Column Layout
544
- regions:
545
- left:
546
- label: Left Column
547
- right:
548
- label: Right Column
549
- `);
550
-
551
- writeFileSync(join(defsDir, 'node-layouts', 'single-column.yml'), `id: single-column
552
- label: Single Column Page
553
- regions:
554
- main:
555
- label: Main Content Area
556
- `);
557
-
558
- writeFileSync(join(defsDir, 'field-sets', 'page-base.yaml'), `id: page-base
559
- label: Base Page Fields
560
- fields:
561
- metaTitle:
562
- label: Meta Title
563
- type: text
564
- metaDescription:
565
- label: Meta Description
566
- type: text
567
- `);
568
-
569
- writeFileSync(join(defsDir, 'trees', 'navigation.yaml'), `id: navigation
570
- label: Main Navigation Tree
571
- maxDepth: 3
572
- allowedNodeTypes:
573
- - page
574
- `);
575
- }
576
-
546
+ /**
547
+ * Helper: Copies the Astro starter template and hydrates runtime tokens
548
+ */
577
549
  async function generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, workerName = 'kama-worker', isCloudflare = false) {
578
550
  console.log(`\n🚀 Scaffolding Astro site (${siteFolder}) mapped to ${siteDomain} (Service: ${workerName})...`);
579
551
 
580
552
  const siteDir = join(rootDir, 'sites', siteFolder);
581
- mkdirSync(join(siteDir, 'src/pages'), { recursive: true });
582
- mkdirSync(join(siteDir, 'src/layouts'), { recursive: true });
583
- mkdirSync(join(siteDir, 'src/components/blocks'), { recursive: true });
584
- mkdirSync(join(siteDir, 'src/lib'), { recursive: true });
585
-
586
- writeFileSync(
587
- join(siteDir, 'package.json'),
588
- JSON.stringify({
589
- name: siteFolder,
590
- type: 'module',
591
- version: '0.0.1',
592
- engines: { node: '>=22.12.0' },
593
- scripts: {
594
- dev: 'astro dev',
595
- build: 'astro build',
596
- preview: 'astro preview',
597
- deploy: 'astro build && wrangler deploy',
598
- },
599
- dependencies: {
600
- '@astrojs/cloudflare': '^14.2.5',
601
- '@brightsideoy/kama': currentKamaVersion,
602
- astro: '^7.2.0',
603
- wrangler: '^4.0.0',
604
- },
605
- }, null, 2)
606
- );
553
+ const templateDir = join(__dirname, '../templates/astro-starter');
607
554
 
608
- const siteWranglerConfig = {
609
- $schema: 'node_modules/wrangler/config-schema.json',
610
- name: siteFolder,
611
- compatibility_date: '2026-08-01',
612
- compatibility_flags: ['nodejs_compat'],
613
- assets: {
614
- directory: './dist/client',
615
- binding: 'ASSETS',
616
- html_handling: 'drop-trailing-slash',
617
- not_found_handling: '404-page',
618
- run_worker_first: true
619
- },
620
- services: [{ binding: 'CMS_WORKER', service: workerName }],
621
- kv_namespaces: [{ binding: "HTML_CACHE", id: "REPLACE_WITH_YOUR_KV_ID", preview_id: "LOCAL_DEV_KV" }],
622
- vars: {
623
- PUBLIC_CMS_URL: cmsFullUrl,
624
- PUBLIC_API_URL: cmsFullUrl,
625
- },
626
- };
627
-
628
- if (isCloudflare) {
629
- siteWranglerConfig.routes = [{ pattern: siteDomain, custom_domain: true }];
555
+ if (!existsSync(templateDir)) {
556
+ console.error(`❌ Error: Astro template not found at ${templateDir}`);
557
+ console.error(`Please ensure your Kama package installation is complete and templates are published.`);
558
+ return;
630
559
  }
631
560
 
632
- writeFileSync(
633
- join(siteDir, 'wrangler.json'),
634
- JSON.stringify(siteWranglerConfig, null, 2)
635
- );
636
-
637
- const cmsHostname = cmsFullUrl.replace(/^https?:\/\//, '').split(':')[0];
638
-
639
- const astroConfig = `import { defineConfig } from 'astro/config';
640
- import cloudflare from '@astrojs/cloudflare';
641
-
642
- export default defineConfig({
643
- output: 'server',
644
- trailingSlash: 'ignore',
645
- image: {
646
- remotePatterns: [
647
- { protocol: 'http', hostname: 'localhost', port: '8787' },
648
- { protocol: '${isCloudflare ? 'https' : 'http'}', hostname: '${cmsHostname}' }
649
- ]
650
- },
651
- build: {
652
- inlineStylesheets: 'always',
653
- },
654
- adapter: cloudflare({
655
- imageService: 'cloudflare',
656
- })
657
- });`;
658
- writeFileSync(join(siteDir, 'astro.config.mjs'), astroConfig);
659
-
660
- writeFileSync(
661
- join(siteDir, '.env'),
662
- `PUBLIC_CMS_URL="${cmsFullUrl}"\nPUBLIC_API_URL="${cmsFullUrl}"\n`
663
- );
561
+ // 1. Copy the full, complete starter template directory directly
562
+ cpSync(templateDir, siteDir, { recursive: true });
664
563
 
665
- writeFileSync(
666
- join(siteDir, 'src/layouts/Layout.astro'),
667
- `---
668
- export interface Props {
669
- \ttitle: string;
670
- }
671
- const { title } = Astro.props;
672
- ---
673
- <!DOCTYPE html>
674
- <html lang="en">
675
- \t<head>
676
- \t\t<meta charset="UTF-8" />
677
- \t\t<meta name="viewport" content="width=device-width" />
678
- \t\t<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
679
- \t\t<meta name="generator" content={Astro.generator} />
680
- \t\t<title>{title}</title>
681
- \t</head>
682
- \t<body>
683
- \t\t<slot />
684
- \t</body>
685
- </html>`
686
- );
687
-
688
- writeFileSync(
689
- join(siteDir, 'src/pages/[...slug].astro'),
690
- `---
691
- import Layout from '../layouts/Layout.astro';
692
-
693
- const { slug } = Astro.params;
694
- const cmsUrl = import.meta.env.PUBLIC_CMS_URL;
695
- ---
696
- <Layout title="Kama Generated Site">
697
- \t<main>
698
- \t\t<h1>Welcome to your new Kama Site</h1>
699
- \t\t<p>Current route: <strong>{slug || '/'}</strong></p>
700
- \t</main>
701
- </Layout>`
702
- );
564
+ // 2. Hydrate package.json from template
565
+ const templatePkgPath = join(siteDir, 'package.json.template');
566
+ if (existsSync(templatePkgPath)) {
567
+ const pkgContent = readFileSync(templatePkgPath, 'utf-8')
568
+ .replace(/\{\{SITE_NAME\}\}/g, siteFolder)
569
+ .replace(/\{\{KAMA_VERSION\}\}/g, currentKamaVersion);
703
570
 
704
- writeFileSync(
705
- join(siteDir, 'src/middleware.ts'),
706
- `import { defineMiddleware } from "astro:middleware";
707
- import { env } from "cloudflare:workers";
708
-
709
- export const onRequest = defineMiddleware(async (context, next) => {
710
- const pathname = context.url.pathname;
711
- const cfContext = (context.locals as any).cfContext;
712
- const workerEnv = cfContext?.env || (env as any);
713
- const HTML_CACHE = workerEnv?.HTML_CACHE;
714
- const cleanLookupPath = pathname.replace(/\\/$/, "") || "/";
715
-
716
- if (pathname === "/api/purge" || pathname === "/api/purge/") {
717
- if (context.request.method !== "POST") return new Response("Method Not Allowed", { status: 405 });
718
-
719
- try {
720
- const body = await context.request.json();
721
- const now = Date.now().toString();
722
-
723
- if (HTML_CACHE) {
724
- if (body.purge_everything) {
725
- await HTML_CACHE.put("site_version", now);
726
- } else if (Array.isArray(body.urls) && body.urls.length > 0) {
727
- await Promise.all(body.urls.map((u: string) => {
728
- const path = new URL(u).pathname.replace(/\\/$/, "") || "/";
729
- return HTML_CACHE.put(\`v:\${path}\`, now);
730
- }));
731
- }
732
-
733
- if (Array.isArray(body.deleted_urls) && body.deleted_urls.length > 0) {
734
- await Promise.all(body.deleted_urls.map(async (u: string) => {
735
- const path = new URL(u).pathname.replace(/\\/$/, "") || "/";
736
- const [siteVer, pageVer] = await Promise.all([
737
- HTML_CACHE.get("site_version"), HTML_CACHE.get(\`v:\${cleanLookupPath}\`)
738
- ]);
739
- if (siteVer || pageVer) {
740
- await HTML_CACHE.delete(\`\${path}:v:\${siteVer || "1"}:\${pageVer || "1"}\`);
741
- }
742
- await HTML_CACHE.delete(\`v:\${path}\`);
743
- }));
744
- }
745
- }
746
-
747
- if (typeof cfContext?.cache?.purge === 'function') await cfContext.cache.purge({ purgeEverything: true });
748
-
749
- return new Response(JSON.stringify({ success: true }), { status: 200 });
750
- } catch (err: any) {
751
- return new Response(JSON.stringify({ error: err.message }), { status: 500 });
752
- }
571
+ writeFileSync(join(siteDir, 'package.json'), pkgContent);
572
+ unlinkSync(templatePkgPath);
753
573
  }
754
574
 
755
- const isWarmer = context.request.headers.get("X-Cache-Warmer") === "true";
756
- const isHtmlPage = !pathname.includes('.') && !pathname.startsWith('/api/');
757
- let activeCacheKey = cleanLookupPath;
758
-
759
- if (isHtmlPage && HTML_CACHE && !isWarmer) {
760
- try {
761
- const [siteVer, pageVer] = await Promise.all([
762
- HTML_CACHE.get("site_version"),
763
- HTML_CACHE.get(\`v:\${cleanLookupPath}\`)
764
- ]);
765
- activeCacheKey = \`\${cleanLookupPath}:v:\${siteVer || "1"}:\${pageVer || "1"}\`;
766
-
767
- const cachedHtml = await HTML_CACHE.get(activeCacheKey);
768
- if (cachedHtml) {
769
- return new Response(cachedHtml, {
770
- status: 200,
771
- headers: {
772
- "Content-Type": "text/html; charset=utf-8",
773
- "X-Cache-Status": "HIT-KV-STATICFILE",
774
- "Cache-Control": "no-store, no-cache, must-revalidate"
775
- }
776
- });
777
- }
778
- } catch (e) {
779
- console.warn("[Middleware] KV skip:", e);
575
+ // 3. Hydrate wrangler.json tokens
576
+ const wranglerPath = join(siteDir, 'wrangler.json');
577
+ if (existsSync(wranglerPath)) {
578
+ let wranglerStr = readFileSync(wranglerPath, 'utf-8')
579
+ .replace(/\{\{SITE_NAME\}\}/g, siteFolder)
580
+ .replace(/\{\{WORKER_NAME\}\}/g, workerName)
581
+ .replace(/\{\{CMS_URL\}\}/g, cmsFullUrl);
582
+
583
+ if (isCloudflare) {
584
+ const configObj = JSON.parse(wranglerStr);
585
+ configObj.routes = [{ pattern: siteDomain, custom_domain: true }];
586
+ wranglerStr = JSON.stringify(configObj, null, 2);
780
587
  }
781
- }
782
588
 
783
- const response = await next();
784
-
785
- if (isHtmlPage && HTML_CACHE && response.status === 200 && response.headers.get("content-type")?.includes("text/html")) {
786
- const htmlText = await response.clone().text();
787
- const writeTask = (async () => {
788
- if (activeCacheKey === cleanLookupPath || isWarmer) {
789
- const forcedVersion = Date.now().toString();
790
- await HTML_CACHE.put(\`v:\${cleanLookupPath}\`, forcedVersion);
791
- const siteVer = await HTML_CACHE.get("site_version") || "1";
792
- activeCacheKey = \`\${cleanLookupPath}:v:\${siteVer}:\${forcedVersion}\`;
793
- }
794
- await HTML_CACHE.put(activeCacheKey, htmlText);
795
- })();
796
-
797
- if (cfContext?.waitUntil) cfContext.waitUntil(writeTask);
589
+ writeFileSync(wranglerPath, wranglerStr);
798
590
  }
799
591
 
800
- return response;
801
- });`
802
- );
592
+ // 4. Create dynamic .env file
593
+ const envContent = [
594
+ `PUBLIC_CMS_URL="${cmsFullUrl}"`,
595
+ `PUBLIC_API_URL="${cmsFullUrl}"`,
596
+ `PUBLIC_CHANNEL_ID="website"`,
597
+ `PUBLIC_TREE_ID="main-navigation"`,
598
+ `PUBLIC_SITE_NAME="KAMA Starter"`
599
+ ].join('\n') + '\n';
803
600
 
804
- if (isCloudflare) {
805
- console.log(`\n⚠️ Note for Astro Frontend: You will need to create a KV Namespace for the HTML Cache.`);
806
- console.log(` Run: npx wrangler kv:namespace create HTML_CACHE`);
807
- console.log(` Then update the ID in sites/${siteFolder}/wrangler.json`);
808
- }
601
+ writeFileSync(join(siteDir, '.env'), envContent);
809
602
  }
810
603
 
811
604
  main().catch(console.error);