@brightsideoy/kama 0.1.0-alpha.12 ā 0.1.0-alpha.121
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +195 -0
- package/bin/cli.js +602 -0
- package/content/definitions/block-layouts/three-column.yaml +20 -0
- package/content/definitions/block-layouts/two-column.yaml +29 -0
- package/content/definitions/blocks/button.yaml +24 -0
- package/content/definitions/blocks/header.yaml +13 -0
- package/content/definitions/blocks/header_image.yaml +21 -0
- package/content/definitions/blocks/hero.yaml +28 -0
- package/content/definitions/blocks/html.yaml +12 -0
- package/content/definitions/blocks/image_card.yaml +56 -0
- package/content/definitions/blocks/single_image.yaml +27 -0
- package/content/definitions/blocks/text.yaml +15 -0
- package/content/definitions/channels/ecommerce.yaml +28 -0
- package/content/definitions/channels/website.yaml +17 -0
- package/content/definitions/field-sets/access.yaml +27 -0
- package/content/definitions/field-sets/channel-base.yaml +50 -0
- package/content/definitions/field-sets/channel-seo.yaml +21 -0
- package/content/definitions/field-sets/comments.yaml +11 -0
- package/content/definitions/field-sets/content-block.yaml +41 -0
- package/content/definitions/field-sets/page-base.yaml +44 -0
- package/content/definitions/field-sets/page-seo.yaml +34 -0
- package/content/definitions/field-sets/page-social.yaml +78 -0
- package/content/definitions/field-sets/schema-type.yaml +43 -0
- package/content/definitions/field-sets/tree-base.yaml +31 -0
- package/content/definitions/field-sets/visibility.yaml +8 -0
- package/content/definitions/node-layouts/content.yml +12 -0
- package/content/definitions/node-layouts/landing-page.yml +25 -0
- package/content/definitions/node-layouts/single-column.yml +44 -0
- package/content/definitions/node-layouts/two-column.yml +49 -0
- package/content/definitions/nodes/address.yaml +40 -0
- package/content/definitions/nodes/article.yaml +48 -0
- package/content/definitions/nodes/event.yaml +55 -0
- package/content/definitions/nodes/external-link.yaml +29 -0
- package/content/definitions/nodes/page-skeleton.yaml +48 -0
- package/content/definitions/nodes/page.yaml +57 -0
- package/content/definitions/nodes/person.yaml +41 -0
- package/content/definitions/nodes/product.yaml +89 -0
- package/content/definitions/trees/navigation.yaml +24 -0
- package/definitions/editor/button.ts +11 -0
- package/definitions/editor/default.ts +21 -0
- package/definitions/editor/header.ts +36 -0
- package/definitions/editor/minimal.ts +15 -0
- package/definitions/validators/iban.ts +12 -0
- package/definitions/validators/magic-word.ts +7 -0
- package/dist/assets/index-C6GoXL-R.js +162 -0
- package/dist/assets/index-Drmp3h6P.css +1 -0
- package/dist/index.html +13 -0
- package/dist/index.js +12209 -2045
- package/documentation/SCHEMAS.md +182 -0
- package/package.json +14 -11
- package/schema.sql +204 -0
- package/templates/astro-starter/astro.config.mjs +40 -0
- package/templates/astro-starter/middleware/api-purge.ts +130 -0
- package/templates/astro-starter/middleware/index.ts +10 -0
- package/templates/astro-starter/middleware/kv-cache.ts +76 -0
- package/templates/astro-starter/middleware/response-headers.ts +43 -0
- package/templates/astro-starter/package.json.template +20 -0
- package/templates/astro-starter/public/css/standard-theme.css +157 -0
- package/templates/astro-starter/public/robots.txt +23 -0
- package/templates/astro-starter/src/components/blocks/BlockRenderer.astro +133 -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/HtmlBlock.astro +24 -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/seo/SeoHead.astro +150 -0
- package/templates/astro-starter/src/components/ui/KamaImage.astro +149 -0
- package/templates/astro-starter/src/layouts/SkeletonLayout.astro +45 -0
- package/templates/astro-starter/src/layouts/StandardLayout.astro +52 -0
- package/templates/astro-starter/src/lib/cms.ts +149 -0
- package/templates/astro-starter/src/lib/media/kama-image-service.ts +41 -0
- package/templates/astro-starter/src/lib/media/media-proxy.ts +127 -0
- package/templates/astro-starter/src/lib/media/media-url.ts +34 -0
- package/templates/astro-starter/src/pages/404.astro +46 -0
- package/templates/astro-starter/src/pages/[...slug].astro +201 -0
- package/templates/astro-starter/src/pages/llms-full.txt.ts +161 -0
- package/templates/astro-starter/src/pages/llms.txt.ts +107 -0
- package/templates/astro-starter/src/pages/media/[...path].ts +7 -0
- package/templates/astro-starter/src/utils/tiptap.ts +113 -0
- package/templates/astro-starter/tsconfig.json +0 -0
- package/test-local.sh +76 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, cpSync, unlinkSync, statSync } from 'node:fs';
|
|
4
|
+
import { join, dirname, basename, relative, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import * as readline from 'node:readline/promises';
|
|
7
|
+
|
|
8
|
+
// Dynamically resolve current package version
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
let currentKamaVersion = 'alpha';
|
|
12
|
+
try {
|
|
13
|
+
const pkgPath = join(__dirname, '../package.json');
|
|
14
|
+
if (existsSync(pkgPath)) {
|
|
15
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
16
|
+
if (pkg.version) currentKamaVersion = `^${pkg.version}`;
|
|
17
|
+
}
|
|
18
|
+
} catch (e) {
|
|
19
|
+
console.warn("ā ļø Could not detect Kama version, defaulting to 'alpha'");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const rl = readline.createInterface({
|
|
23
|
+
input: process.stdin,
|
|
24
|
+
output: process.stdout,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
async function main() {
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
const command = args[0]?.toLowerCase();
|
|
30
|
+
const subCommand = args[1]?.toLowerCase();
|
|
31
|
+
|
|
32
|
+
console.log('\nš„ --- KAMA ENGINE CLI --- š„\n');
|
|
33
|
+
|
|
34
|
+
if (command === 'init') {
|
|
35
|
+
await createFullWorkspace();
|
|
36
|
+
} else if (command === 'add' && subCommand === 'site') {
|
|
37
|
+
await createNewSiteOnly();
|
|
38
|
+
} else if (command === 'promote') {
|
|
39
|
+
await promoteLocalToCloudflare();
|
|
40
|
+
} else if (command === 'bundle') {
|
|
41
|
+
await bundleWorkspaceSchemas();
|
|
42
|
+
} else {
|
|
43
|
+
console.log('Available Commands:');
|
|
44
|
+
console.log(' 1) npx kama init - Kickstart new Monorepo (CMS + Optional Site)');
|
|
45
|
+
console.log(' 2) npx kama add site - Add new Astro Site to current workspace (sites/)');
|
|
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');
|
|
48
|
+
|
|
49
|
+
const choice = await rl.question('\nSelect choice [1/2/3/4]: ');
|
|
50
|
+
|
|
51
|
+
if (choice.trim() === '4') {
|
|
52
|
+
await promoteLocalToCloudflare();
|
|
53
|
+
} else if (choice.trim() === '3') {
|
|
54
|
+
await bundleWorkspaceSchemas();
|
|
55
|
+
} else if (choice.trim() === '2') {
|
|
56
|
+
await createNewSiteOnly();
|
|
57
|
+
} else {
|
|
58
|
+
await createFullWorkspace();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
rl.close();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* COMMAND: npx kama init
|
|
67
|
+
* Kickstart full Monorepo Workspace
|
|
68
|
+
*/
|
|
69
|
+
async function createFullWorkspace() {
|
|
70
|
+
const cwdName = basename(process.cwd());
|
|
71
|
+
const hasExistingPkg = existsSync(join(process.cwd(), 'package.json'));
|
|
72
|
+
|
|
73
|
+
const defaultDirPrompt = hasExistingPkg ? '.' : cwdName;
|
|
74
|
+
const inputName = await rl.question(`Project folder name (use '.' for current dir) [${defaultDirPrompt}]: `);
|
|
75
|
+
|
|
76
|
+
const chosenFolder = inputName.trim() || defaultDirPrompt;
|
|
77
|
+
const isCurrentDir = chosenFolder === '.';
|
|
78
|
+
|
|
79
|
+
const projName = isCurrentDir ? cwdName : chosenFolder;
|
|
80
|
+
const rootDir = isCurrentDir ? process.cwd() : join(process.cwd(), projName);
|
|
81
|
+
|
|
82
|
+
console.log('\nš Where do you plan to deploy KAMA?');
|
|
83
|
+
console.log(' 1) Local Development Only (SQLite / Testing)');
|
|
84
|
+
console.log(' 2) Cloudflare Edge (Workers / D1 / R2)');
|
|
85
|
+
const envChoice = await rl.question('\nChoice [1/2]: ');
|
|
86
|
+
const isCloudflare = envChoice.trim() === '2';
|
|
87
|
+
|
|
88
|
+
const createSiteInput = await rl.question('\nš Do you want to scaffold an initial Astro frontend site? (y/n) [y]: ');
|
|
89
|
+
const shouldCreateSite = createSiteInput.trim().toLowerCase() !== 'n';
|
|
90
|
+
|
|
91
|
+
let cmsDomain = 'localhost:8787';
|
|
92
|
+
let siteDomain = 'localhost:4321';
|
|
93
|
+
|
|
94
|
+
if (isCloudflare) {
|
|
95
|
+
cmsDomain = (await rl.question('\nCMS Admin Domain (e.g., admin.t3cloud.eu): ')) || 'admin.t3cloud.eu';
|
|
96
|
+
if (shouldCreateSite) {
|
|
97
|
+
siteDomain = (await rl.question('First Website Domain (e.g., kama.t3cloud.eu): ')) || 'kama.t3cloud.eu';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const cmsProtocol = isCloudflare ? 'https://' : 'http://';
|
|
102
|
+
const cmsFullUrl = `${cmsProtocol}${cmsDomain.replace(/^https?:\/\//, '')}`;
|
|
103
|
+
|
|
104
|
+
const workerName = `${projName}-worker`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
105
|
+
let dbName = `${projName}-db`.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
106
|
+
const bucketName = `${projName}-media-prod`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
107
|
+
|
|
108
|
+
const siteFolderName = isCloudflare ? siteDomain.split('.')[0] : 'web';
|
|
109
|
+
|
|
110
|
+
if (!isCurrentDir) {
|
|
111
|
+
console.log(`\nš Creating workspace folder at ./${projName}...`);
|
|
112
|
+
mkdirSync(rootDir, { recursive: true });
|
|
113
|
+
} else {
|
|
114
|
+
console.log(`\nš Initializing workspace in current directory (${rootDir})...`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
writeFileSync(
|
|
118
|
+
join(rootDir, '.gitignore'),
|
|
119
|
+
`node_modules/
|
|
120
|
+
.env
|
|
121
|
+
.DS_Store
|
|
122
|
+
kama-worker/.dev.vars
|
|
123
|
+
kama-worker/.wrangler/
|
|
124
|
+
sites/*/.env
|
|
125
|
+
sites/*/.astro/
|
|
126
|
+
sites/*/dist/
|
|
127
|
+
kama-worker/workspace-schemas.js
|
|
128
|
+
`
|
|
129
|
+
);
|
|
130
|
+
|
|
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
|
+
}
|
|
139
|
+
|
|
140
|
+
let d1DatabaseId = '00000000-0000-0000-0000-000000000000';
|
|
141
|
+
let isAuthenticated = false;
|
|
142
|
+
|
|
143
|
+
if (isCloudflare) {
|
|
144
|
+
console.log(`\nš Checking Cloudflare authentication...`);
|
|
145
|
+
try {
|
|
146
|
+
execSync('npx wrangler whoami', { stdio: 'pipe' });
|
|
147
|
+
isAuthenticated = true;
|
|
148
|
+
console.log(`ā
Logged in to Cloudflare.`);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.log(`ā ļø You are not logged in to Cloudflare.`);
|
|
151
|
+
console.log(`š Launching browser for Cloudflare login...`);
|
|
152
|
+
try {
|
|
153
|
+
execSync('npx wrangler login', { stdio: 'inherit' });
|
|
154
|
+
isAuthenticated = true;
|
|
155
|
+
console.log(`ā
Successfully logged in!`);
|
|
156
|
+
} catch (loginError) {
|
|
157
|
+
console.log(`ā Login failed or was cancelled.`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (isAuthenticated) {
|
|
162
|
+
try {
|
|
163
|
+
console.log(`\nšļø Checking existing Cloudflare D1 databases...`);
|
|
164
|
+
const listOutput = execSync(`npx wrangler d1 list --json`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
165
|
+
const jsonStart = listOutput.indexOf('[');
|
|
166
|
+
const jsonEnd = listOutput.lastIndexOf(']') + 1;
|
|
167
|
+
let databases = [];
|
|
168
|
+
|
|
169
|
+
if (jsonStart !== -1 && jsonEnd !== -1) {
|
|
170
|
+
databases = JSON.parse(listOutput.substring(jsonStart, jsonEnd));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const existingDb = databases.find(db => db.name === dbName);
|
|
174
|
+
|
|
175
|
+
if (existingDb) {
|
|
176
|
+
console.log(`ā ļø Database '${dbName}' already exists on your account.`);
|
|
177
|
+
const useExisting = await rl.question(`Do you want to link this existing database? (y/n) [y]: `);
|
|
178
|
+
|
|
179
|
+
if (useExisting.toLowerCase() !== 'n') {
|
|
180
|
+
d1DatabaseId = existingDb.uuid;
|
|
181
|
+
console.log(`ā
Linked existing database ID: ${d1DatabaseId}`);
|
|
182
|
+
} else {
|
|
183
|
+
dbName = (await rl.question(`Enter a new database name: `)).trim();
|
|
184
|
+
console.log(`\nšļø Creating new database '${dbName}'...`);
|
|
185
|
+
const d1Output = execSync(`npx wrangler d1 create ${dbName}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
186
|
+
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}/);
|
|
187
|
+
if (match) {
|
|
188
|
+
d1DatabaseId = match[0];
|
|
189
|
+
console.log(`ā
Success! Database ID bound: ${d1DatabaseId}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
console.log(`\nšļø Attempting to auto-create Cloudflare D1 Database '${dbName}'...`);
|
|
194
|
+
const d1Output = execSync(`npx wrangler d1 create ${dbName}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
195
|
+
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}/);
|
|
196
|
+
if (match) {
|
|
197
|
+
d1DatabaseId = match[0];
|
|
198
|
+
console.log(`ā
Success! Database ID bound: ${d1DatabaseId}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.log(`ā ļø Database operation failed. We'll leave a placeholder.`);
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
console.log(`ā ļø Skipping database creation. We'll leave a placeholder in wrangler.json.`);
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
console.log(`\nš» Local mode selected. Skipping Cloudflare authentication and domain setup...`);
|
|
209
|
+
}
|
|
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'
|
|
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
|
+
rootPkg.scripts['predev'] = bundleScript;
|
|
237
|
+
rootPkg.scripts['dev'] = 'npx concurrently "npm run dev:cms" "npm run dev:site"';
|
|
238
|
+
rootPkg.scripts['deploy:site'] = `npm run deploy -w sites/${siteFolderName}`;
|
|
239
|
+
} else {
|
|
240
|
+
rootPkg.scripts['predev'] = bundleScript;
|
|
241
|
+
rootPkg.scripts['dev'] = 'npm run dev:cms';
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
writeFileSync(
|
|
245
|
+
join(rootDir, 'package.json'),
|
|
246
|
+
JSON.stringify(rootPkg, null, 2)
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
console.log(`\nāļø Configuring CMS Worker '${workerName}' (${cmsDomain})...`);
|
|
250
|
+
const cmsDir = join(rootDir, 'kama-worker');
|
|
251
|
+
mkdirSync(cmsDir, { recursive: true });
|
|
252
|
+
|
|
253
|
+
// šÆ FIX: Clones frozen Cloudflare env and safely injects BUNDLED_DEFINITIONS
|
|
254
|
+
const cmsWorkerIndexContent = `import kamaWorker from '@brightsideoy/kama';
|
|
255
|
+
import customSchemas from './workspace-schemas.js';
|
|
256
|
+
|
|
257
|
+
export default {
|
|
258
|
+
async fetch(request, env, ctx) {
|
|
259
|
+
const engineEnv = {
|
|
260
|
+
...env,
|
|
261
|
+
BUNDLED_DEFINITIONS: customSchemas
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
if (typeof kamaWorker === 'function') {
|
|
265
|
+
return kamaWorker(request, engineEnv, ctx);
|
|
266
|
+
}
|
|
267
|
+
if (kamaWorker && typeof kamaWorker.fetch === 'function') {
|
|
268
|
+
return kamaWorker.fetch(request, engineEnv, ctx);
|
|
269
|
+
}
|
|
270
|
+
return new Response('Kama Engine Worker Ready', { status: 200 });
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
`;
|
|
274
|
+
|
|
275
|
+
writeFileSync(join(cmsDir, 'index.js'), cmsWorkerIndexContent);
|
|
276
|
+
|
|
277
|
+
writeFileSync(
|
|
278
|
+
join(cmsDir, 'package.json'),
|
|
279
|
+
JSON.stringify({
|
|
280
|
+
name: workerName,
|
|
281
|
+
type: 'module',
|
|
282
|
+
version: '1.0.0',
|
|
283
|
+
scripts: {
|
|
284
|
+
"schema:local": `wrangler d1 execute ${dbName} --local --file=../node_modules/@brightsideoy/kama/schema.sql > /dev/null 2>&1`,
|
|
285
|
+
"schema:remote": `wrangler d1 execute ${dbName} --remote --file=../node_modules/@brightsideoy/kama/schema.sql`,
|
|
286
|
+
"dev": "npm run schema:local && wrangler dev",
|
|
287
|
+
"deploy": "npm run schema:remote && wrangler deploy"
|
|
288
|
+
},
|
|
289
|
+
dependencies: {
|
|
290
|
+
'@brightsideoy/kama': currentKamaVersion,
|
|
291
|
+
},
|
|
292
|
+
devDependencies: {
|
|
293
|
+
"wrangler": "^4.130.0",
|
|
294
|
+
"tsx": "^4.19.2",
|
|
295
|
+
},
|
|
296
|
+
}, null, 2)
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const cmsWranglerConfig = {
|
|
300
|
+
$schema: '../node_modules/wrangler/config-schema.json',
|
|
301
|
+
name: workerName,
|
|
302
|
+
main: 'index.js',
|
|
303
|
+
compatibility_date: '2026-08-01',
|
|
304
|
+
compatibility_flags: ['nodejs_compat'],
|
|
305
|
+
assets: {
|
|
306
|
+
directory: '../node_modules/@brightsideoy/kama/dist',
|
|
307
|
+
binding: 'ASSETS'
|
|
308
|
+
},
|
|
309
|
+
images: {
|
|
310
|
+
binding: 'IMAGES'
|
|
311
|
+
},
|
|
312
|
+
vars: {
|
|
313
|
+
CDN_DOMAIN: cmsDomain,
|
|
314
|
+
PUBLIC_FRONTEND_URL: (isCloudflare && shouldCreateSite) ? `https://${siteDomain}` : 'http://localhost:4321',
|
|
315
|
+
CLOUDFLARE_ZONE_ID: isCloudflare ? "REPLACE_WITH_YOUR_32_CHAR_ZONE_ID" : "local-zone-not-needed"
|
|
316
|
+
},
|
|
317
|
+
d1_databases: [{
|
|
318
|
+
binding: 'DB',
|
|
319
|
+
database_name: dbName,
|
|
320
|
+
database_id: d1DatabaseId,
|
|
321
|
+
}],
|
|
322
|
+
r2_buckets: [{
|
|
323
|
+
binding: 'MEDIA_BUCKET',
|
|
324
|
+
bucket_name: bucketName,
|
|
325
|
+
}],
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
if (isCloudflare) {
|
|
329
|
+
cmsWranglerConfig.routes = [{ pattern: cmsDomain, custom_domain: true }];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
writeFileSync(join(cmsDir, 'wrangler.json'), JSON.stringify(cmsWranglerConfig, null, 2));
|
|
333
|
+
|
|
334
|
+
writeFileSync(
|
|
335
|
+
join(cmsDir, '.dev.vars'),
|
|
336
|
+
`# Cloudflare Edge Cache Purging Secrets\nCLOUDFLARE_API_TOKEN=${isCloudflare ? 'cfut_YOUR_CUSTOM_PURGE_TOKEN' : 'local_token'}\n`
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
await bundleWorkspaceSchemas(rootDir);
|
|
340
|
+
|
|
341
|
+
if (shouldCreateSite) {
|
|
342
|
+
await generateAstroSite(rootDir, siteFolderName, siteDomain, cmsFullUrl, workerName, isCloudflare);
|
|
343
|
+
} else {
|
|
344
|
+
console.log(`\nā¹ļø Skipping Astro site creation. You can add a site later by running 'npx kama add site' inside this workspace.`);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
console.log(`\n⨠Workspace ready! Next steps:`);
|
|
348
|
+
if (!isCurrentDir) {
|
|
349
|
+
console.log(` 1. cd ${projName}`);
|
|
350
|
+
console.log(` 2. npm install`);
|
|
351
|
+
} else {
|
|
352
|
+
console.log(` 1. npm install`);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (isCloudflare) {
|
|
356
|
+
if (d1DatabaseId === '00000000-0000-0000-0000-000000000000' || d1DatabaseId === 'REPLACE_WITH_YOUR_D1_ID') {
|
|
357
|
+
console.log(` 3. Run 'npx wrangler login' and create a D1 database manually.`);
|
|
358
|
+
}
|
|
359
|
+
console.log(` 4. Update CLOUDFLARE_ZONE_ID in kama-worker/wrangler.json with your actual Zone ID`);
|
|
360
|
+
console.log(` 5. Securely store your purge token: cd kama-worker && npx wrangler secret put CLOUDFLARE_API_TOKEN`);
|
|
361
|
+
console.log(` 6. Run 'npm run dev' from the workspace root to start everything.`);
|
|
362
|
+
} else {
|
|
363
|
+
console.log(` 3. Run 'npm run dev' from the workspace root to start the local backend and frontend.`);
|
|
364
|
+
console.log(`\nš” Note: You can deploy to Cloudflare later by running 'npx kama promote'.`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* COMMAND: npx kama add site
|
|
370
|
+
* Add Astro Site to existing workspace
|
|
371
|
+
*/
|
|
372
|
+
async function createNewSiteOnly() {
|
|
373
|
+
const rootDir = process.cwd();
|
|
374
|
+
|
|
375
|
+
if (!existsSync(join(rootDir, 'package.json'))) {
|
|
376
|
+
console.error('ā Error: Must run this command inside the root of your workspace.');
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
let targetWorkerName = 'kama-worker';
|
|
381
|
+
const cmsWranglerPath = join(rootDir, 'kama-worker', 'wrangler.json');
|
|
382
|
+
if (existsSync(cmsWranglerPath)) {
|
|
383
|
+
try {
|
|
384
|
+
const cmsWrangler = JSON.parse(readFileSync(cmsWranglerPath, 'utf-8'));
|
|
385
|
+
if (cmsWrangler.name) targetWorkerName = cmsWrangler.name;
|
|
386
|
+
} catch (e) { }
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
console.log('\nš Deployment Mode:');
|
|
390
|
+
console.log(' 1) Local Development');
|
|
391
|
+
console.log(' 2) Cloudflare Edge');
|
|
392
|
+
const envChoice = await rl.question('\nChoice [1/2]: ');
|
|
393
|
+
const isCloudflare = envChoice.trim() === '2';
|
|
394
|
+
|
|
395
|
+
const siteFolder = await rl.question('Site folder name (e.g., my-new-site): ');
|
|
396
|
+
|
|
397
|
+
let siteDomain = 'localhost:4321';
|
|
398
|
+
let cmsFullUrl = 'http://localhost:8787';
|
|
399
|
+
|
|
400
|
+
if (isCloudflare) {
|
|
401
|
+
siteDomain = await rl.question('Website Domain (e.g., brand.com): ');
|
|
402
|
+
const cmsDomainInput = (await rl.question('CMS API Domain (e.g., admin.t3cloud.eu): ')) || 'admin.t3cloud.eu';
|
|
403
|
+
cmsFullUrl = `https://${cmsDomainInput.replace(/^https?:\/\//, '')}`;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
await generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, targetWorkerName, isCloudflare);
|
|
407
|
+
|
|
408
|
+
console.log(`\n⨠Site created in sites/${siteFolder}! Run 'npm install' at workspace root.`);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* COMMAND: npx kama bundle
|
|
413
|
+
* Reads YAML from workspace and builds a static module for the worker
|
|
414
|
+
*/
|
|
415
|
+
async function bundleWorkspaceSchemas(targetRootDir) {
|
|
416
|
+
const rootDir = targetRootDir || process.cwd();
|
|
417
|
+
const defsDir = join(rootDir, 'content', 'definitions');
|
|
418
|
+
const workerDir = join(rootDir, 'kama-worker');
|
|
419
|
+
|
|
420
|
+
if (!existsSync(defsDir)) {
|
|
421
|
+
console.warn(`\nā ļø Skipping schema bundling: 'content/definitions' folder not found at ${defsDir}`);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
console.log(`\nš¦ Bundling workspace YAML schemas for Edge deployment...`);
|
|
426
|
+
|
|
427
|
+
function crawlDirectory(dir, baseDir, result = {}) {
|
|
428
|
+
if (!existsSync(dir)) return result;
|
|
429
|
+
const items = readdirSync(dir);
|
|
430
|
+
for (const item of items) {
|
|
431
|
+
const fullPath = join(dir, item);
|
|
432
|
+
if (existsSync(fullPath) && statSync(fullPath).isDirectory()) {
|
|
433
|
+
crawlDirectory(fullPath, baseDir, result);
|
|
434
|
+
} else if (fullPath.endsWith('.yaml') || fullPath.endsWith('.yml')) {
|
|
435
|
+
const relativePath = './' + relative(baseDir, fullPath).replace(/\\/g, '/');
|
|
436
|
+
result[relativePath] = readFileSync(fullPath, 'utf-8');
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return result;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const rawGlobs = {
|
|
443
|
+
nodes: crawlDirectory(join(defsDir, 'nodes'), defsDir),
|
|
444
|
+
trees: crawlDirectory(join(defsDir, 'trees'), defsDir),
|
|
445
|
+
blocks: crawlDirectory(join(defsDir, 'blocks'), defsDir),
|
|
446
|
+
nodeLayouts: crawlDirectory(join(defsDir, 'node-layouts'), defsDir),
|
|
447
|
+
blockLayouts: crawlDirectory(join(defsDir, 'block-layouts'), defsDir),
|
|
448
|
+
fieldSets: crawlDirectory(join(defsDir, 'field-sets'), defsDir),
|
|
449
|
+
channels: crawlDirectory(join(defsDir, 'channels'), defsDir),
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const fileContent = `// AUTO-GENERATED BUNDLE - DO NOT EDIT
|
|
453
|
+
// This file is generated by 'npx kama bundle' prior to local dev or deployment.
|
|
454
|
+
|
|
455
|
+
export default ${JSON.stringify(rawGlobs, null, 2)};
|
|
456
|
+
`;
|
|
457
|
+
|
|
458
|
+
if (!existsSync(workerDir)) {
|
|
459
|
+
mkdirSync(workerDir, { recursive: true });
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const outPath = join(workerDir, 'workspace-schemas.js');
|
|
463
|
+
writeFileSync(outPath, fileContent, 'utf-8');
|
|
464
|
+
|
|
465
|
+
console.log(`ā
Schema bundle generated successfully at ${outPath}`);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* COMMAND: npx kama promote
|
|
470
|
+
* Promote current Local workspace to Cloudflare Edge
|
|
471
|
+
*/
|
|
472
|
+
async function promoteLocalToCloudflare() {
|
|
473
|
+
const rootDir = process.cwd();
|
|
474
|
+
const workerWranglerPath = join(rootDir, 'kama-worker', 'wrangler.json');
|
|
475
|
+
|
|
476
|
+
if (!existsSync(workerWranglerPath)) {
|
|
477
|
+
console.error('ā Error: Could not find kama-worker/wrangler.json. Run this command inside your workspace root.');
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
console.log('\nš --- PROMOTING WORKSPACE TO CLOUDFLARE EDGE --- š\n');
|
|
482
|
+
|
|
483
|
+
try {
|
|
484
|
+
execSync('npx wrangler whoami', { stdio: 'pipe' });
|
|
485
|
+
console.log('ā
Logged in to Cloudflare.');
|
|
486
|
+
} catch (e) {
|
|
487
|
+
console.log('š Launching browser for Cloudflare login...');
|
|
488
|
+
try {
|
|
489
|
+
execSync('npx wrangler login', { stdio: 'inherit' });
|
|
490
|
+
} catch (loginError) {
|
|
491
|
+
console.log('ā Cloudflare login failed or was cancelled.');
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const cmsDomain = (await rl.question('\nCMS Admin Domain (e.g., admin.t3cloud.eu): ')) || 'admin.t3cloud.eu';
|
|
497
|
+
|
|
498
|
+
const sitesFolderDir = join(rootDir, 'sites');
|
|
499
|
+
let siteDomain = '';
|
|
500
|
+
if (existsSync(sitesFolderDir)) {
|
|
501
|
+
siteDomain = await rl.question('Frontend Website Domain (e.g., kama.t3cloud.eu): ');
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const currentConfig = JSON.parse(readFileSync(workerWranglerPath, 'utf-8'));
|
|
505
|
+
const dbName = currentConfig.d1_databases?.[0]?.database_name || 'kama_db';
|
|
506
|
+
|
|
507
|
+
console.log(`\nšļø Provisioning remote Cloudflare D1 Database '${dbName}'...`);
|
|
508
|
+
let d1DatabaseId = '00000000-0000-0000-0000-000000000000';
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
const d1Output = execSync(`npx wrangler d1 create ${dbName}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
512
|
+
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}/);
|
|
513
|
+
if (match) d1DatabaseId = match[0];
|
|
514
|
+
} catch (err) {
|
|
515
|
+
console.log('ā ļø Could not auto-create D1 (it may already exist). Please verify on your Cloudflare dashboard.');
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
currentConfig.vars.CDN_DOMAIN = cmsDomain;
|
|
519
|
+
if (siteDomain) {
|
|
520
|
+
currentConfig.vars.PUBLIC_FRONTEND_URL = `https://${siteDomain}`;
|
|
521
|
+
}
|
|
522
|
+
currentConfig.routes = [{ pattern: cmsDomain, custom_domain: true }];
|
|
523
|
+
if (currentConfig.d1_databases?.[0]) {
|
|
524
|
+
currentConfig.d1_databases[0].database_id = d1DatabaseId;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
writeFileSync(workerWranglerPath, JSON.stringify(currentConfig, null, 2));
|
|
528
|
+
|
|
529
|
+
if (siteDomain && existsSync(sitesFolderDir)) {
|
|
530
|
+
const sites = readdirSync(sitesFolderDir);
|
|
531
|
+
if (sites.length > 0) {
|
|
532
|
+
const siteWranglerPath = join(sitesFolderDir, sites[0], 'wrangler.json');
|
|
533
|
+
if (existsSync(siteWranglerPath)) {
|
|
534
|
+
const siteConfig = JSON.parse(readFileSync(siteWranglerPath, 'utf-8'));
|
|
535
|
+
siteConfig.routes = [{ pattern: siteDomain, custom_domain: true }];
|
|
536
|
+
siteConfig.vars.PUBLIC_CMS_URL = `https://${cmsDomain}`;
|
|
537
|
+
siteConfig.vars.PUBLIC_API_URL = `https://${cmsDomain}`;
|
|
538
|
+
writeFileSync(siteWranglerPath, JSON.stringify(siteConfig, null, 2));
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
console.log(`\nā
Workspace successfully configured for Cloudflare Edge!`);
|
|
544
|
+
console.log(`\nNext steps:`);
|
|
545
|
+
console.log(` 1. Run 'npm run deploy:cms' from the root to publish your backend and database.`);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Helper: Copies the Astro starter template and hydrates runtime tokens
|
|
550
|
+
*/
|
|
551
|
+
async function generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, workerName = 'kama-worker', isCloudflare = false) {
|
|
552
|
+
console.log(`\nš Scaffolding Astro site (${siteFolder}) mapped to ${siteDomain} (Service: ${workerName})...`);
|
|
553
|
+
|
|
554
|
+
const siteDir = join(rootDir, 'sites', siteFolder);
|
|
555
|
+
const templateDir = join(__dirname, '../templates/astro-starter');
|
|
556
|
+
|
|
557
|
+
if (!existsSync(templateDir)) {
|
|
558
|
+
console.error(`ā Error: Astro template not found at ${templateDir}`);
|
|
559
|
+
console.error(`Please ensure your Kama package installation is complete and templates are published.`);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
cpSync(templateDir, siteDir, { recursive: true });
|
|
564
|
+
|
|
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);
|
|
570
|
+
|
|
571
|
+
writeFileSync(join(siteDir, 'package.json'), pkgContent);
|
|
572
|
+
unlinkSync(templatePkgPath);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const wranglerPath = join(siteDir, 'wrangler.json');
|
|
576
|
+
if (existsSync(wranglerPath)) {
|
|
577
|
+
let wranglerStr = readFileSync(wranglerPath, 'utf-8')
|
|
578
|
+
.replace(/\{\{SITE_NAME\}\}/g, siteFolder)
|
|
579
|
+
.replace(/\{\{WORKER_NAME\}\}/g, workerName)
|
|
580
|
+
.replace(/\{\{CMS_URL\}\}/g, cmsFullUrl);
|
|
581
|
+
|
|
582
|
+
if (isCloudflare) {
|
|
583
|
+
const configObj = JSON.parse(wranglerStr);
|
|
584
|
+
configObj.routes = [{ pattern: siteDomain, custom_domain: true }];
|
|
585
|
+
wranglerStr = JSON.stringify(configObj, null, 2);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
writeFileSync(wranglerPath, wranglerStr);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const envContent = [
|
|
592
|
+
`PUBLIC_CMS_URL="${cmsFullUrl}"`,
|
|
593
|
+
`PUBLIC_API_URL="${cmsFullUrl}"`,
|
|
594
|
+
`PUBLIC_CHANNEL_ID="website"`,
|
|
595
|
+
`PUBLIC_TREE_ID="main-navigation"`,
|
|
596
|
+
`PUBLIC_SITE_NAME="KAMA Starter"`
|
|
597
|
+
].join('\n') + '\n';
|
|
598
|
+
|
|
599
|
+
writeFileSync(join(siteDir, '.env'), envContent);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
id: three-column
|
|
2
|
+
label: Three Columns (33/33/33)
|
|
3
|
+
showLabel: false
|
|
4
|
+
group: Sub-Layouts
|
|
5
|
+
|
|
6
|
+
layout:
|
|
7
|
+
columns: "1fr 1fr 1fr"
|
|
8
|
+
areas:
|
|
9
|
+
- ["left", "middle", "right"]
|
|
10
|
+
|
|
11
|
+
regions:
|
|
12
|
+
left:
|
|
13
|
+
label: Left Column
|
|
14
|
+
showLabel: false # š Hides "LEFT COLUMN" header on this region cell
|
|
15
|
+
middle:
|
|
16
|
+
label: Middle Column
|
|
17
|
+
showLabel: false # š Hides "LEFT COLUMN" header on this region cell
|
|
18
|
+
right:
|
|
19
|
+
label: Right Column
|
|
20
|
+
showLabel: false # š Keeps "RIGHT COLUMN" visible
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
id: two-column
|
|
2
|
+
label: Two Columns (50/50)
|
|
3
|
+
showLabel: false # š Hides the main "Two Columns" badge on the canvas
|
|
4
|
+
group: Sub-Layouts
|
|
5
|
+
|
|
6
|
+
layout:
|
|
7
|
+
columns: "1fr 1fr"
|
|
8
|
+
areas:
|
|
9
|
+
- ["left", "right"]
|
|
10
|
+
|
|
11
|
+
regions:
|
|
12
|
+
left:
|
|
13
|
+
label: Left Column
|
|
14
|
+
showLabel: false # š Hides "LEFT COLUMN" header on this region cell
|
|
15
|
+
right:
|
|
16
|
+
label: Right Column
|
|
17
|
+
showLabel: false # š Keeps "RIGHT COLUMN" visible
|
|
18
|
+
|
|
19
|
+
# Optional: We can give the layout block its own settings!
|
|
20
|
+
fields:
|
|
21
|
+
gap:
|
|
22
|
+
label: Column Gap
|
|
23
|
+
type: select
|
|
24
|
+
default: "2rem"
|
|
25
|
+
options:
|
|
26
|
+
- { label: "None", value: "0" }
|
|
27
|
+
- { label: "Small", value: "1rem" }
|
|
28
|
+
- { label: "Medium", value: "2rem" }
|
|
29
|
+
- { label: "Large", value: "4rem" }
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
id: button
|
|
2
|
+
label: Button
|
|
3
|
+
group: Content
|
|
4
|
+
description: A customizable button link.
|
|
5
|
+
|
|
6
|
+
fields:
|
|
7
|
+
text:
|
|
8
|
+
label: Text
|
|
9
|
+
type: richtext
|
|
10
|
+
editor: button
|
|
11
|
+
public: true
|
|
12
|
+
link:
|
|
13
|
+
label: Link
|
|
14
|
+
type: link
|
|
15
|
+
position: sidebar
|
|
16
|
+
allowedLinkTypes:
|
|
17
|
+
- tree
|
|
18
|
+
- node
|
|
19
|
+
- url
|
|
20
|
+
- file
|
|
21
|
+
- email
|
|
22
|
+
- telephone
|
|
23
|
+
tabs:
|
|
24
|
+
access: !include ../field-sets/access.yaml
|