@brightsideoy/kama 0.1.0-alpha.156 → 0.1.0-alpha.158

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 (34) hide show
  1. package/bin/cli.js +222 -253
  2. package/content/definitions/nodes/page.yaml +0 -16
  3. package/dist/assets/index-B-WxBAFU.js +164 -0
  4. package/dist/assets/index-C5TmJVER.css +1 -0
  5. package/dist/{dist-C-WAK7EF.js → dist-CBGESg3a.js} +691 -475
  6. package/dist/index.html +2 -2
  7. package/dist/index.js +693 -475
  8. package/dist/schema-compiler.js +1 -1
  9. package/package.json +1 -1
  10. package/schema.sql +2 -2
  11. package/templates/astro-starter/src/components/blocks/BlockRenderer.astro +1 -0
  12. package/templates/astro-starter/src/components/blocks/ColumnLayout.astro +1 -0
  13. package/templates/astro-starter/src/components/blocks/HeaderImage.astro +1 -0
  14. package/templates/astro-starter/src/components/blocks/HtmlBlock.astro +2 -1
  15. package/templates/astro-starter/src/components/blocks/ImageBlock.astro +39 -71
  16. package/templates/astro-starter/src/components/blocks/TextBlock.astro +2 -0
  17. package/templates/astro-starter/src/components/global/Breadcrumb.astro +2 -0
  18. package/templates/astro-starter/src/components/global/Footer.astro +2 -0
  19. package/templates/astro-starter/src/components/global/Header.astro +99 -33
  20. package/templates/astro-starter/src/components/{seo/SeoHead.astro → global/MetaHead.astro} +101 -49
  21. package/templates/astro-starter/src/components/global/NavigationMenu.astro +1 -0
  22. package/templates/astro-starter/src/components/ui/KamaImage.astro +114 -67
  23. package/templates/astro-starter/src/layouts/SkeletonLayout.astro +5 -3
  24. package/templates/astro-starter/src/layouts/StandardLayout.astro +14 -5
  25. package/templates/astro-starter/src/lib/cms.ts +48 -0
  26. package/templates/astro-starter/src/lib/media/kama-image-service.ts +16 -1
  27. package/templates/astro-starter/src/lib/media/media-proxy.ts +8 -6
  28. package/templates/astro-starter/src/lib/media/media-url.ts +65 -1
  29. package/templates/astro-starter/src/middleware.ts +86 -17
  30. package/templates/astro-starter/src/pages/[...slug].astro +10 -22
  31. package/definitions/_page.yaml +0 -52
  32. package/dist/assets/index-DnRQYNN4.js +0 -164
  33. package/dist/assets/index-EaDn3OUJ.css +0 -1
  34. /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,18 +503,13 @@ 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
- cmsFullUrl = detectedCmsDomain.startsWith('http')
539
- ? detectedCmsDomain
540
- : `https://${detectedCmsDomain.replace(/^https?:\/\//, '')}`;
541
- console.log(`ℹ️ Auto-detected CMS Admin Domain: ${cmsFullUrl}`);
512
+ cmsFullUrl = detectedCmsDomain.startsWith('http') ? detectedCmsDomain : `https://${detectedCmsDomain.replace(/^https?:\/\//, '')}`;
542
513
  } else {
543
514
  const cmsDomainInput = (await rl.question('CMS API Domain (e.g., admin.brand.com): ')).trim();
544
515
  cmsFullUrl = `https://${cmsDomainInput.replace(/^https?:\/\//, '')}`;
@@ -549,7 +520,6 @@ async function createNewSiteOnly() {
549
520
 
550
521
  console.log(`\n✨ Site created in sites/${siteFolder}!`);
551
522
  console.log(`📦 Run 'npm install' at workspace root.`);
552
- console.log(`🌱 Run 'npm run dev' to boot up and seed the new site data.`);
553
523
  }
554
524
 
555
525
  /**
@@ -557,20 +527,12 @@ async function createNewSiteOnly() {
557
527
  */
558
528
  async function bundleWorkspaceSchemas(targetRootDir) {
559
529
  let rootDir = targetRootDir || process.cwd();
560
-
561
- if (basename(rootDir) === 'kama-worker') {
562
- rootDir = resolve(rootDir, '..');
563
- }
530
+ if (basename(rootDir) === 'kama-worker') rootDir = resolve(rootDir, '..');
564
531
 
565
532
  try {
566
533
  let compilerPath = join(__dirname, '../dist/schema-compiler.js');
567
-
568
- if (!existsSync(compilerPath)) {
569
- compilerPath = join(__dirname, '../scripts/schema-compiler.ts');
570
- }
571
-
572
- const compilerUrl = pathToFileURL(compilerPath).href;
573
- await import(compilerUrl);
534
+ if (!existsSync(compilerPath)) compilerPath = join(__dirname, '../scripts/schema-compiler.ts');
535
+ await import(pathToFileURL(compilerPath).href);
574
536
  } catch (e) {
575
537
  console.error(`❌ Schema compilation failed:`, e.message || e);
576
538
  }
@@ -581,94 +543,163 @@ async function bundleWorkspaceSchemas(targetRootDir) {
581
543
  */
582
544
  async function promoteLocalToCloudflare() {
583
545
  const rootDir = process.cwd();
584
- const workerWranglerPath = join(rootDir, 'kama-worker', 'wrangler.json');
546
+ const workerDir = join(rootDir, 'kama-worker');
547
+ const rootPkgPath = join(rootDir, 'package.json');
585
548
 
586
- if (!existsSync(workerWranglerPath)) {
587
- 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.');
588
551
  return;
589
552
  }
590
553
 
591
- console.log('\n🌐 --- PROMOTING WORKSPACE TO CLOUDFLARE EDGE --- 🌐\n');
554
+ console.log('\n🌐 --- UPGRADING WORKSPACE TO CLOUDFLARE EDGE --- 🌐\n');
592
555
 
593
556
  try {
594
557
  execSync('npx wrangler whoami', { stdio: 'pipe' });
595
558
  console.log('✅ Logged in to Cloudflare.');
596
559
  } catch (e) {
597
560
  console.log('🌐 Launching browser for Cloudflare login...');
598
- try {
599
- execSync('npx wrangler login', { stdio: 'inherit' });
600
- } catch (loginError) {
601
- console.log('❌ Cloudflare login failed or was cancelled.');
602
- return;
603
- }
561
+ try { execSync('npx wrangler login', { stdio: 'inherit' }); }
562
+ catch (loginError) { console.log('❌ Cloudflare login failed.'); return; }
604
563
  }
605
564
 
606
565
  const cmsDomain = (await rl.question('\nCMS Admin Domain (e.g., admin.yourdomain.com): ')).trim();
607
566
 
608
567
  const sitesFolderDir = join(rootDir, 'sites');
609
568
  let siteDomain = '';
569
+ let siteDir = null;
610
570
  if (existsSync(sitesFolderDir)) {
611
- 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
+ }
612
576
  }
613
577
 
614
- const currentConfig = JSON.parse(readFileSync(workerWranglerPath, 'utf-8'));
615
- const dbName = currentConfig.d1_databases?.[0]?.database_name || 'kama_db';
616
-
617
- 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, '_');
618
580
  let d1DatabaseId = '00000000-0000-0000-0000-000000000000';
619
581
 
620
582
  try {
621
583
  const d1Output = execSync(`npx wrangler d1 create ${dbName}`, { encoding: 'utf-8', stdio: 'pipe' });
622
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}/);
623
585
  if (match) d1DatabaseId = match[0];
624
- } catch (err) {
625
- console.log('⚠️ Could not auto-create D1 (it may already exist). Please verify on your Cloudflare dashboard.');
626
- }
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
+ };
627
611
 
612
+ currentConfig.dev = currentConfig.dev || { host: 'localhost', port: 8787 };
628
613
  currentConfig.cache = { enabled: true };
629
614
  currentConfig.vars.CDN_DOMAIN = cmsDomain;
630
- if (siteDomain) {
631
- currentConfig.vars.PUBLIC_FRONTEND_URL = `https://${siteDomain}`;
632
- }
615
+ if (siteDomain) currentConfig.vars.PUBLIC_FRONTEND_URL = `https://${siteDomain}`;
633
616
  currentConfig.routes = [{ pattern: cmsDomain, custom_domain: true }];
634
- if (currentConfig.d1_databases?.[0]) {
635
- currentConfig.d1_databases[0].database_id = d1DatabaseId;
636
- }
617
+ currentConfig.d1_databases = [{ binding: 'DB', database_name: dbName, database_id: d1DatabaseId }];
618
+ currentConfig.r2_buckets = [{ binding: 'MEDIA_BUCKET', bucket_name: bucketName }];
637
619
 
638
620
  writeFileSync(workerWranglerPath, JSON.stringify(currentConfig, null, 2));
639
621
 
640
- if (siteDomain && existsSync(sitesFolderDir)) {
641
- const sites = readdirSync(sitesFolderDir);
642
- if (sites.length > 0) {
643
- const siteWranglerPath = join(sitesFolderDir, sites[0], 'wrangler.json');
644
- if (existsSync(siteWranglerPath)) {
645
- const siteConfig = JSON.parse(readFileSync(siteWranglerPath, 'utf-8'));
646
-
647
- siteConfig.cache = { enabled: true };
648
- siteConfig.routes = [{
649
- pattern: siteDomain,
650
- zone_name: siteDomain,
651
- custom_domain: true,
652
- enabled: true,
653
- previews_enabled: false
654
- }];
655
-
656
- siteConfig.assets = {
657
- directory: "./dist/client",
658
- binding: "ASSETS",
659
- run_worker_first: true
660
- };
661
-
662
- siteConfig.vars = siteConfig.vars || {};
663
- siteConfig.vars.PUBLIC_CMS_URL = `https://${cmsDomain}`;
664
- siteConfig.vars.PUBLIC_API_URL = `https://${cmsDomain}`;
665
-
666
- writeFileSync(siteWranglerPath, JSON.stringify(siteConfig, null, 2));
667
- }
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));
668
699
  }
669
700
  }
670
701
 
671
- console.log(`\n✅ Workspace successfully configured for Cloudflare Edge!`);
702
+ console.log(`\n✅ Workspace successfully upgraded to Cloudflare Edge!`);
672
703
  }
673
704
 
674
705
  /**
@@ -687,23 +718,49 @@ async function generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, wo
687
718
 
688
719
  cpSync(templateDir, siteDir, { recursive: true });
689
720
 
690
- // 🎯 1. Auto-generate tsconfig.json explicitly to prevent deployment crashes
691
- writeFileSync(join(siteDir, 'tsconfig.json'), JSON.stringify({
721
+ const tsConfig = {
692
722
  extends: "astro/tsconfigs/strict",
693
- compilerOptions: {
694
- baseUrl: ".",
695
- paths: { "@/*": ["src/*"] },
696
- types: ["@cloudflare/workers-types"]
697
- }
698
- }, null, 2));
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));
699
727
 
700
728
  const templatePkgPath = join(siteDir, 'package.json.template');
701
729
  if (existsSync(templatePkgPath)) {
702
- const pkgContent = readFileSync(templatePkgPath, 'utf-8')
730
+ const rawContent = readFileSync(templatePkgPath, 'utf-8')
703
731
  .replace(/\{\{SITE_NAME\}\}/g, siteFolder)
704
732
  .replace(/\{\{KAMA_VERSION\}\}/g, currentKamaVersion);
705
733
 
706
- 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));
707
764
  unlinkSync(templatePkgPath);
708
765
  }
709
766
 
@@ -711,139 +768,51 @@ async function generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, wo
711
768
  const safeSiteLabel = siteLabel.replace(/'/g, "''");
712
769
  const channelKey = siteFolder.replace(/[^a-z0-9_-]/gi, '-').toLowerCase();
713
770
  const treeKey = 'main-tree';
714
-
715
- // Sanitize worker name
716
771
  const safeWorkerName = `${siteFolder.toLowerCase().replace(/[^a-z0-9-]/g, '-')}-site`;
717
772
 
718
- // 🎯 2. Auto-provision KV Namespaces if running in Cloudflare mode
719
- let sessionId = "REPLACE_WITH_YOUR_SESSION_KV_ID";
720
- let htmlCacheId = "REPLACE_WITH_YOUR_HTML_CACHE_KV_ID";
721
-
722
773
  if (isCloudflare) {
723
- console.log(`\n🗄️ Provisioning isolated KV Namespaces for ${siteFolder}...`);
774
+ let sessionId = "REPLACE_WITH_YOUR_SESSION_KV_ID";
775
+ let htmlCacheId = "REPLACE_WITH_YOUR_HTML_CACHE_KV_ID";
776
+
724
777
  try {
725
778
  const prefix = siteFolder.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase();
726
-
727
- console.log(` Creating Session KV...`);
728
779
  const sessionOut = execSync(`npx wrangler kv namespace create "SESSION_${prefix}"`, { encoding: 'utf-8', stdio: 'pipe' });
729
- const sessionMatch = sessionOut.match(/"id":\s*"([a-f0-9]{32})"/);
730
- if (sessionMatch) sessionId = sessionMatch[1];
731
-
732
- console.log(` Creating Cache KV...`);
780
+ if (sessionOut.match(/"id":\s*"([a-f0-9]{32})"/)) sessionId = sessionOut.match(/"id":\s*"([a-f0-9]{32})"/)[1];
733
781
  const cacheOut = execSync(`npx wrangler kv namespace create "HTML_CACHE_${prefix}"`, { encoding: 'utf-8', stdio: 'pipe' });
734
- const cacheMatch = cacheOut.match(/"id":\s*"([a-f0-9]{32})"/);
735
- if (cacheMatch) htmlCacheId = cacheMatch[1];
736
-
737
- console.log(`✅ KV Namespaces created and bound automatically!`);
738
- } catch (err) {
739
- console.warn(`⚠️ Failed to auto-create KV namespaces automatically (You may need to do it manually).`);
740
- }
741
- }
742
-
743
- // 🎯 3. Generate wrangler.json completely
744
- const wranglerPath = join(siteDir, 'wrangler.json');
745
- const siteConfig = {
746
- $schema: "node_modules/wrangler/config-schema.json",
747
- name: safeWorkerName,
748
- compatibility_date: "2024-09-23",
749
- compatibility_flags: ["nodejs_compat"],
750
- cache: { enabled: true },
751
- assets: {
752
- directory: "./dist/client",
753
- binding: "ASSETS",
754
- run_worker_first: true
755
- },
756
- kv_namespaces: [
757
- {
758
- binding: "SESSION",
759
- id: sessionId
760
- },
761
- {
762
- binding: "HTML_CACHE",
763
- id: htmlCacheId
764
- }
765
- ],
766
- services: [
767
- {
768
- binding: "CMS_WORKER",
769
- service: workerName
770
- }
771
- ],
772
- vars: {
773
- PUBLIC_CMS_URL: cmsFullUrl,
774
- PUBLIC_API_URL: cmsFullUrl,
775
- PUBLIC_CHANNEL_ID: channelKey,
776
- PUBLIC_TREE_ID: treeKey,
777
- PUBLIC_HOME_SLUG: "home",
778
- PUBLIC_SITE_NAME: siteLabel
779
- }
780
- };
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
+ };
781
797
 
782
- if (isCloudflare && siteDomain !== 'localhost:4321') {
783
- siteConfig.routes = [{
784
- pattern: siteDomain,
785
- custom_domain: true,
786
- enabled: true,
787
- previews_enabled: false
788
- }];
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));
789
800
  }
790
801
 
791
- writeFileSync(wranglerPath, JSON.stringify(siteConfig, null, 2));
792
-
793
802
  const seedSql = `
794
803
  -- Seed data for site: ${safeSiteLabel}
795
-
796
- -- 1. Create the Channel
797
- INSERT OR IGNORE INTO channels (key, label, channelType, defaultLanguage, isHidden, dataJson)
798
- VALUES ('${channelKey}', '${safeSiteLabel}', 'website', 'en', 0, '{}');
799
-
800
- -- 2. Create the Tree and link it to the Channel
801
- INSERT OR IGNORE INTO trees (key, label, treeType, channelId, isHidden, dataJson)
802
- VALUES ('${treeKey}', 'Main Tree', 'navigation', (SELECT id FROM channels WHERE key = '${channelKey}'), 0, '{}');
803
-
804
- INSERT OR IGNORE INTO channel_trees (channelId, treeId, key)
805
- VALUES ((SELECT id FROM channels WHERE key = '${channelKey}'), (SELECT id FROM trees WHERE key = '${treeKey}'), 'main');
806
-
807
- -- 3. Create the Home Page
808
- INSERT INTO content_nodes (contentType, title, slug, language, status, dataJson)
809
- SELECT 'page', 'Welcome to ${safeSiteLabel}', 'home', 'en', 'published', '{"titleSuffix":"– ${safeSiteLabel}","layoutId":"single-column","subpageLayoutId":"two-column","logoText":"${safeSiteLabel}"}'
810
- WHERE NOT EXISTS (SELECT 1 FROM content_nodes WHERE slug = 'home' AND title = 'Welcome to ${safeSiteLabel}');
811
-
812
- -- 4. Attach the Home Page to the Channel
813
- INSERT OR IGNORE INTO node_channels (nodeId, channelId)
814
- VALUES (
815
- (SELECT id FROM content_nodes WHERE slug = 'home' AND title = 'Welcome to ${safeSiteLabel}' ORDER BY id DESC LIMIT 1),
816
- (SELECT id FROM channels WHERE key = '${channelKey}')
817
- );
818
-
819
- -- 5. Attach the Home Page to the Main Tree
820
- INSERT OR IGNORE INTO tree_nodes (treeId, contentId, parentId, sortOrder)
821
- VALUES (
822
- (SELECT id FROM trees WHERE key = '${treeKey}'),
823
- (SELECT id FROM content_nodes WHERE slug = 'home' AND title = 'Welcome to ${safeSiteLabel}' ORDER BY id DESC LIMIT 1),
824
- NULL,
825
- 0
826
- );
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);
827
810
  `;
828
811
 
829
812
  const workerDir = join(rootDir, 'kama-worker');
830
- const seedPath = join(workerDir, 'seed.sql');
831
- if (existsSync(workerDir)) {
832
- appendFileSync(seedPath, seedSql);
833
- }
813
+ if (existsSync(workerDir)) appendFileSync(join(workerDir, 'seed.sql'), seedSql);
834
814
 
835
- // Local Development Overrides
836
- const envContent = [
837
- `# Local Development Overrides (Production uses wrangler.json vars)`,
838
- `PUBLIC_CMS_URL="http://localhost:8787"`,
839
- `PUBLIC_API_URL="http://localhost:8787"`,
840
- `PUBLIC_CHANNEL_ID="${channelKey}"`,
841
- `PUBLIC_TREE_ID="${treeKey}"`,
842
- `PUBLIC_HOME_SLUG="home"`,
843
- `PUBLIC_SITE_NAME="${siteLabel.replace(/"/g, '\\"')}"`
844
- ].join('\n') + '\n';
845
-
846
- 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`);
847
816
  }
848
817
 
849
818
  main().catch(console.error);