@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.
- package/bin/cli.js +172 -379
- package/content/definitions/blocks/button.yaml +7 -8
- package/content/definitions/blocks/header.yaml +7 -9
- package/content/definitions/blocks/header_image.yaml +14 -15
- package/content/definitions/blocks/hero.yaml +22 -21
- package/content/definitions/blocks/image_card.yaml +46 -47
- package/content/definitions/blocks/single_image.yaml +19 -20
- package/content/definitions/blocks/text.yaml +8 -9
- package/content/definitions/channels/ecommerce.yaml +14 -16
- package/content/definitions/channels/website.yaml +20 -21
- package/content/definitions/field-sets/access.yaml +3 -5
- package/content/definitions/field-sets/channel-base.yaml +25 -25
- package/content/definitions/field-sets/content-block.yaml +36 -41
- package/content/definitions/field-sets/page-base.yaml +40 -45
- package/content/definitions/field-sets/page-meta.yaml +1 -4
- package/content/definitions/field-sets/page-social.yaml +6 -8
- package/content/definitions/field-sets/tree-base.yaml +25 -25
- package/content/definitions/nodes/address.yaml +29 -21
- package/content/definitions/nodes/article.yaml +24 -19
- package/content/definitions/nodes/event.yaml +32 -26
- package/content/definitions/nodes/external-link.yaml +20 -17
- package/content/definitions/nodes/page.yaml +38 -35
- package/content/definitions/nodes/person.yaml +29 -27
- package/content/definitions/nodes/product.yaml +52 -49
- package/content/definitions/trees/navigation.yaml +26 -21
- package/content/definitions/trees/social-links.yaml +5 -2
- package/dist/assets/index-8T39DEP3.js +162 -0
- package/dist/assets/index-B9oh2_TL.css +1 -0
- package/dist/index.html +2 -2
- package/dist/index.js +6492 -4249
- package/package.json +5 -6
- package/schema.sql +5 -13
- package/templates/astro-starter/astro.config.mjs +40 -0
- package/templates/astro-starter/package.json.template +20 -0
- package/templates/astro-starter/src/components/blocks/BlockRenderer.astro +125 -0
- package/templates/astro-starter/src/components/blocks/ColumnLayout.astro +66 -0
- package/templates/astro-starter/src/components/blocks/HeaderImage.astro +131 -0
- package/templates/astro-starter/src/components/blocks/Hero.astro +218 -0
- package/templates/astro-starter/src/components/blocks/ImageBlock.astro +160 -0
- package/templates/astro-starter/src/components/blocks/TextBlock.astro +13 -0
- package/templates/astro-starter/src/components/global/Breadcrumb.astro +62 -0
- package/templates/astro-starter/src/components/global/Footer.astro +48 -0
- package/templates/astro-starter/src/components/global/Header.astro +92 -0
- package/templates/astro-starter/src/components/global/NavigationMenu.astro +450 -0
- package/templates/astro-starter/src/components/ui/KamaImage.astro +148 -0
- package/templates/astro-starter/src/kama-image-service.ts +41 -0
- package/templates/astro-starter/src/layouts/Layout.astro +280 -0
- package/templates/astro-starter/src/lib/cms.ts +149 -0
- package/templates/astro-starter/src/lib/media-url.ts +34 -0
- package/templates/astro-starter/src/middleware.ts +306 -0
- package/templates/astro-starter/src/pages/404.astro +46 -0
- package/templates/astro-starter/src/pages/[...slug].astro +234 -0
- package/templates/astro-starter/src/pages/media/[...path].ts +113 -0
- package/templates/astro-starter/tsconfig.json +0 -0
- package/test-local.sh +76 -0
- package/dist/assets/index-10GEw0KY.css +0 -1
- package/dist/assets/index-THJ6BqPP.js +0 -1394
- package/kama-worker/bundled-definitions.js +0 -2394
- package/kama-worker/bundled-definitions.ts +0 -2395
- 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
|
|
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() === '
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
267
|
-
"schema:local": `
|
|
268
|
-
"schema:remote": `
|
|
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:
|
|
277
|
-
tsx:
|
|
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
|
|
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
|
|
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
|
-
|
|
474
|
-
|
|
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
|
-
|
|
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
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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
|
-
|
|
633
|
-
|
|
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
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
\
|
|
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
|
-
|
|
705
|
-
|
|
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
|
-
|
|
756
|
-
const
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|