@brightsideoy/kama 0.1.0-alpha.155 → 0.1.0-alpha.157

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 (33) hide show
  1. package/bin/cli.js +229 -231
  2. package/dist/assets/index-BesA3e7A.js +164 -0
  3. package/dist/assets/index-D6yHZ_u1.css +1 -0
  4. package/dist/{dist-C-WAK7EF.js → dist-mic4UZBb.js} +691 -439
  5. package/dist/index.html +2 -2
  6. package/dist/index.js +693 -439
  7. package/dist/schema-compiler.js +1 -1
  8. package/package.json +1 -1
  9. package/schema.sql +2 -2
  10. package/templates/astro-starter/src/components/blocks/BlockRenderer.astro +1 -0
  11. package/templates/astro-starter/src/components/blocks/ColumnLayout.astro +1 -0
  12. package/templates/astro-starter/src/components/blocks/HeaderImage.astro +1 -0
  13. package/templates/astro-starter/src/components/blocks/HtmlBlock.astro +2 -1
  14. package/templates/astro-starter/src/components/blocks/ImageBlock.astro +39 -71
  15. package/templates/astro-starter/src/components/blocks/TextBlock.astro +2 -0
  16. package/templates/astro-starter/src/components/global/Breadcrumb.astro +2 -0
  17. package/templates/astro-starter/src/components/global/Footer.astro +2 -0
  18. package/templates/astro-starter/src/components/global/Header.astro +100 -28
  19. package/templates/astro-starter/src/components/{seo/SeoHead.astro → global/MetaHead.astro} +101 -49
  20. package/templates/astro-starter/src/components/global/NavigationMenu.astro +1 -0
  21. package/templates/astro-starter/src/components/ui/KamaImage.astro +114 -67
  22. package/templates/astro-starter/src/layouts/SkeletonLayout.astro +5 -3
  23. package/templates/astro-starter/src/layouts/StandardLayout.astro +14 -5
  24. package/templates/astro-starter/src/lib/cms.ts +48 -0
  25. package/templates/astro-starter/src/lib/media/kama-image-service.ts +16 -1
  26. package/templates/astro-starter/src/lib/media/media-proxy.ts +8 -6
  27. package/templates/astro-starter/src/lib/media/media-url.ts +65 -1
  28. package/templates/astro-starter/src/middleware.ts +131 -18
  29. package/templates/astro-starter/src/pages/[...slug].astro +10 -9
  30. package/definitions/_page.yaml +0 -52
  31. package/dist/assets/index-DGHRgyms.js +0 -164
  32. package/dist/assets/index-EaDn3OUJ.css +0 -1
  33. /package/templates/astro-starter/{public/css → src/styles}/standard-theme.css +0 -0
package/bin/cli.js CHANGED
@@ -1,10 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import { execSync } from 'node:child_process';
3
3
  import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, cpSync, statSync, unlinkSync, appendFileSync } from 'node:fs';
4
- import { join, dirname, basename, resolve, relative } from 'node:path';
4
+ import { join, dirname, basename, resolve } from 'node:path';
5
5
  import { fileURLToPath, pathToFileURL } from 'node:url';
6
6
  import * as readline from 'node:readline/promises';
7
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
+
8
14
  // Dynamically resolve current package version
9
15
  const __filename = fileURLToPath(import.meta.url);
10
16
  const __dirname = dirname(__filename);
@@ -247,11 +253,13 @@ async function createFullWorkspace() {
247
253
  dependencies: {
248
254
  '@brightsideoy/kama': currentKamaVersion,
249
255
  },
250
- devDependencies: {
251
- 'wrangler': '^4.0.0'
252
- }
256
+ devDependencies: {}
253
257
  };
254
258
 
259
+ if (isCloudflare) {
260
+ rootPkg.devDependencies['wrangler'] = WRANGLER_LATEST;
261
+ }
262
+
255
263
  if (shouldCreateSite) {
256
264
  rootPkg.devDependencies['concurrently'] = '^9.0.0';
257
265
  rootPkg.scripts['dev:site'] = `npm run dev -w sites/${siteFolderName}`;
@@ -263,10 +271,7 @@ async function createFullWorkspace() {
263
271
  rootPkg.scripts['dev'] = 'npm run dev:cms';
264
272
  }
265
273
 
266
- writeFileSync(
267
- join(rootDir, 'package.json'),
268
- JSON.stringify(rootPkg, null, 2)
269
- );
274
+ writeFileSync(join(rootDir, 'package.json'), JSON.stringify(rootPkg, null, 2));
270
275
 
271
276
  console.log(`\n☁️ Configuring CMS Worker '${workerName}' (${cmsDomain || 'localhost:8787'})...`);
272
277
  const cmsDir = join(rootDir, 'kama-worker');
@@ -319,7 +324,7 @@ export default {
319
324
  '@libsql/client': '^0.14.0'
320
325
  },
321
326
  devDependencies: {
322
- "wrangler": "^4.0.0"
327
+ "wrangler": WRANGLER_LATEST
323
328
  },
324
329
  }, null, 2)
325
330
  );
@@ -330,41 +335,29 @@ export default {
330
335
  main: 'index.js',
331
336
  compatibility_date: '2024-09-23',
332
337
  compatibility_flags: ['nodejs_compat'],
333
- cache: {
334
- enabled: true
335
- },
336
- assets: {
337
- directory: '../node_modules/@brightsideoy/kama/dist',
338
- binding: 'ASSETS'
339
- },
340
- images: {
341
- binding: 'IMAGES'
342
- },
338
+ dev: { host: 'localhost', port: 8787 },
339
+ cache: { enabled: true },
340
+ assets: { directory: '../node_modules/@brightsideoy/kama/dist', binding: 'ASSETS' },
341
+ images: { binding: 'IMAGES' },
343
342
  vars: {
344
- CDN_DOMAIN: cmsDomain,
345
- PUBLIC_FRONTEND_URL: (shouldCreateSite) ? `https://${siteDomain}` : 'http://localhost:4321',
343
+ CDN_DOMAIN: cmsDomain || 'localhost:8787',
344
+ PUBLIC_FRONTEND_URL: (shouldCreateSite) ? (siteDomain && siteDomain !== 'localhost:4321' ? `https://${siteDomain}` : 'http://localhost:4321') : 'http://localhost:4321',
346
345
  CLOUDFLARE_ZONE_ID: "REPLACE_WITH_YOUR_32_CHAR_ZONE_ID",
347
346
  KAMA_RUNTIME: 'cloudflare'
348
347
  },
349
- d1_databases: [{
350
- binding: 'DB',
351
- database_name: dbName,
352
- database_id: d1DatabaseId,
353
- }],
354
- r2_buckets: [{
355
- binding: 'MEDIA_BUCKET',
356
- bucket_name: bucketName,
357
- }],
358
- routes: [{ pattern: cmsDomain, custom_domain: true }]
348
+ d1_databases: [{ binding: 'DB', database_name: dbName, database_id: d1DatabaseId }],
349
+ r2_buckets: [{ binding: 'MEDIA_BUCKET', bucket_name: bucketName }],
350
+ routes: cmsDomain ? [{ pattern: cmsDomain, custom_domain: true }] : [{ pattern: "", custom_domain: true }]
359
351
  };
360
352
 
361
353
  writeFileSync(join(cmsDir, 'wrangler.json'), JSON.stringify(cmsWranglerConfig, null, 2));
362
354
 
355
+ // 🎯 FIX: CDN_DOMAIN left empty to auto-detect http://localhost:8787
363
356
  writeFileSync(join(cmsDir, '.dev.vars'), `# Cloudflare Edge Cache Purging Secrets
364
357
  CLOUDFLARE_API_TOKEN=cfut_YOUR_CUSTOM_PURGE_TOKEN
365
358
 
366
359
  # Local Development Overrides (Takes precedence over wrangler.json vars during local dev)
367
- CDN_DOMAIN=http://localhost:8787
360
+ CDN_DOMAIN=
368
361
  PUBLIC_FRONTEND_URL=http://localhost:4321
369
362
  `);
370
363
  } else {
@@ -451,14 +444,7 @@ serve({ fetch: kamaApp.fetch, port }, (info) => {
451
444
  );
452
445
 
453
446
  // For Node mode, localhost is explicitly used
454
- writeFileSync(join(cmsDir, '.env'), `PORT=8787
455
- KAMA_RUNTIME=node
456
- DB_PATH=./kama.db
457
- LOCAL_MEDIA_PATH=./public/media
458
- PUBLIC_CMS_URL=http://localhost:8787
459
- PUBLIC_API_URL=http://localhost:8787
460
- PUBLIC_FRONTEND_URL=http://localhost:4321
461
- `);
447
+ 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`);
462
448
  }
463
449
 
464
450
  await bundleWorkspaceSchemas(rootDir);
@@ -470,18 +456,14 @@ PUBLIC_FRONTEND_URL=http://localhost:4321
470
456
  }
471
457
 
472
458
  console.log(`\n✨ Workspace ready! Next steps:`);
473
- if (!isCurrentDir) {
474
- console.log(` 1. cd ${projName}`);
475
- console.log(` 2. npm install`);
476
- } else {
477
- console.log(` 1. npm install`);
478
- }
459
+ if (!isCurrentDir) console.log(` 1. cd ${projName}`);
460
+ console.log(` 2. npm install`);
479
461
 
480
462
  if (isCloudflare) {
481
463
  if (d1DatabaseId === '00000000-0000-0000-0000-000000000000') {
482
464
  console.log(` 3. Run 'npx wrangler login' and create a D1 database manually.`);
483
465
  }
484
- console.log(` 4. Update CLOUDFLARE_ZONE_ID in kama-worker/wrangler.json with your actual Zone ID`);
466
+ console.log(` 4. Update CLOUDFLARE_ZONE_ID in kama-worker/wrangler.json`);
485
467
  console.log(` 5. Securely store your purge token: cd kama-worker && npx wrangler secret put CLOUDFLARE_API_TOKEN`);
486
468
  console.log(` 6. Run 'npm run dev' from the workspace root to start everything.`);
487
469
  } else {
@@ -492,7 +474,6 @@ PUBLIC_FRONTEND_URL=http://localhost:4321
492
474
 
493
475
  /**
494
476
  * COMMAND: npx kama add site
495
- * Add Astro Site to existing workspace
496
477
  */
497
478
  async function createNewSiteOnly() {
498
479
  const rootDir = process.cwd();
@@ -505,18 +486,13 @@ async function createNewSiteOnly() {
505
486
  let targetWorkerName = 'kama-worker';
506
487
  let detectedCmsDomain = '';
507
488
 
508
- // 🎯 Auto-detect existing worker name & CMS domain from kama-worker/wrangler.json
509
489
  const cmsWranglerPath = join(rootDir, 'kama-worker', 'wrangler.json');
510
490
  if (existsSync(cmsWranglerPath)) {
511
491
  try {
512
492
  const cmsWrangler = JSON.parse(readFileSync(cmsWranglerPath, 'utf-8'));
513
493
  if (cmsWrangler.name) targetWorkerName = cmsWrangler.name;
514
-
515
- if (cmsWrangler.vars?.CDN_DOMAIN) {
516
- detectedCmsDomain = cmsWrangler.vars.CDN_DOMAIN;
517
- } else if (cmsWrangler.routes?.[0]?.pattern) {
518
- detectedCmsDomain = cmsWrangler.routes[0].pattern;
519
- }
494
+ if (cmsWrangler.vars?.CDN_DOMAIN) detectedCmsDomain = cmsWrangler.vars.CDN_DOMAIN;
495
+ else if (cmsWrangler.routes?.[0]?.pattern) detectedCmsDomain = cmsWrangler.routes[0].pattern;
520
496
  } catch (e) { }
521
497
  }
522
498
 
@@ -527,21 +503,14 @@ async function createNewSiteOnly() {
527
503
  const isCloudflare = envChoice.trim() === '2';
528
504
 
529
505
  const siteFolder = (await rl.question('Astro package/folder name (e.g., my-new-site) [kama-starter]: ')).trim() || 'kama-starter';
530
-
531
506
  let siteDomain = 'localhost:4321';
532
507
  let cmsFullUrl = 'http://localhost:8787';
533
508
 
534
509
  if (isCloudflare) {
535
510
  siteDomain = (await rl.question('Website Domain (e.g., brand.com): ')).trim();
536
-
537
511
  if (detectedCmsDomain) {
538
- // 🎯 Automatically use the detected CMS Admin Domain
539
- cmsFullUrl = detectedCmsDomain.startsWith('http')
540
- ? detectedCmsDomain
541
- : `https://${detectedCmsDomain.replace(/^https?:\/\//, '')}`;
542
- console.log(`ℹ️ Auto-detected CMS Admin Domain: ${cmsFullUrl}`);
512
+ cmsFullUrl = detectedCmsDomain.startsWith('http') ? detectedCmsDomain : `https://${detectedCmsDomain.replace(/^https?:\/\//, '')}`;
543
513
  } else {
544
- // Fallback prompt only if no wrangler config was found
545
514
  const cmsDomainInput = (await rl.question('CMS API Domain (e.g., admin.brand.com): ')).trim();
546
515
  cmsFullUrl = `https://${cmsDomainInput.replace(/^https?:\/\//, '')}`;
547
516
  }
@@ -551,7 +520,6 @@ async function createNewSiteOnly() {
551
520
 
552
521
  console.log(`\n✨ Site created in sites/${siteFolder}!`);
553
522
  console.log(`📦 Run 'npm install' at workspace root.`);
554
- console.log(`🌱 Run 'npm run dev' to boot up and seed the new site data.`);
555
523
  }
556
524
 
557
525
  /**
@@ -559,20 +527,12 @@ async function createNewSiteOnly() {
559
527
  */
560
528
  async function bundleWorkspaceSchemas(targetRootDir) {
561
529
  let rootDir = targetRootDir || process.cwd();
562
-
563
- if (basename(rootDir) === 'kama-worker') {
564
- rootDir = resolve(rootDir, '..');
565
- }
530
+ if (basename(rootDir) === 'kama-worker') rootDir = resolve(rootDir, '..');
566
531
 
567
532
  try {
568
533
  let compilerPath = join(__dirname, '../dist/schema-compiler.js');
569
-
570
- if (!existsSync(compilerPath)) {
571
- compilerPath = join(__dirname, '../scripts/schema-compiler.ts');
572
- }
573
-
574
- const compilerUrl = pathToFileURL(compilerPath).href;
575
- await import(compilerUrl);
534
+ if (!existsSync(compilerPath)) compilerPath = join(__dirname, '../scripts/schema-compiler.ts');
535
+ await import(pathToFileURL(compilerPath).href);
576
536
  } catch (e) {
577
537
  console.error(`❌ Schema compilation failed:`, e.message || e);
578
538
  }
@@ -580,100 +540,166 @@ async function bundleWorkspaceSchemas(targetRootDir) {
580
540
 
581
541
  /**
582
542
  * COMMAND: npx kama promote
583
- * Promote current Local workspace to Cloudflare Edge
584
543
  */
585
544
  async function promoteLocalToCloudflare() {
586
545
  const rootDir = process.cwd();
587
- const workerWranglerPath = join(rootDir, 'kama-worker', 'wrangler.json');
546
+ const workerDir = join(rootDir, 'kama-worker');
547
+ const rootPkgPath = join(rootDir, 'package.json');
588
548
 
589
- if (!existsSync(workerWranglerPath)) {
590
- console.error('❌ Error: Could not find kama-worker/wrangler.json. Run this command inside your workspace root.');
549
+ if (!existsSync(workerDir) || !existsSync(rootPkgPath)) {
550
+ console.error('❌ Error: Could not find kama-worker folder or package.json. Run this command inside your workspace root.');
591
551
  return;
592
552
  }
593
553
 
594
- console.log('\n🌐 --- PROMOTING WORKSPACE TO CLOUDFLARE EDGE --- 🌐\n');
554
+ console.log('\n🌐 --- UPGRADING WORKSPACE TO CLOUDFLARE EDGE --- 🌐\n');
595
555
 
596
556
  try {
597
557
  execSync('npx wrangler whoami', { stdio: 'pipe' });
598
558
  console.log('✅ Logged in to Cloudflare.');
599
559
  } catch (e) {
600
560
  console.log('🌐 Launching browser for Cloudflare login...');
601
- try {
602
- execSync('npx wrangler login', { stdio: 'inherit' });
603
- } catch (loginError) {
604
- console.log('❌ Cloudflare login failed or was cancelled.');
605
- return;
606
- }
561
+ try { execSync('npx wrangler login', { stdio: 'inherit' }); }
562
+ catch (loginError) { console.log('❌ Cloudflare login failed.'); return; }
607
563
  }
608
564
 
609
565
  const cmsDomain = (await rl.question('\nCMS Admin Domain (e.g., admin.yourdomain.com): ')).trim();
610
566
 
611
567
  const sitesFolderDir = join(rootDir, 'sites');
612
568
  let siteDomain = '';
569
+ let siteDir = null;
613
570
  if (existsSync(sitesFolderDir)) {
614
- siteDomain = (await rl.question('Frontend Website Domain (e.g., yourdomain.com): ')).trim();
571
+ const sites = readdirSync(sitesFolderDir);
572
+ if (sites.length > 0) {
573
+ siteDir = join(sitesFolderDir, sites[0]);
574
+ siteDomain = (await rl.question('Frontend Website Domain (e.g., yourdomain.com): ')).trim();
575
+ }
615
576
  }
616
577
 
617
- const currentConfig = JSON.parse(readFileSync(workerWranglerPath, 'utf-8'));
618
- const dbName = currentConfig.d1_databases?.[0]?.database_name || 'kama_db';
619
-
620
- console.log(`\n🗄️ Provisioning remote Cloudflare D1 Database '${dbName}'...`);
578
+ const projName = basename(rootDir);
579
+ let dbName = `${projName}-db`.replace(/[^a-zA-Z0-9_-]/g, '_');
621
580
  let d1DatabaseId = '00000000-0000-0000-0000-000000000000';
622
581
 
623
582
  try {
624
583
  const d1Output = execSync(`npx wrangler d1 create ${dbName}`, { encoding: 'utf-8', stdio: 'pipe' });
625
584
  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}/);
626
585
  if (match) d1DatabaseId = match[0];
627
- } catch (err) {
628
- console.log('⚠️ Could not auto-create D1 (it may already exist). Please verify on your Cloudflare dashboard.');
629
- }
586
+ } catch (err) { }
587
+
588
+ // 1. Upgrade Root
589
+ const rootPkg = JSON.parse(readFileSync(rootPkgPath, 'utf-8'));
590
+ rootPkg.devDependencies = rootPkg.devDependencies || {};
591
+ rootPkg.devDependencies['wrangler'] = WRANGLER_LATEST;
592
+ writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, 2));
593
+
594
+ // 2. Upgrade Worker
595
+ const workerName = `${projName}-worker`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
596
+ const bucketName = `${projName}-media-prod`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
597
+ const workerWranglerPath = join(workerDir, 'wrangler.json');
598
+
599
+ let currentConfig = existsSync(workerWranglerPath)
600
+ ? JSON.parse(readFileSync(workerWranglerPath, 'utf-8'))
601
+ : {
602
+ $schema: '../node_modules/wrangler/config-schema.json',
603
+ name: workerName,
604
+ main: 'index.js',
605
+ compatibility_date: '2024-09-23',
606
+ compatibility_flags: ['nodejs_compat'],
607
+ assets: { directory: '../node_modules/@brightsideoy/kama/dist', binding: 'ASSETS' },
608
+ images: { binding: 'IMAGES' },
609
+ vars: { CLOUDFLARE_ZONE_ID: "REPLACE_WITH_YOUR_32_CHAR_ZONE_ID", KAMA_RUNTIME: 'cloudflare' }
610
+ };
630
611
 
612
+ currentConfig.dev = currentConfig.dev || { host: 'localhost', port: 8787 };
631
613
  currentConfig.cache = { enabled: true };
632
614
  currentConfig.vars.CDN_DOMAIN = cmsDomain;
633
- if (siteDomain) {
634
- currentConfig.vars.PUBLIC_FRONTEND_URL = `https://${siteDomain}`;
635
- }
615
+ if (siteDomain) currentConfig.vars.PUBLIC_FRONTEND_URL = `https://${siteDomain}`;
636
616
  currentConfig.routes = [{ pattern: cmsDomain, custom_domain: true }];
637
- if (currentConfig.d1_databases?.[0]) {
638
- currentConfig.d1_databases[0].database_id = d1DatabaseId;
639
- }
617
+ currentConfig.d1_databases = [{ binding: 'DB', database_name: dbName, database_id: d1DatabaseId }];
618
+ currentConfig.r2_buckets = [{ binding: 'MEDIA_BUCKET', bucket_name: bucketName }];
640
619
 
641
620
  writeFileSync(workerWranglerPath, JSON.stringify(currentConfig, null, 2));
642
621
 
643
- if (siteDomain && existsSync(sitesFolderDir)) {
644
- const sites = readdirSync(sitesFolderDir);
645
- if (sites.length > 0) {
646
- const siteWranglerPath = join(sitesFolderDir, sites[0], 'wrangler.json');
647
- if (existsSync(siteWranglerPath)) {
648
- const siteConfig = JSON.parse(readFileSync(siteWranglerPath, 'utf-8'));
649
-
650
- siteConfig.cache = { enabled: true };
651
- siteConfig.routes = [{
652
- pattern: siteDomain,
653
- zone_name: siteDomain,
654
- custom_domain: true,
655
- enabled: true,
656
- previews_enabled: false
657
- }];
658
-
659
- siteConfig.assets = {
660
- directory: "./dist/client",
661
- binding: "ASSETS",
662
- run_worker_first: true
663
- };
664
-
665
- siteConfig.vars = siteConfig.vars || {};
666
- siteConfig.vars.PUBLIC_CMS_URL = `https://${cmsDomain}`;
667
- siteConfig.vars.PUBLIC_API_URL = `https://${cmsDomain}`;
668
-
669
- writeFileSync(siteWranglerPath, JSON.stringify(siteConfig, null, 2));
670
- }
622
+ // 🎯 FIX: CDN_DOMAIN left empty to auto-detect http://localhost:8787
623
+ if (!existsSync(join(workerDir, '.dev.vars'))) {
624
+ writeFileSync(join(workerDir, '.dev.vars'), `CLOUDFLARE_API_TOKEN=cfut_YOUR_CUSTOM_PURGE_TOKEN\nCDN_DOMAIN=\nPUBLIC_FRONTEND_URL=http://localhost:4321\n`);
625
+ }
626
+
627
+ const workerIndexPath = join(workerDir, 'index.js');
628
+ if (!existsSync(workerIndexPath)) {
629
+ writeFileSync(workerIndexPath, `import kamaWorker from '@brightsideoy/kama';\nimport * as localBundle from './bundled-definitions.js';\nexport default {\n async fetch(request, env, ctx) {\n const schemas = localBundle.BUNDLED_DEFINITIONS || localBundle.default || localBundle;\n const customEnv = { ...env, BUNDLED_DEFINITIONS: schemas, KAMA_RUNTIME: 'cloudflare' };\n if (kamaWorker && typeof kamaWorker.fetch === 'function') return kamaWorker.fetch(request, customEnv, ctx);\n if (kamaWorker && kamaWorker.app && typeof kamaWorker.app.fetch === 'function') return kamaWorker.app.fetch(request, customEnv, ctx);\n return new Response('Kama Engine Worker Ready', { status: 200 });\n }\n};\n`);
630
+ }
631
+
632
+ const workerPkgPath = join(workerDir, 'package.json');
633
+ if (existsSync(workerPkgPath)) {
634
+ const workerPkg = JSON.parse(readFileSync(workerPkgPath, 'utf-8'));
635
+ workerPkg.scripts.dev = "npm run schema:local && npm run seed:local && wrangler dev --port 8787 --inspector-port 9239";
636
+ workerPkg.scripts.deploy = "npm run schema:remote && npm run seed:remote && wrangler deploy";
637
+ workerPkg.scripts["schema:remote"] = `wrangler d1 execute ${dbName} --remote --file=../node_modules/@brightsideoy/kama/schema.sql`;
638
+ workerPkg.scripts["seed:remote"] = `wrangler d1 execute ${dbName} --remote --file=seed.sql`;
639
+ workerPkg.devDependencies = workerPkg.devDependencies || {};
640
+ workerPkg.devDependencies.wrangler = WRANGLER_LATEST;
641
+ writeFileSync(workerPkgPath, JSON.stringify(workerPkg, null, 2));
642
+ }
643
+
644
+ // 3. Upgrade Site
645
+ if (siteDir && siteDomain) {
646
+ let sessionId = "REPLACE_WITH_YOUR_SESSION_KV_ID";
647
+ let htmlCacheId = "REPLACE_WITH_YOUR_HTML_CACHE_KV_ID";
648
+ const prefix = basename(siteDir).replace(/[^a-zA-Z0-9]/g, '_').toUpperCase();
649
+
650
+ try {
651
+ const sessionOut = execSync(`npx wrangler kv namespace create "SESSION_${prefix}"`, { encoding: 'utf-8', stdio: 'pipe' });
652
+ if (sessionOut.match(/"id":\s*"([a-f0-9]{32})"/)) sessionId = sessionOut.match(/"id":\s*"([a-f0-9]{32})"/)[1];
653
+ const cacheOut = execSync(`npx wrangler kv namespace create "HTML_CACHE_${prefix}"`, { encoding: 'utf-8', stdio: 'pipe' });
654
+ if (cacheOut.match(/"id":\s*"([a-f0-9]{32})"/)) htmlCacheId = cacheOut.match(/"id":\s*"([a-f0-9]{32})"/)[1];
655
+ } catch (err) { }
656
+
657
+ const siteWranglerPath = join(siteDir, 'wrangler.json');
658
+ const siteConfig = {
659
+ $schema: "node_modules/wrangler/config-schema.json",
660
+ name: `${basename(siteDir).toLowerCase().replace(/[^a-z0-9-]/g, '-')}-site`,
661
+ compatibility_date: "2024-09-23",
662
+ compatibility_flags: ["nodejs_compat"],
663
+ dev: { host: 'localhost', port: 4321 },
664
+ cache: { enabled: true },
665
+ assets: { directory: "./dist/client", binding: "ASSETS", run_worker_first: true },
666
+ kv_namespaces: [{ binding: "SESSION", id: sessionId }, { binding: "HTML_CACHE", id: htmlCacheId }],
667
+ services: [{ binding: "CMS_WORKER", service: currentConfig.name }],
668
+ routes: [{ pattern: siteDomain, zone_name: siteDomain, custom_domain: true, enabled: true, previews_enabled: false }],
669
+ vars: { PUBLIC_CMS_URL: `https://${cmsDomain}`, PUBLIC_API_URL: `https://${cmsDomain}` }
670
+ };
671
+ writeFileSync(siteWranglerPath, JSON.stringify(siteConfig, null, 2));
672
+
673
+ const sitePkgPath = join(siteDir, 'package.json');
674
+ if (existsSync(sitePkgPath)) {
675
+ const sitePkg = JSON.parse(readFileSync(sitePkgPath, 'utf-8'));
676
+ sitePkg.dependencies = sitePkg.dependencies || {};
677
+ sitePkg.dependencies['astro'] = ASTRO_LATEST;
678
+
679
+ if (sitePkg.dependencies['@astrojs/node']) delete sitePkg.dependencies['@astrojs/node'];
680
+ sitePkg.dependencies['@astrojs/cloudflare'] = ASTRO_CF_LATEST;
681
+
682
+ sitePkg.devDependencies = sitePkg.devDependencies || {};
683
+ sitePkg.devDependencies['wrangler'] = WRANGLER_LATEST;
684
+
685
+ writeFileSync(sitePkgPath, JSON.stringify(sitePkg, null, 2));
686
+ }
687
+
688
+ // 🎯 100% BULLETPROOF CONFIG WRITE FOR CLOUDFLARE
689
+ const astroConfigPath = join(siteDir, 'astro.config.mjs');
690
+ const cfConfigContent = `import { defineConfig } from 'astro/config';\nimport cloudflare from '@astrojs/cloudflare';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nexport default defineConfig({\n output: 'server',\n trailingSlash: 'ignore',\n image: { service: { entrypoint: './src/lib/media/kama-image-service.ts' } },\n adapter: cloudflare({ imageService: process.env.NODE_ENV === 'production' ? "cloudflare" : "passthrough" }),\n vite: {\n resolve: { alias: { '@': path.resolve(__dirname, './src') } },\n ssr: {\n external: ['cloudflare:workers', 'tinyexec', 'node:child_process', 'node:fs', 'node:url', 'node:path', 'node:stream', 'node:readline', 'node:stream/promises']\n },\n environments: {\n ssr: {\n external: ['cloudflare:workers', 'tinyexec', 'node:child_process', 'node:fs', 'node:url', 'node:path', 'node:stream', 'node:readline', 'node:stream/promises']\n }\n }\n }\n});\n`;
691
+ writeFileSync(astroConfigPath, cfConfigContent);
692
+
693
+ const tsConfigPath = join(siteDir, 'tsconfig.json');
694
+ if (existsSync(tsConfigPath)) {
695
+ const tsConfig = JSON.parse(readFileSync(tsConfigPath, 'utf-8'));
696
+ tsConfig.compilerOptions = tsConfig.compilerOptions || {};
697
+ tsConfig.compilerOptions.types = ["@cloudflare/workers-types"];
698
+ writeFileSync(tsConfigPath, JSON.stringify(tsConfig, null, 2));
671
699
  }
672
700
  }
673
701
 
674
- console.log(`\n✅ Workspace successfully configured for Cloudflare Edge!`);
675
- console.log(`\nNext steps:`);
676
- console.log(` 1. Run 'npm run deploy:cms' from the root to publish your backend and database.`);
702
+ console.log(`\n✅ Workspace successfully upgraded to Cloudflare Edge!`);
677
703
  }
678
704
 
679
705
  /**
@@ -687,19 +713,54 @@ async function generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, wo
687
713
 
688
714
  if (!existsSync(templateDir)) {
689
715
  console.error(`❌ Error: Astro template not found at ${templateDir}`);
690
- console.error(`Please ensure your Kama package installation is complete and templates are published.`);
691
716
  return;
692
717
  }
693
718
 
694
719
  cpSync(templateDir, siteDir, { recursive: true });
695
720
 
721
+ const tsConfig = {
722
+ extends: "astro/tsconfigs/strict",
723
+ compilerOptions: { baseUrl: ".", paths: { "@/*": ["src/*"] } }
724
+ };
725
+ if (isCloudflare) tsConfig.compilerOptions.types = ["@cloudflare/workers-types"];
726
+ writeFileSync(join(siteDir, 'tsconfig.json'), JSON.stringify(tsConfig, null, 2));
727
+
696
728
  const templatePkgPath = join(siteDir, 'package.json.template');
697
729
  if (existsSync(templatePkgPath)) {
698
- const pkgContent = readFileSync(templatePkgPath, 'utf-8')
730
+ const rawContent = readFileSync(templatePkgPath, 'utf-8')
699
731
  .replace(/\{\{SITE_NAME\}\}/g, siteFolder)
700
732
  .replace(/\{\{KAMA_VERSION\}\}/g, currentKamaVersion);
701
733
 
702
- writeFileSync(join(siteDir, 'package.json'), pkgContent);
734
+ const sitePkg = JSON.parse(rawContent);
735
+
736
+ sitePkg.dependencies = sitePkg.dependencies || {};
737
+ sitePkg.dependencies['astro'] = ASTRO_LATEST;
738
+
739
+ if (!isCloudflare) {
740
+ if (sitePkg.dependencies['@astrojs/cloudflare']) delete sitePkg.dependencies['@astrojs/cloudflare'];
741
+ if (sitePkg.dependencies['wrangler']) delete sitePkg.dependencies['wrangler'];
742
+ if (sitePkg.devDependencies && sitePkg.devDependencies['wrangler']) delete sitePkg.devDependencies['wrangler'];
743
+
744
+ sitePkg.dependencies['@astrojs/node'] = ASTRO_NODE_LATEST;
745
+
746
+ // 🎯 100% BULLETPROOF CONFIG WRITE FOR NODE
747
+ const astroConfigPath = join(siteDir, 'astro.config.mjs');
748
+ const nodeConfigContent = `import { defineConfig } from 'astro/config';\nimport node from '@astrojs/node';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nexport default defineConfig({\n output: 'server',\n trailingSlash: 'ignore',\n image: { service: { entrypoint: './src/lib/media/kama-image-service.ts' } },\n adapter: node({ mode: "standalone" }),\n vite: {\n resolve: { alias: { '@': path.resolve(__dirname, './src') } },\n ssr: {\n external: ['cloudflare:workers', 'tinyexec', 'node:child_process', 'node:fs', 'node:url', 'node:path', 'node:stream', 'node:readline', 'node:stream/promises']\n },\n environments: {\n ssr: {\n external: ['cloudflare:workers', 'tinyexec', 'node:child_process', 'node:fs', 'node:url', 'node:path', 'node:stream', 'node:readline', 'node:stream/promises']\n }\n }\n }\n});\n`;
749
+ writeFileSync(astroConfigPath, nodeConfigContent);
750
+
751
+ } else {
752
+ sitePkg.dependencies['@astrojs/cloudflare'] = ASTRO_CF_LATEST;
753
+ if (sitePkg.dependencies['wrangler']) delete sitePkg.dependencies['wrangler'];
754
+ sitePkg.devDependencies = sitePkg.devDependencies || {};
755
+ sitePkg.devDependencies['wrangler'] = WRANGLER_LATEST;
756
+
757
+ // 🎯 100% BULLETPROOF CONFIG WRITE FOR CLOUDFLARE
758
+ const astroConfigPath = join(siteDir, 'astro.config.mjs');
759
+ const cfConfigContent = `import { defineConfig } from 'astro/config';\nimport cloudflare from '@astrojs/cloudflare';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nexport default defineConfig({\n output: 'server',\n trailingSlash: 'ignore',\n image: { service: { entrypoint: './src/lib/media/kama-image-service.ts' } },\n adapter: cloudflare({ imageService: process.env.NODE_ENV === 'production' ? "cloudflare" : "passthrough" }),\n vite: {\n resolve: { alias: { '@': path.resolve(__dirname, './src') } },\n ssr: {\n external: ['cloudflare:workers', 'tinyexec', 'node:child_process', 'node:fs', 'node:url', 'node:path', 'node:stream', 'node:readline', 'node:stream/promises']\n },\n environments: {\n ssr: {\n external: ['cloudflare:workers', 'tinyexec', 'node:child_process', 'node:fs', 'node:url', 'node:path', 'node:stream', 'node:readline', 'node:stream/promises']\n }\n }\n }\n});\n`;
760
+ writeFileSync(astroConfigPath, cfConfigContent);
761
+ }
762
+
763
+ writeFileSync(join(siteDir, 'package.json'), JSON.stringify(sitePkg, null, 2));
703
764
  unlinkSync(templatePkgPath);
704
765
  }
705
766
 
@@ -707,114 +768,51 @@ async function generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, wo
707
768
  const safeSiteLabel = siteLabel.replace(/'/g, "''");
708
769
  const channelKey = siteFolder.replace(/[^a-z0-9_-]/gi, '-').toLowerCase();
709
770
  const treeKey = 'main-tree';
710
-
711
- // 🎯 FIX: Completely Generate wrangler.json structurally from scratch with `-site` name appended
712
771
  const safeWorkerName = `${siteFolder.toLowerCase().replace(/[^a-z0-9-]/g, '-')}-site`;
713
772
 
714
- const wranglerPath = join(siteDir, 'wrangler.json');
715
- const siteConfig = {
716
- $schema: "node_modules/wrangler/config-schema.json",
717
- name: safeWorkerName, // 🎯 Uses sanitized name (e.g. doc-kamaengine-com-site)
718
- compatibility_date: "2024-09-23",
719
- compatibility_flags: ["nodejs_compat"],
720
- cache: { enabled: true },
721
- assets: {
722
- directory: "./dist/client",
723
- binding: "ASSETS",
724
- run_worker_first: true
725
- },
726
- kv_namespaces: [
727
- {
728
- binding: "SESSION",
729
- id: "REPLACE_WITH_YOUR_SESSION_KV_ID"
730
- },
731
- {
732
- binding: "HTML_CACHE",
733
- id: "REPLACE_WITH_YOUR_HTML_CACHE_KV_ID"
734
- }
735
- ],
736
- services: [
737
- {
738
- binding: "CMS_WORKER",
739
- service: workerName // 🎯 Correctly links back to the CMS worker
740
- }
741
- ],
742
- vars: {
743
- PUBLIC_CMS_URL: cmsFullUrl,
744
- PUBLIC_API_URL: cmsFullUrl,
745
- PUBLIC_CHANNEL_ID: channelKey,
746
- PUBLIC_TREE_ID: treeKey,
747
- PUBLIC_HOME_SLUG: "home",
748
- PUBLIC_SITE_NAME: siteLabel
749
- }
750
- };
773
+ if (isCloudflare) {
774
+ let sessionId = "REPLACE_WITH_YOUR_SESSION_KV_ID";
775
+ let htmlCacheId = "REPLACE_WITH_YOUR_HTML_CACHE_KV_ID";
751
776
 
752
- if (isCloudflare && siteDomain !== 'localhost:4321') {
753
- siteConfig.routes = [{
754
- pattern: siteDomain,
755
- zone_name: siteDomain,
756
- custom_domain: true,
757
- enabled: true,
758
- previews_enabled: false
759
- }];
760
- }
777
+ try {
778
+ const prefix = siteFolder.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase();
779
+ const sessionOut = execSync(`npx wrangler kv namespace create "SESSION_${prefix}"`, { encoding: 'utf-8', stdio: 'pipe' });
780
+ if (sessionOut.match(/"id":\s*"([a-f0-9]{32})"/)) sessionId = sessionOut.match(/"id":\s*"([a-f0-9]{32})"/)[1];
781
+ const cacheOut = execSync(`npx wrangler kv namespace create "HTML_CACHE_${prefix}"`, { encoding: 'utf-8', stdio: 'pipe' });
782
+ if (cacheOut.match(/"id":\s*"([a-f0-9]{32})"/)) htmlCacheId = cacheOut.match(/"id":\s*"([a-f0-9]{32})"/)[1];
783
+ } catch (err) { }
784
+
785
+ const siteConfig = {
786
+ $schema: "node_modules/wrangler/config-schema.json",
787
+ name: safeWorkerName,
788
+ compatibility_date: "2024-09-23",
789
+ compatibility_flags: ["nodejs_compat"],
790
+ dev: { host: "localhost", port: 4321 },
791
+ cache: { enabled: true },
792
+ assets: { directory: "./dist/client", binding: "ASSETS", run_worker_first: true },
793
+ kv_namespaces: [{ binding: "SESSION", id: sessionId }, { binding: "HTML_CACHE", id: htmlCacheId }],
794
+ services: [{ binding: "CMS_WORKER", service: workerName }],
795
+ 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 }
796
+ };
761
797
 
762
- writeFileSync(wranglerPath, JSON.stringify(siteConfig, null, 2));
798
+ if (siteDomain !== 'localhost:4321') siteConfig.routes = [{ pattern: siteDomain, custom_domain: true, enabled: true, previews_enabled: false }];
799
+ writeFileSync(join(siteDir, 'wrangler.json'), JSON.stringify(siteConfig, null, 2));
800
+ }
763
801
 
764
802
  const seedSql = `
765
803
  -- Seed data for site: ${safeSiteLabel}
766
-
767
- -- 1. Create the Channel
768
- INSERT OR IGNORE INTO channels (key, label, channelType, defaultLanguage, isHidden, dataJson)
769
- VALUES ('${channelKey}', '${safeSiteLabel}', 'website', 'en', 0, '{}');
770
-
771
- -- 2. Create the Tree and link it to the Channel
772
- INSERT OR IGNORE INTO trees (key, label, treeType, channelId, isHidden, dataJson)
773
- VALUES ('${treeKey}', 'Main Tree', 'navigation', (SELECT id FROM channels WHERE key = '${channelKey}'), 0, '{}');
774
-
775
- INSERT OR IGNORE INTO channel_trees (channelId, treeId, key)
776
- VALUES ((SELECT id FROM channels WHERE key = '${channelKey}'), (SELECT id FROM trees WHERE key = '${treeKey}'), 'main');
777
-
778
- -- 3. Create the Home Page
779
- INSERT INTO content_nodes (contentType, title, slug, language, status, dataJson)
780
- SELECT 'page', 'Welcome to ${safeSiteLabel}', 'home', 'en', 'published', '{"titleSuffix":"– ${safeSiteLabel}","layoutId":"single-column","subpageLayoutId":"two-column","logoText":"${safeSiteLabel}"}'
781
- WHERE NOT EXISTS (SELECT 1 FROM content_nodes WHERE slug = 'home' AND title = 'Welcome to ${safeSiteLabel}');
782
-
783
- -- 4. Attach the Home Page to the Channel
784
- INSERT OR IGNORE INTO node_channels (nodeId, channelId)
785
- VALUES (
786
- (SELECT id FROM content_nodes WHERE slug = 'home' AND title = 'Welcome to ${safeSiteLabel}' ORDER BY id DESC LIMIT 1),
787
- (SELECT id FROM channels WHERE key = '${channelKey}')
788
- );
789
-
790
- -- 5. Attach the Home Page to the Main Tree
791
- INSERT OR IGNORE INTO tree_nodes (treeId, contentId, parentId, sortOrder)
792
- VALUES (
793
- (SELECT id FROM trees WHERE key = '${treeKey}'),
794
- (SELECT id FROM content_nodes WHERE slug = 'home' AND title = 'Welcome to ${safeSiteLabel}' ORDER BY id DESC LIMIT 1),
795
- NULL,
796
- 0
797
- );
804
+ INSERT OR IGNORE INTO channels (key, label, channelType, defaultLanguage, isHidden, dataJson) VALUES ('${channelKey}', '${safeSiteLabel}', 'website', 'en', 0, '{}');
805
+ INSERT OR IGNORE INTO trees (key, label, treeType, channelId, isHidden, dataJson) VALUES ('${treeKey}', 'Main Tree', 'navigation', (SELECT id FROM channels WHERE key = '${channelKey}'), 0, '{}');
806
+ 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');
807
+ 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}');
808
+ 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}'));
809
+ 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);
798
810
  `;
799
811
 
800
812
  const workerDir = join(rootDir, 'kama-worker');
801
- const seedPath = join(workerDir, 'seed.sql');
802
- if (existsSync(workerDir)) {
803
- appendFileSync(seedPath, seedSql);
804
- }
813
+ if (existsSync(workerDir)) appendFileSync(join(workerDir, 'seed.sql'), seedSql);
805
814
 
806
- // Local Development Overrides
807
- const envContent = [
808
- `# Local Development Overrides (Production uses wrangler.json vars)`,
809
- `PUBLIC_CMS_URL="http://localhost:8787"`,
810
- `PUBLIC_API_URL="http://localhost:8787"`,
811
- `PUBLIC_CHANNEL_ID="${channelKey}"`,
812
- `PUBLIC_TREE_ID="${treeKey}"`,
813
- `PUBLIC_HOME_SLUG="home"`,
814
- `PUBLIC_SITE_NAME="${siteLabel.replace(/"/g, '\\"')}"`
815
- ].join('\n') + '\n';
816
-
817
- writeFileSync(join(siteDir, '.env'), envContent);
815
+ 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`);
818
816
  }
819
817
 
820
818
  main().catch(console.error);