@brightsideoy/kama 0.1.0-alpha.99 → 1.0.0-alpha.3
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 +169 -0
- package/bin/cli.js +775 -251
- 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 +13 -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 +41 -0
- package/{defaults → content}/definitions/field-sets/access.yaml +3 -5
- package/content/definitions/field-sets/channel-base.yaml +57 -0
- package/{defaults/definitions/field-sets/page-meta.yaml → content/definitions/field-sets/channel-seo.yaml} +3 -11
- 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/{defaults → content}/definitions/field-sets/page-social.yaml +6 -8
- package/content/definitions/field-sets/schema-type.yaml +43 -0
- package/content/definitions/field-sets/tree-base.yaml +31 -0
- package/content/definitions/node-layouts/content.yml +12 -0
- package/{defaults → content}/definitions/node-layouts/landing-page.yml +8 -3
- package/{defaults → content}/definitions/node-layouts/single-column.yml +10 -5
- package/{defaults → content}/definitions/node-layouts/two-column.yml +14 -6
- package/content/definitions/nodes/address.yaml +40 -0
- package/content/definitions/nodes/article.yaml +49 -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/{defaults → content}/definitions/nodes/page.yaml +9 -20
- package/content/definitions/nodes/person.yaml +37 -0
- package/content/definitions/nodes/product.yaml +89 -0
- package/content/definitions/taxonomies/categories.yaml +15 -0
- package/content/definitions/taxonomies/tags.yaml +12 -0
- package/content/definitions/trees/navigation.yaml +24 -0
- package/definitions/editor/default.ts +1 -0
- package/dist/assets/index-C3LzwJjY.css +1 -0
- package/dist/assets/index-DhguVxzs.js +164 -0
- package/dist/dist-cK3xGnBZ.js +26457 -0
- package/dist/index.html +4 -2
- package/dist/index.js +24028 -7125
- package/dist/schema-compiler.js +48 -0
- package/documentation/SCHEMAS.md +182 -0
- package/package.json +20 -24
- package/schema.sql +55 -23
- package/templates/astro-starter/astro.config.mjs +27 -0
- package/templates/astro-starter/package.json.template +20 -0
- package/templates/astro-starter/public/robots.txt +23 -0
- package/templates/astro-starter/src/components/blocks/BlockRenderer.astro +134 -0
- package/templates/astro-starter/src/components/blocks/ColumnLayout.astro +67 -0
- package/templates/astro-starter/src/components/blocks/HeaderImage.astro +132 -0
- package/templates/astro-starter/src/components/blocks/Hero.astro +218 -0
- package/templates/astro-starter/src/components/blocks/HtmlBlock.astro +25 -0
- package/templates/astro-starter/src/components/blocks/ImageBlock.astro +92 -0
- package/templates/astro-starter/src/components/blocks/TextBlock.astro +15 -0
- package/templates/astro-starter/src/components/global/Breadcrumb.astro +108 -0
- package/templates/astro-starter/src/components/global/Footer.astro +50 -0
- package/templates/astro-starter/src/components/global/Header.astro +171 -0
- package/templates/astro-starter/src/components/global/MetaHead.astro +266 -0
- package/templates/astro-starter/src/components/global/NavigationMenu.astro +413 -0
- package/templates/astro-starter/src/components/ui/KamaImage.astro +172 -0
- package/templates/astro-starter/src/layouts/SkeletonLayout.astro +56 -0
- package/templates/astro-starter/src/layouts/StandardLayout.astro +62 -0
- package/templates/astro-starter/src/lib/cms.ts +217 -0
- package/templates/astro-starter/src/lib/media/kama-image-service.ts +56 -0
- package/templates/astro-starter/src/lib/media/media-proxy.ts +129 -0
- package/templates/astro-starter/src/lib/media/media-url.ts +98 -0
- package/templates/astro-starter/src/middleware.ts +435 -0
- package/templates/astro-starter/src/pages/404.astro +46 -0
- package/templates/astro-starter/src/pages/[...slug].astro +243 -0
- package/templates/astro-starter/src/pages/llms-full.txt.ts +171 -0
- package/templates/astro-starter/src/pages/llms.txt.ts +113 -0
- package/templates/astro-starter/src/pages/media/[...path].ts +14 -0
- package/templates/astro-starter/src/pages/partials/footer.astro +69 -0
- package/templates/astro-starter/src/pages/partials/nav.astro +141 -0
- package/templates/astro-starter/src/styles/standard-theme.css +157 -0
- package/templates/astro-starter/src/utils/tiptap.ts +113 -0
- package/templates/astro-starter/tsconfig.json +0 -0
- package/templates/kama-worker/index.js +19 -0
- package/templates/kama-worker/server.js +104 -0
- package/wrangler.json +22 -0
- package/defaults/definitions/blocks/button.yaml +0 -15
- package/defaults/definitions/blocks/header.yaml +0 -15
- package/defaults/definitions/blocks/header_image.yaml +0 -22
- package/defaults/definitions/blocks/hero.yaml +0 -27
- package/defaults/definitions/blocks/image_card.yaml +0 -57
- package/defaults/definitions/blocks/single_image.yaml +0 -28
- package/defaults/definitions/blocks/text.yaml +0 -16
- package/defaults/definitions/channels/ecommerce.yaml +0 -24
- package/defaults/definitions/channels/website.yaml +0 -32
- package/defaults/definitions/field-sets/channel-base.yaml +0 -31
- package/defaults/definitions/field-sets/content-block.yaml +0 -46
- package/defaults/definitions/field-sets/page-base.yaml +0 -49
- package/defaults/definitions/field-sets/tree-base.yaml +0 -31
- package/defaults/definitions/node-layouts/three-column.yml +0 -41
- package/defaults/definitions/nodes/address.yaml +0 -32
- package/defaults/definitions/nodes/article.yaml +0 -43
- package/defaults/definitions/nodes/event.yaml +0 -49
- package/defaults/definitions/nodes/external-link.yaml +0 -26
- package/defaults/definitions/nodes/person.yaml +0 -37
- package/defaults/definitions/nodes/product.yaml +0 -86
- package/defaults/definitions/trees/navigation.yaml +0 -29
- package/defaults/definitions/trees/social-links.yaml +0 -9
- package/definitions/_page.yaml +0 -52
- package/dist/assets/index-D6qQsWOj.js +0 -1392
- package/dist/assets/index-TdW5F0pX.css +0 -1
- package/dist/media/kama/2026/08/17a098c92bc54ea4b4441e531a34575b.jpg +0 -0
- package/dist/media/kama/2026/08/3e9ae457dd23461680cc699f42d07cf0.jpg +0 -0
- package/dist/media/kama/2026/08/8e5b3a2fd194459f9b962f8e12928bf0.jpg +0 -0
- package/dist/media/kama/2026/08/8ea5c21026cd4944bdf78c99fe2544a9.jpg +0 -0
- package/scripts/bundle-definitions.js +0 -176
- /package/{defaults → content}/definitions/block-layouts/three-column.yaml +0 -0
- /package/{defaults → content}/definitions/block-layouts/two-column.yaml +0 -0
- /package/{defaults → content}/definitions/field-sets/comments.yaml +0 -0
- /package/{defaults → content}/definitions/field-sets/visibility.yaml +0 -0
package/bin/cli.js
CHANGED
|
@@ -1,343 +1,867 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, cpSync, unlinkSync, appendFileSync } from 'node:fs';
|
|
4
|
+
import { join, dirname, basename, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
|
+
|
|
7
|
+
// 🎯 Modern CLI UI Components
|
|
8
|
+
import { intro, outro, text, select, confirm, spinner, note, isCancel, cancel } from '@clack/prompts';
|
|
9
|
+
|
|
10
|
+
// --- UI HELPERS (Zero Dependency Colors) ---
|
|
11
|
+
const pc = {
|
|
12
|
+
green: (t) => `\x1b[32m${t}\x1b[0m`,
|
|
13
|
+
cyan: (t) => `\x1b[36m${t}\x1b[0m`,
|
|
14
|
+
dim: (t) => `\x1b[2m${t}\x1b[0m`,
|
|
15
|
+
yellow: (t) => `\x1b[33m${t}\x1b[0m`,
|
|
16
|
+
red: (t) => `\x1b[31m${t}\x1b[0m`,
|
|
17
|
+
bgCyan: (t) => `\x1b[46m\x1b[30m ${t} \x1b[0m`,
|
|
18
|
+
bold: (t) => `\x1b[1m${t}\x1b[0m`,
|
|
19
|
+
reset: '\x1b[0m'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Graceful exit handler for Ctrl+C
|
|
23
|
+
function handleCancel(value) {
|
|
24
|
+
if (isCancel(value)) {
|
|
25
|
+
cancel('Operation cancelled.');
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 🎯 Always use the absolute newest ecosystem
|
|
32
|
+
const ASTRO_LATEST = "latest";
|
|
33
|
+
const ASTRO_NODE_LATEST = "latest";
|
|
34
|
+
const ASTRO_CF_LATEST = "latest";
|
|
35
|
+
const WRANGLER_LATEST = "latest";
|
|
36
|
+
|
|
37
|
+
// Dynamically resolve current package version
|
|
38
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
39
|
+
const __dirname = dirname(__filename);
|
|
40
|
+
let currentKamaVersion = 'alpha';
|
|
41
|
+
try {
|
|
42
|
+
const pkgPath = join(__dirname, '../package.json');
|
|
43
|
+
if (existsSync(pkgPath)) {
|
|
44
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
45
|
+
if (pkg.version) currentKamaVersion = `^${pkg.version}`;
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.warn("⚠️ Could not detect Kama version, defaulting to 'alpha'");
|
|
49
|
+
}
|
|
6
50
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
51
|
+
function showHelp() {
|
|
52
|
+
console.log(`\n${pc.bgCyan(' KAMA • ENGINE CLI ')}\n`);
|
|
53
|
+
console.log('Usage: npx kama <command> [options]\n');
|
|
54
|
+
|
|
55
|
+
console.log(pc.cyan('Commands:'));
|
|
56
|
+
console.log(' init Kickstart a new Monorepo (CMS Worker + Optional Astro Site)');
|
|
57
|
+
console.log(' add site Add a new Astro Frontend Site to the current workspace (sites/)');
|
|
58
|
+
console.log(' bundle Compile and bundle YAML schemas into bundled-definitions.js for Edge deployment');
|
|
59
|
+
console.log(' promote Promote the current Local workspace to Cloudflare Edge');
|
|
60
|
+
console.log(' help, -h Show this help message\n');
|
|
61
|
+
|
|
62
|
+
console.log(pc.cyan('Workspace Scripts (Run from project root):'));
|
|
63
|
+
console.log(' npm update Update to latest KAMA version');
|
|
64
|
+
console.log(' npm run setup Initialize database schema and seed data (Run Once)');
|
|
65
|
+
console.log(' npm run dev Boot up the CMS backend and Astro frontend concurrently');
|
|
66
|
+
console.log(' npm run deploy:cms Deploy the backend worker and database to Cloudflare');
|
|
67
|
+
console.log(' npm run deploy:site Deploy the Astro frontend site to Cloudflare\n');
|
|
68
|
+
|
|
69
|
+
console.log(pc.cyan('Examples:'));
|
|
70
|
+
console.log(' npx kama init');
|
|
71
|
+
console.log(' npx kama add site');
|
|
72
|
+
console.log(' npx kama bundle\n');
|
|
73
|
+
}
|
|
11
74
|
|
|
12
75
|
async function main() {
|
|
13
|
-
|
|
76
|
+
const args = process.argv.slice(2);
|
|
77
|
+
const command = args[0]?.toLowerCase();
|
|
78
|
+
const subCommand = args[1]?.toLowerCase();
|
|
14
79
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
80
|
+
if (['help', '--help', '-h'].includes(command)) {
|
|
81
|
+
showHelp();
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
18
84
|
|
|
19
|
-
|
|
85
|
+
console.clear();
|
|
86
|
+
intro(`${pc.bgCyan(' KAMA • ENGINE ')} ${pc.dim('v' + currentKamaVersion.replace('^', ''))}`);
|
|
20
87
|
|
|
21
|
-
if (
|
|
88
|
+
if (command === 'init') {
|
|
89
|
+
await createFullWorkspace();
|
|
90
|
+
} else if (command === 'add' && subCommand === 'site') {
|
|
22
91
|
await createNewSiteOnly();
|
|
92
|
+
} else if (command === 'promote') {
|
|
93
|
+
await promoteLocalToCloudflare();
|
|
94
|
+
} else if (command === 'bundle') {
|
|
95
|
+
await bundleWorkspaceSchemas();
|
|
23
96
|
} else {
|
|
24
|
-
await
|
|
97
|
+
const choice = handleCancel(await select({
|
|
98
|
+
message: 'What would you like to do?',
|
|
99
|
+
options: [
|
|
100
|
+
{ value: 'init', label: 'Init', hint: 'Kickstart new Workspace (CMS + Optional Site)' },
|
|
101
|
+
{ value: 'add_site', label: 'Add Site', hint: 'Add Astro Site to current workspace (sites/)' },
|
|
102
|
+
{ value: 'bundle', label: 'Bundle', hint: 'Compile YAML schemas for Edge deployment' },
|
|
103
|
+
{ value: 'promote', label: 'Promote', hint: 'Promote current Local workspace to Cloudflare Edge' },
|
|
104
|
+
{ value: 'help', label: 'Help', hint: 'Show all commands and usage' },
|
|
105
|
+
{ value: 'exit', label: 'Exit' }
|
|
106
|
+
]
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
if (choice === 'help') {
|
|
110
|
+
showHelp();
|
|
111
|
+
process.exit(0);
|
|
112
|
+
} else if (choice === 'promote') {
|
|
113
|
+
await promoteLocalToCloudflare();
|
|
114
|
+
} else if (choice === 'bundle') {
|
|
115
|
+
await bundleWorkspaceSchemas();
|
|
116
|
+
} else if (choice === 'add_site') {
|
|
117
|
+
await createNewSiteOnly();
|
|
118
|
+
} else if (choice === 'init') {
|
|
119
|
+
await createFullWorkspace();
|
|
120
|
+
} else {
|
|
121
|
+
outro('Goodbye!');
|
|
122
|
+
process.exit(0);
|
|
123
|
+
}
|
|
25
124
|
}
|
|
125
|
+
}
|
|
26
126
|
|
|
27
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Helper to update astro.config.mjs for Cloudflare
|
|
129
|
+
*/
|
|
130
|
+
function convertAstroConfigToCloudflare(siteDir) {
|
|
131
|
+
const configPath = join(siteDir, 'astro.config.mjs');
|
|
132
|
+
if (existsSync(configPath)) {
|
|
133
|
+
let content = readFileSync(configPath, 'utf-8');
|
|
134
|
+
content = content
|
|
135
|
+
.replace("import node from '@astrojs/node';", "import cloudflare from '@astrojs/cloudflare';")
|
|
136
|
+
.replace('adapter: node({ mode: "standalone" })', 'adapter: cloudflare({ imageService: process.env.NODE_ENV === "production" ? "cloudflare" : "passthrough" })');
|
|
137
|
+
writeFileSync(configPath, content, 'utf-8');
|
|
138
|
+
}
|
|
28
139
|
}
|
|
29
140
|
|
|
30
141
|
/**
|
|
31
|
-
*
|
|
142
|
+
* COMMAND: npx kama init
|
|
143
|
+
* Kickstart full Monorepo Workspace
|
|
32
144
|
*/
|
|
33
145
|
async function createFullWorkspace() {
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
146
|
+
const cwdName = basename(process.cwd());
|
|
147
|
+
const hasExistingPkg = existsSync(join(process.cwd(), 'package.json'));
|
|
148
|
+
const defaultDirPrompt = hasExistingPkg ? '.' : cwdName;
|
|
149
|
+
|
|
150
|
+
const inputName = handleCancel(await text({
|
|
151
|
+
message: 'Project folder name (use "." for current dir):',
|
|
152
|
+
placeholder: defaultDirPrompt,
|
|
153
|
+
defaultValue: defaultDirPrompt
|
|
154
|
+
}));
|
|
155
|
+
|
|
156
|
+
const chosenFolder = inputName.trim() || defaultDirPrompt;
|
|
157
|
+
const isCurrentDir = chosenFolder === '.';
|
|
158
|
+
const projName = isCurrentDir ? cwdName : chosenFolder;
|
|
159
|
+
const rootDir = isCurrentDir ? process.cwd() : join(process.cwd(), projName);
|
|
160
|
+
|
|
161
|
+
const isCloudflare = handleCancel(await select({
|
|
162
|
+
message: 'Choose your Deployment Architecture:',
|
|
163
|
+
options: [
|
|
164
|
+
{ value: true, label: 'Cloudflare Edge', hint: 'Workers, D1, R2, CF Image Resizing' },
|
|
165
|
+
{ value: false, label: 'VPS / Local Server', hint: 'Node.js, SQLite, Sharp cropping' }
|
|
166
|
+
]
|
|
167
|
+
}));
|
|
168
|
+
|
|
169
|
+
const shouldCreateSite = handleCancel(await confirm({
|
|
170
|
+
message: 'Do you want to scaffold an initial Astro frontend site?',
|
|
171
|
+
initialValue: true
|
|
172
|
+
}));
|
|
173
|
+
|
|
174
|
+
let siteFolderName = 'kama-starter';
|
|
175
|
+
let siteDomain = 'localhost:4321';
|
|
176
|
+
let cmsDomain = 'localhost:8787';
|
|
177
|
+
|
|
178
|
+
if (shouldCreateSite) {
|
|
179
|
+
siteFolderName = handleCancel(await text({
|
|
180
|
+
message: 'Astro package/folder name (e.g., my-website.eu):',
|
|
181
|
+
placeholder: 'kama-starter',
|
|
182
|
+
defaultValue: 'kama-starter'
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
37
185
|
|
|
38
|
-
|
|
186
|
+
if (isCloudflare) {
|
|
187
|
+
cmsDomain = handleCancel(await text({
|
|
188
|
+
message: 'CMS Admin Domain (e.g., admin.yourdomain.com):',
|
|
189
|
+
defaultValue: ''
|
|
190
|
+
}));
|
|
191
|
+
if (shouldCreateSite) {
|
|
192
|
+
siteDomain = handleCancel(await text({
|
|
193
|
+
message: 'First Website Domain (e.g., yourdomain.com):',
|
|
194
|
+
defaultValue: ''
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const cmsProtocol = isCloudflare ? 'https://' : 'http://';
|
|
200
|
+
const cmsFullUrl = cmsDomain ? `${cmsProtocol}${cmsDomain.replace(/^https?:\/\//, '')}` : 'http://localhost:8787';
|
|
39
201
|
|
|
40
|
-
// 🎯 Derive dynamic worker, database, and bucket names from project folder name
|
|
41
202
|
const workerName = `${projName}-worker`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
42
203
|
let dbName = `${projName}-db`.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
43
204
|
const bucketName = `${projName}-media-prod`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
44
205
|
|
|
45
|
-
|
|
46
|
-
|
|
206
|
+
const s = spinner();
|
|
207
|
+
s.start('Provisioning workspace...');
|
|
208
|
+
|
|
209
|
+
if (!isCurrentDir) {
|
|
210
|
+
mkdirSync(rootDir, { recursive: true });
|
|
211
|
+
}
|
|
47
212
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
213
|
+
writeFileSync(
|
|
214
|
+
join(rootDir, '.gitignore'),
|
|
215
|
+
`node_modules/\n.env\n.DS_Store\nkama-worker/.dev.vars\nkama-worker/.env\nkama-worker/.wrangler/\nkama-worker/public/\nkama-worker/*.db\nsites/*/.env\nsites/*/.astro/\nsites/*/dist/\nkama-worker/bundled-definitions.js\n`
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const sourceDefsDir = join(__dirname, '../content/definitions');
|
|
219
|
+
const targetDefsDir = join(rootDir, 'content', 'definitions');
|
|
220
|
+
if (existsSync(sourceDefsDir)) {
|
|
221
|
+
cpSync(sourceDefsDir, targetDefsDir, { recursive: true });
|
|
222
|
+
}
|
|
51
223
|
|
|
52
|
-
|
|
53
|
-
let d1DatabaseId = 'REPLACE_WITH_YOUR_D1_ID';
|
|
224
|
+
let d1DatabaseId = '00000000-0000-0000-0000-000000000000';
|
|
54
225
|
let isAuthenticated = false;
|
|
55
226
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
execSync('npx wrangler whoami', { stdio: 'pipe' });
|
|
59
|
-
isAuthenticated = true;
|
|
60
|
-
console.log(`✅ Logged in to Cloudflare.`);
|
|
61
|
-
} catch (error) {
|
|
62
|
-
console.log(`⚠️ You are not logged in to Cloudflare.`);
|
|
63
|
-
console.log(`🌐 Launching browser for Cloudflare login...`);
|
|
227
|
+
if (isCloudflare) {
|
|
228
|
+
s.message('Checking Cloudflare authentication...');
|
|
64
229
|
try {
|
|
65
|
-
execSync('npx wrangler
|
|
230
|
+
execSync('npx wrangler whoami', { stdio: 'ignore' });
|
|
66
231
|
isAuthenticated = true;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
console.log(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
console.log(`\n🗄️ Checking existing Cloudflare D1 databases...`);
|
|
76
|
-
const listOutput = execSync(`npx wrangler d1 list --json`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
77
|
-
|
|
78
|
-
const jsonStart = listOutput.indexOf('[');
|
|
79
|
-
const jsonEnd = listOutput.lastIndexOf(']') + 1;
|
|
80
|
-
let databases = [];
|
|
81
|
-
|
|
82
|
-
if (jsonStart !== -1 && jsonEnd !== -1) {
|
|
83
|
-
databases = JSON.parse(listOutput.substring(jsonStart, jsonEnd));
|
|
232
|
+
} catch (error) {
|
|
233
|
+
s.stop('Not authenticated with Cloudflare.', true);
|
|
234
|
+
console.log(pc.dim('Launching browser for Cloudflare login...'));
|
|
235
|
+
try {
|
|
236
|
+
execSync('npx wrangler login', { stdio: 'inherit' });
|
|
237
|
+
isAuthenticated = true;
|
|
238
|
+
} catch (loginError) {
|
|
239
|
+
// Ignore
|
|
84
240
|
}
|
|
241
|
+
s.start('Resuming provisioning...');
|
|
242
|
+
}
|
|
85
243
|
|
|
86
|
-
|
|
244
|
+
if (isAuthenticated) {
|
|
245
|
+
try {
|
|
246
|
+
s.message('Checking existing Cloudflare D1 databases...');
|
|
247
|
+
const listOutput = execSync(`npx wrangler d1 list --json`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
248
|
+
const jsonStart = listOutput.indexOf('[');
|
|
249
|
+
const jsonEnd = listOutput.lastIndexOf(']') + 1;
|
|
250
|
+
let databases = [];
|
|
87
251
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
252
|
+
if (jsonStart !== -1 && jsonEnd !== -1) {
|
|
253
|
+
databases = JSON.parse(listOutput.substring(jsonStart, jsonEnd));
|
|
254
|
+
}
|
|
91
255
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
256
|
+
const existingDb = databases.find(db => db.name === dbName);
|
|
257
|
+
|
|
258
|
+
if (existingDb) {
|
|
259
|
+
s.stop(`Database '${dbName}' already exists on your account.`);
|
|
260
|
+
const useExisting = handleCancel(await confirm({
|
|
261
|
+
message: 'Do you want to link this existing database?',
|
|
262
|
+
initialValue: true
|
|
263
|
+
}));
|
|
264
|
+
|
|
265
|
+
if (useExisting) {
|
|
266
|
+
d1DatabaseId = existingDb.uuid;
|
|
267
|
+
s.start(`Linked existing database ID: ${d1DatabaseId}`);
|
|
268
|
+
} else {
|
|
269
|
+
dbName = handleCancel(await text({
|
|
270
|
+
message: 'Enter a new database name:'
|
|
271
|
+
}));
|
|
272
|
+
s.start(`Creating new database '${dbName}'...`);
|
|
273
|
+
const d1Output = execSync(`npx wrangler d1 create ${dbName}`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
274
|
+
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}/);
|
|
275
|
+
if (match) {
|
|
276
|
+
d1DatabaseId = match[0];
|
|
277
|
+
}
|
|
278
|
+
}
|
|
95
279
|
} else {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const d1Output = execSync(`npx wrangler d1 create ${dbName}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
280
|
+
s.message(`Attempting to auto-create Cloudflare D1 Database '${dbName}'...`);
|
|
281
|
+
const d1Output = execSync(`npx wrangler d1 create ${dbName}`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
99
282
|
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}/);
|
|
100
283
|
if (match) {
|
|
101
284
|
d1DatabaseId = match[0];
|
|
102
|
-
console.log(`✅ Success! Database ID bound: ${d1DatabaseId}`);
|
|
103
285
|
}
|
|
104
286
|
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const d1Output = execSync(`npx wrangler d1 create ${dbName}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
108
|
-
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}/);
|
|
109
|
-
if (match) {
|
|
110
|
-
d1DatabaseId = match[0];
|
|
111
|
-
console.log(`✅ Success! Database ID bound: ${d1DatabaseId}`);
|
|
112
|
-
}
|
|
287
|
+
} catch (error) {
|
|
288
|
+
// Placeholder
|
|
113
289
|
}
|
|
114
|
-
} catch (error) {
|
|
115
|
-
console.log(`⚠️ Database operation failed. We'll leave a placeholder.`);
|
|
116
290
|
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
s.message('Writing configuration files...');
|
|
294
|
+
|
|
295
|
+
const rootScripts = {
|
|
296
|
+
'setup': 'npm run setup:local -w kama-worker',
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const rootPkg = {
|
|
300
|
+
name: projName,
|
|
301
|
+
private: true,
|
|
302
|
+
type: 'module',
|
|
303
|
+
workspaces: ['kama-worker', 'sites/*'],
|
|
304
|
+
scripts: rootScripts,
|
|
305
|
+
dependencies: {
|
|
306
|
+
'@brightsideoy/kama': currentKamaVersion,
|
|
307
|
+
},
|
|
308
|
+
devDependencies: {}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
if (isCloudflare) {
|
|
312
|
+
rootPkg.devDependencies['wrangler'] = WRANGLER_LATEST;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (shouldCreateSite) {
|
|
316
|
+
rootPkg.devDependencies['concurrently'] = '^9.0.0';
|
|
317
|
+
rootPkg.scripts['dev:site'] = `npm run dev -w sites/${siteFolderName}`;
|
|
318
|
+
rootPkg.scripts['predev'] = 'npx kama bundle';
|
|
319
|
+
rootPkg.scripts['dev'] = 'npx concurrently "npm run dev -w kama-worker" "npm run dev -w sites/' + siteFolderName + '"';
|
|
320
|
+
rootPkg.scripts['deploy:site'] = `npm run deploy -w sites/${siteFolderName}`;
|
|
321
|
+
rootPkg.scripts['deploy:cms'] = `npm run deploy -w kama-worker`;
|
|
117
322
|
} else {
|
|
118
|
-
|
|
323
|
+
rootPkg.scripts['predev'] = 'npx kama bundle';
|
|
324
|
+
rootPkg.scripts['dev'] = 'npm run dev -w kama-worker';
|
|
325
|
+
rootPkg.scripts['deploy:cms'] = `npm run deploy -w kama-worker`;
|
|
119
326
|
}
|
|
120
|
-
// ------------------------------
|
|
121
327
|
|
|
122
|
-
|
|
123
|
-
writeFileSync(
|
|
124
|
-
join(rootDir, 'package.json'),
|
|
125
|
-
JSON.stringify({
|
|
126
|
-
name: projName,
|
|
127
|
-
private: true,
|
|
128
|
-
type: 'module',
|
|
129
|
-
workspaces: ['cms-worker', 'sites/*'],
|
|
130
|
-
scripts: {
|
|
131
|
-
'bundle:schemas': 'node node_modules/@brightsideoy/kama/scripts/bundle-definitions.js',
|
|
132
|
-
'dev:cms': 'npm run bundle:schemas && npm run dev -w cms-worker',
|
|
133
|
-
'deploy:cms': 'npm run bundle:schemas && npm run deploy -w cms-worker',
|
|
134
|
-
},
|
|
135
|
-
dependencies: {
|
|
136
|
-
'@brightsideoy/kama': 'alpha',
|
|
137
|
-
}
|
|
138
|
-
}, null, 2)
|
|
139
|
-
);
|
|
328
|
+
writeFileSync(join(rootDir, 'package.json'), JSON.stringify(rootPkg, null, 2));
|
|
140
329
|
|
|
141
|
-
|
|
142
|
-
console.log(`\n☁️ Configuring CMS Worker '${workerName}' (${cmsDomain})...`);
|
|
143
|
-
const cmsDir = join(rootDir, 'cms-worker');
|
|
330
|
+
const cmsDir = join(rootDir, 'kama-worker');
|
|
144
331
|
mkdirSync(cmsDir, { recursive: true });
|
|
145
332
|
|
|
146
|
-
writeFileSync(
|
|
147
|
-
join(cmsDir, 'index.js'),
|
|
148
|
-
`export { default } from '@brightsideoy/kama';\n`
|
|
149
|
-
);
|
|
333
|
+
writeFileSync(join(cmsDir, 'seed.sql'), '-- Kama Auto-Generated Seed File\nSELECT 1;\n');
|
|
150
334
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
JSON.stringify({
|
|
154
|
-
name: workerName,
|
|
155
|
-
type: 'module',
|
|
156
|
-
version: '1.0.0',
|
|
157
|
-
scripts: {
|
|
158
|
-
"bundle:schemas": "node ../node_modules/@brightsideoy/kama/scripts/bundle-definitions.js",
|
|
159
|
-
"schema:local": `npm run bundle:schemas && wrangler d1 execute ${dbName} --local --file=../node_modules/@brightsideoy/kama/schema.sql`,
|
|
160
|
-
"schema:remote": `npm run bundle:schemas && wrangler d1 execute ${dbName} --remote --file=../node_modules/@brightsideoy/kama/schema.sql`,
|
|
161
|
-
"dev": "npm run schema:local && wrangler dev",
|
|
162
|
-
"deploy": "npm run schema:remote && wrangler deploy"
|
|
163
|
-
},
|
|
164
|
-
dependencies: {
|
|
165
|
-
'@brightsideoy/kama': 'alpha',
|
|
166
|
-
},
|
|
167
|
-
devDependencies: {
|
|
168
|
-
wrangler: '^4.127.1',
|
|
169
|
-
tsx: '^4.19.2',
|
|
170
|
-
},
|
|
171
|
-
}, null, 2)
|
|
172
|
-
);
|
|
335
|
+
const emptyDefs = `{ nodes: {}, trees: {}, blocks: {}, nodeLayouts: {}, blockLayouts: {}, channels: {} }`;
|
|
336
|
+
writeFileSync(join(cmsDir, 'bundled-definitions.js'), `const defs = ${emptyDefs};\nexport const BUNDLED_DEFINITIONS = defs;\nexport default defs;\n`);
|
|
173
337
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
338
|
+
if (isCloudflare) {
|
|
339
|
+
const templateIndexPath = join(__dirname, '../templates/kama-worker/index.js');
|
|
340
|
+
if (existsSync(templateIndexPath)) {
|
|
341
|
+
cpSync(templateIndexPath, join(cmsDir, 'index.js'));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
writeFileSync(
|
|
345
|
+
join(cmsDir, 'package.json'),
|
|
346
|
+
JSON.stringify({
|
|
347
|
+
name: workerName,
|
|
348
|
+
type: 'module',
|
|
349
|
+
version: '1.0.0',
|
|
350
|
+
scripts: {
|
|
351
|
+
"setup:local": `wrangler d1 execute ${dbName} --local --file=../node_modules/@brightsideoy/kama/schema.sql > /dev/null 2>&1 && wrangler d1 execute ${dbName} --local --file=seed.sql > /dev/null 2>&1`,
|
|
352
|
+
"setup:remote": `wrangler d1 execute ${dbName} --remote --file=../node_modules/@brightsideoy/kama/schema.sql && wrangler d1 execute ${dbName} --remote --file=seed.sql`,
|
|
353
|
+
"dev": "wrangler dev --port 8787 --inspector-port 9239",
|
|
354
|
+
"deploy": "npm run setup:remote && wrangler deploy"
|
|
355
|
+
},
|
|
356
|
+
dependencies: {
|
|
357
|
+
'@brightsideoy/kama': currentKamaVersion,
|
|
358
|
+
'@libsql/client': '^0.14.0'
|
|
359
|
+
},
|
|
360
|
+
devDependencies: {
|
|
361
|
+
"wrangler": WRANGLER_LATEST
|
|
362
|
+
},
|
|
363
|
+
}, null, 2)
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
const cmsWranglerConfig = {
|
|
178
367
|
$schema: '../node_modules/wrangler/config-schema.json',
|
|
179
368
|
name: workerName,
|
|
180
369
|
main: 'index.js',
|
|
181
|
-
compatibility_date: '
|
|
370
|
+
compatibility_date: '2024-09-23',
|
|
182
371
|
compatibility_flags: ['nodejs_compat'],
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
},
|
|
187
|
-
images: {
|
|
188
|
-
binding: 'IMAGES'
|
|
189
|
-
},
|
|
190
|
-
routes: [{ pattern: cmsDomain, custom_domain: true }],
|
|
372
|
+
dev: { host: 'localhost', port: 8787 },
|
|
373
|
+
cache: { enabled: true },
|
|
374
|
+
assets: { directory: '../node_modules/@brightsideoy/kama/dist', binding: 'ASSETS' },
|
|
375
|
+
images: { binding: 'IMAGES' },
|
|
191
376
|
vars: {
|
|
192
|
-
CDN_DOMAIN: cmsDomain,
|
|
193
|
-
PUBLIC_FRONTEND_URL: `https://${siteDomain}
|
|
194
|
-
CLOUDFLARE_ZONE_ID: "REPLACE_WITH_YOUR_32_CHAR_ZONE_ID"
|
|
377
|
+
CDN_DOMAIN: cmsDomain || 'localhost:8787',
|
|
378
|
+
PUBLIC_FRONTEND_URL: (shouldCreateSite) ? (siteDomain && siteDomain !== 'localhost:4321' ? `https://${siteDomain}` : 'http://localhost:4321') : 'http://localhost:4321',
|
|
379
|
+
CLOUDFLARE_ZONE_ID: "REPLACE_WITH_YOUR_32_CHAR_ZONE_ID",
|
|
380
|
+
KAMA_RUNTIME: 'cloudflare'
|
|
195
381
|
},
|
|
196
|
-
d1_databases: [{
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}],
|
|
201
|
-
r2_buckets: [{
|
|
202
|
-
binding: 'MEDIA_BUCKET',
|
|
203
|
-
bucket_name: bucketName,
|
|
204
|
-
}],
|
|
205
|
-
}, null, 2)
|
|
206
|
-
);
|
|
382
|
+
d1_databases: [{ binding: 'DB', database_name: dbName, database_id: d1DatabaseId }],
|
|
383
|
+
r2_buckets: [{ binding: 'MEDIA_BUCKET', bucket_name: bucketName }],
|
|
384
|
+
routes: cmsDomain ? [{ pattern: cmsDomain, custom_domain: true }] : [{ pattern: "", custom_domain: true }]
|
|
385
|
+
};
|
|
207
386
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
join(cmsDir, '.dev.vars'),
|
|
211
|
-
|
|
212
|
-
|
|
387
|
+
writeFileSync(join(cmsDir, 'wrangler.json'), JSON.stringify(cmsWranglerConfig, null, 2));
|
|
388
|
+
|
|
389
|
+
writeFileSync(join(cmsDir, '.dev.vars'), `# Cloudflare Edge Cache Purging Secrets\nCLOUDFLARE_API_TOKEN=cfut_YOUR_CUSTOM_PURGE_TOKEN\n\n# Local Development Overrides\nCDN_DOMAIN=\nPUBLIC_FRONTEND_URL=http://localhost:4321\n`);
|
|
390
|
+
} else {
|
|
391
|
+
const templateServerPath = join(__dirname, '../templates/kama-worker/server.js');
|
|
392
|
+
if (existsSync(templateServerPath)) {
|
|
393
|
+
cpSync(templateServerPath, join(cmsDir, 'server.js'));
|
|
394
|
+
}
|
|
213
395
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
396
|
+
writeFileSync(
|
|
397
|
+
join(cmsDir, 'package.json'),
|
|
398
|
+
JSON.stringify({
|
|
399
|
+
name: workerName,
|
|
400
|
+
type: 'module',
|
|
401
|
+
version: '1.0.0',
|
|
402
|
+
scripts: {
|
|
403
|
+
"setup:local": "sqlite3 kama.db < ../node_modules/@brightsideoy/kama/schema.sql && sqlite3 kama.db < seed.sql",
|
|
404
|
+
"dev": "tsx server.js",
|
|
405
|
+
"start": "node server.js"
|
|
406
|
+
},
|
|
407
|
+
dependencies: {
|
|
408
|
+
'@brightsideoy/kama': currentKamaVersion,
|
|
409
|
+
'@hono/node-server': '^1.13.0',
|
|
410
|
+
'@libsql/client': '^0.14.0',
|
|
411
|
+
'sharp': '^0.33.5',
|
|
412
|
+
'dotenv': '^16.4.5'
|
|
413
|
+
},
|
|
414
|
+
devDependencies: {
|
|
415
|
+
"tsx": "^4.19.0"
|
|
416
|
+
},
|
|
417
|
+
}, null, 2)
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
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`);
|
|
421
|
+
}
|
|
217
422
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (
|
|
222
|
-
|
|
423
|
+
s.message('Bundling schemas...');
|
|
424
|
+
await bundleWorkspaceSchemas(rootDir, true);
|
|
425
|
+
|
|
426
|
+
if (shouldCreateSite) {
|
|
427
|
+
s.message(`Scaffolding Astro site (${siteFolderName})...`);
|
|
428
|
+
await generateAstroSite(rootDir, siteFolderName, siteDomain, cmsFullUrl, workerName, isCloudflare);
|
|
223
429
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
430
|
+
|
|
431
|
+
s.stop('Workspace provisioned successfully.');
|
|
432
|
+
|
|
433
|
+
let nextSteps = '';
|
|
434
|
+
let stepCount = 1;
|
|
435
|
+
if (!isCurrentDir) nextSteps += `${stepCount++}. cd ${projName}\n`;
|
|
436
|
+
nextSteps += `${stepCount++}. npm install\n`;
|
|
437
|
+
nextSteps += `${stepCount++}. npm run setup ${pc.dim('<- Run this once to init the database!')}\n`;
|
|
438
|
+
nextSteps += `${stepCount++}. npm run dev\n`;
|
|
439
|
+
|
|
440
|
+
if (isCloudflare) {
|
|
441
|
+
nextSteps += `\n${pc.dim('Cloudflare Note:')}\n`;
|
|
442
|
+
nextSteps += `${pc.dim('Update CLOUDFLARE_ZONE_ID in wrangler.json')}\n`;
|
|
443
|
+
nextSteps += `${pc.dim('Set API token: npx wrangler secret put CLOUDFLARE_API_TOKEN')}`;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
note(nextSteps, 'Next steps');
|
|
447
|
+
outro(`KAMA • ENGINE is ready! 🚀`);
|
|
227
448
|
}
|
|
228
449
|
|
|
229
450
|
/**
|
|
230
|
-
*
|
|
451
|
+
* COMMAND: npx kama add site
|
|
231
452
|
*/
|
|
232
453
|
async function createNewSiteOnly() {
|
|
233
454
|
const rootDir = process.cwd();
|
|
234
455
|
|
|
235
456
|
if (!existsSync(join(rootDir, 'package.json'))) {
|
|
236
|
-
|
|
237
|
-
|
|
457
|
+
cancel('Error: Must run this command inside the root of your workspace.');
|
|
458
|
+
process.exit(1);
|
|
238
459
|
}
|
|
239
460
|
|
|
240
461
|
let targetWorkerName = 'kama-worker';
|
|
241
|
-
|
|
462
|
+
let detectedCmsDomain = '';
|
|
463
|
+
|
|
464
|
+
const cmsWranglerPath = join(rootDir, 'kama-worker', 'wrangler.json');
|
|
242
465
|
if (existsSync(cmsWranglerPath)) {
|
|
243
466
|
try {
|
|
244
467
|
const cmsWrangler = JSON.parse(readFileSync(cmsWranglerPath, 'utf-8'));
|
|
245
468
|
if (cmsWrangler.name) targetWorkerName = cmsWrangler.name;
|
|
469
|
+
if (cmsWrangler.vars?.CDN_DOMAIN) detectedCmsDomain = cmsWrangler.vars.CDN_DOMAIN;
|
|
470
|
+
else if (cmsWrangler.routes?.[0]?.pattern) detectedCmsDomain = cmsWrangler.routes[0].pattern;
|
|
246
471
|
} catch (e) { }
|
|
247
472
|
}
|
|
248
473
|
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
474
|
+
const isCloudflare = handleCancel(await select({
|
|
475
|
+
message: 'Deployment Architecture for this site:',
|
|
476
|
+
options: [
|
|
477
|
+
{ value: true, label: 'Cloudflare Edge' },
|
|
478
|
+
{ value: false, label: 'VPS / Local Server' }
|
|
479
|
+
]
|
|
480
|
+
}));
|
|
481
|
+
|
|
482
|
+
const siteFolder = handleCancel(await text({
|
|
483
|
+
message: 'Astro package/folder name (e.g., my-new-site):',
|
|
484
|
+
placeholder: 'kama-starter',
|
|
485
|
+
defaultValue: 'kama-starter'
|
|
486
|
+
}));
|
|
487
|
+
|
|
488
|
+
let siteDomain = 'localhost:4321';
|
|
489
|
+
let cmsFullUrl = 'http://localhost:8787';
|
|
490
|
+
|
|
491
|
+
if (isCloudflare) {
|
|
492
|
+
siteDomain = handleCancel(await text({
|
|
493
|
+
message: 'Website Domain (e.g., brand.com):',
|
|
494
|
+
defaultValue: ''
|
|
495
|
+
}));
|
|
496
|
+
if (detectedCmsDomain) {
|
|
497
|
+
cmsFullUrl = detectedCmsDomain.startsWith('http') ? detectedCmsDomain : `https://${detectedCmsDomain.replace(/^https?:\/\//, '')}`;
|
|
498
|
+
} else {
|
|
499
|
+
const cmsDomainInput = handleCancel(await text({
|
|
500
|
+
message: 'CMS API Domain (e.g., admin.brand.com):',
|
|
501
|
+
defaultValue: ''
|
|
502
|
+
}));
|
|
503
|
+
cmsFullUrl = `https://${cmsDomainInput.replace(/^https?:\/\//, '')}`;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
252
506
|
|
|
253
|
-
|
|
507
|
+
const s = spinner();
|
|
508
|
+
s.start(`Generating Astro site...`);
|
|
509
|
+
await generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, targetWorkerName, isCloudflare);
|
|
510
|
+
s.stop(`Site created in sites/${siteFolder}!`);
|
|
254
511
|
|
|
255
|
-
|
|
512
|
+
note(`1. npm install\n2. npm run setup ${pc.dim('<- Re-run to register the new site seed data')}`, 'Next steps');
|
|
513
|
+
outro('Site added successfully! 🚀');
|
|
256
514
|
}
|
|
257
515
|
|
|
258
516
|
/**
|
|
259
|
-
*
|
|
517
|
+
* COMMAND: npx kama bundle
|
|
260
518
|
*/
|
|
261
|
-
async function
|
|
262
|
-
|
|
519
|
+
async function bundleWorkspaceSchemas(targetRootDir, silent = false) {
|
|
520
|
+
let rootDir = targetRootDir || process.cwd();
|
|
521
|
+
if (basename(rootDir) === 'kama-worker') rootDir = resolve(rootDir, '..');
|
|
263
522
|
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
523
|
+
const s = spinner();
|
|
524
|
+
if (!silent) s.start('Bundling schemas...');
|
|
525
|
+
try {
|
|
526
|
+
let compilerPath = join(__dirname, '../dist/schema-compiler.js');
|
|
527
|
+
if (!existsSync(compilerPath)) compilerPath = join(__dirname, '../scripts/schema-compiler.ts');
|
|
528
|
+
await import(pathToFileURL(compilerPath).href);
|
|
529
|
+
if (!silent) s.stop('Schemas bundled successfully.');
|
|
530
|
+
} catch (e) {
|
|
531
|
+
if (!silent) s.stop(`Schema compilation failed: ${e.message}`, true);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
269
534
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
scripts: {
|
|
278
|
-
dev: 'astro dev',
|
|
279
|
-
build: 'astro build',
|
|
280
|
-
preview: 'astro preview',
|
|
281
|
-
deploy: 'astro build && wrangler deploy',
|
|
282
|
-
},
|
|
283
|
-
dependencies: {
|
|
284
|
-
'@astrojs/cloudflare': '^14.2.5',
|
|
285
|
-
'@brightsideoy/kama': 'alpha',
|
|
286
|
-
astro: '^7.2.2',
|
|
287
|
-
wrangler: '^4.127.1',
|
|
288
|
-
},
|
|
289
|
-
}, null, 2)
|
|
290
|
-
);
|
|
535
|
+
/**
|
|
536
|
+
* COMMAND: npx kama promote
|
|
537
|
+
*/
|
|
538
|
+
async function promoteLocalToCloudflare() {
|
|
539
|
+
const rootDir = process.cwd();
|
|
540
|
+
const workerDir = join(rootDir, 'kama-worker');
|
|
541
|
+
const rootPkgPath = join(rootDir, 'package.json');
|
|
291
542
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
543
|
+
if (!existsSync(workerDir) || !existsSync(rootPkgPath)) {
|
|
544
|
+
cancel('Error: Could not find kama-worker folder or package.json. Run this command inside your workspace root.');
|
|
545
|
+
process.exit(1);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const s = spinner();
|
|
549
|
+
s.start('Checking Cloudflare authentication...');
|
|
550
|
+
|
|
551
|
+
try {
|
|
552
|
+
execSync('npx wrangler whoami', { stdio: 'ignore' });
|
|
553
|
+
s.stop('Logged in to Cloudflare.');
|
|
554
|
+
} catch (e) {
|
|
555
|
+
s.stop('Not logged in to Cloudflare.', true);
|
|
556
|
+
console.log(pc.dim('Launching browser for Cloudflare login...'));
|
|
557
|
+
try { execSync('npx wrangler login', { stdio: 'ignore' }); }
|
|
558
|
+
catch (loginError) { cancel('Cloudflare login failed.'); process.exit(1); }
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const cmsDomain = handleCancel(await text({
|
|
562
|
+
message: 'CMS Admin Domain (e.g., admin.yourdomain.com):',
|
|
563
|
+
defaultValue: ''
|
|
564
|
+
}));
|
|
565
|
+
|
|
566
|
+
const sitesFolderDir = join(rootDir, 'sites');
|
|
567
|
+
let siteDomain = '';
|
|
568
|
+
let siteDir = null;
|
|
569
|
+
if (existsSync(sitesFolderDir)) {
|
|
570
|
+
const sites = readdirSync(sitesFolderDir);
|
|
571
|
+
if (sites.length > 0) {
|
|
572
|
+
siteDir = join(sitesFolderDir, sites[0]);
|
|
573
|
+
siteDomain = handleCancel(await text({
|
|
574
|
+
message: `Frontend Website Domain for ${sites[0]} (e.g., yourdomain.com):`,
|
|
575
|
+
defaultValue: ''
|
|
576
|
+
}));
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
s.start('Upgrading workspace to Cloudflare Edge...');
|
|
581
|
+
|
|
582
|
+
const projName = basename(rootDir);
|
|
583
|
+
let dbName = `${projName}-db`.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
584
|
+
let d1DatabaseId = '00000000-0000-0000-0000-000000000000';
|
|
585
|
+
|
|
586
|
+
try {
|
|
587
|
+
const d1Output = execSync(`npx wrangler d1 create ${dbName}`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
588
|
+
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}/);
|
|
589
|
+
if (match) d1DatabaseId = match[0];
|
|
590
|
+
} catch (err) { }
|
|
591
|
+
|
|
592
|
+
const rootPkg = JSON.parse(readFileSync(rootPkgPath, 'utf-8'));
|
|
593
|
+
rootPkg.devDependencies = rootPkg.devDependencies || {};
|
|
594
|
+
rootPkg.devDependencies['wrangler'] = WRANGLER_LATEST;
|
|
595
|
+
writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, 2));
|
|
596
|
+
|
|
597
|
+
const workerName = `${projName}-worker`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
598
|
+
const bucketName = `${projName}-media-prod`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
599
|
+
const workerWranglerPath = join(workerDir, 'wrangler.json');
|
|
600
|
+
|
|
601
|
+
let currentConfig = existsSync(workerWranglerPath)
|
|
602
|
+
? JSON.parse(readFileSync(workerWranglerPath, 'utf-8'))
|
|
603
|
+
: {
|
|
604
|
+
$schema: '../node_modules/wrangler/config-schema.json',
|
|
605
|
+
name: workerName,
|
|
606
|
+
main: 'index.js',
|
|
607
|
+
compatibility_date: '2024-09-23',
|
|
299
608
|
compatibility_flags: ['nodejs_compat'],
|
|
300
|
-
assets: {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
609
|
+
assets: { directory: '../node_modules/@brightsideoy/kama/dist', binding: 'ASSETS' },
|
|
610
|
+
images: { binding: 'IMAGES' },
|
|
611
|
+
vars: { CLOUDFLARE_ZONE_ID: "REPLACE_WITH_YOUR_32_CHAR_ZONE_ID", KAMA_RUNTIME: 'cloudflare' }
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
currentConfig.dev = currentConfig.dev || { host: 'localhost', port: 8787 };
|
|
615
|
+
currentConfig.cache = { enabled: true };
|
|
616
|
+
currentConfig.vars.CDN_DOMAIN = cmsDomain;
|
|
617
|
+
if (siteDomain) currentConfig.vars.PUBLIC_FRONTEND_URL = `https://${siteDomain}`;
|
|
618
|
+
currentConfig.routes = [{ pattern: cmsDomain, custom_domain: true }];
|
|
619
|
+
currentConfig.d1_databases = [{ binding: 'DB', database_name: dbName, database_id: d1DatabaseId }];
|
|
620
|
+
currentConfig.r2_buckets = [{ binding: 'MEDIA_BUCKET', bucket_name: bucketName }];
|
|
621
|
+
|
|
622
|
+
writeFileSync(workerWranglerPath, JSON.stringify(currentConfig, null, 2));
|
|
623
|
+
|
|
624
|
+
if (!existsSync(join(workerDir, '.dev.vars'))) {
|
|
625
|
+
writeFileSync(join(workerDir, '.dev.vars'), `CLOUDFLARE_API_TOKEN=cfut_YOUR_CUSTOM_PURGE_TOKEN\nCDN_DOMAIN=\nPUBLIC_FRONTEND_URL=http://localhost:4321\n`);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const workerIndexPath = join(workerDir, 'index.js');
|
|
629
|
+
if (!existsSync(workerIndexPath)) {
|
|
630
|
+
const templateIndexPath = join(__dirname, '../templates/kama-worker/index.js');
|
|
631
|
+
if (existsSync(templateIndexPath)) {
|
|
632
|
+
cpSync(templateIndexPath, workerIndexPath);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
315
635
|
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
},
|
|
328
|
-
build: {
|
|
329
|
-
inlineStylesheets: 'always',
|
|
330
|
-
},
|
|
331
|
-
adapter: cloudflare({
|
|
332
|
-
imageService: 'cloudflare',
|
|
333
|
-
})
|
|
334
|
-
});`;
|
|
335
|
-
writeFileSync(join(siteDir, 'astro.config.mjs'), astroConfig);
|
|
636
|
+
const workerPkgPath = join(workerDir, 'package.json');
|
|
637
|
+
if (existsSync(workerPkgPath)) {
|
|
638
|
+
const workerPkg = JSON.parse(readFileSync(workerPkgPath, 'utf-8'));
|
|
639
|
+
workerPkg.scripts.dev = "wrangler dev --port 8787 --inspector-port 9239";
|
|
640
|
+
workerPkg.scripts.deploy = "npm run setup:remote && wrangler deploy";
|
|
641
|
+
workerPkg.scripts["setup:local"] = `wrangler d1 execute ${dbName} --local --file=../node_modules/@brightsideoy/kama/schema.sql && wrangler d1 execute ${dbName} --local --file=seed.sql`;
|
|
642
|
+
workerPkg.scripts["setup:remote"] = `wrangler d1 execute ${dbName} --remote --file=../node_modules/@brightsideoy/kama/schema.sql && wrangler d1 execute ${dbName} --remote --file=seed.sql`;
|
|
643
|
+
workerPkg.devDependencies = workerPkg.devDependencies || {};
|
|
644
|
+
workerPkg.devDependencies.wrangler = WRANGLER_LATEST;
|
|
645
|
+
writeFileSync(workerPkgPath, JSON.stringify(workerPkg, null, 2));
|
|
646
|
+
}
|
|
336
647
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
648
|
+
if (siteDir && siteDomain) {
|
|
649
|
+
let sessionId = "REPLACE_WITH_YOUR_SESSION_KV_ID";
|
|
650
|
+
let htmlCacheId = "REPLACE_WITH_YOUR_HTML_CACHE_KV_ID";
|
|
651
|
+
const prefix = basename(siteDir).replace(/[^a-zA-Z0-9]/g, '_').toUpperCase();
|
|
652
|
+
|
|
653
|
+
try {
|
|
654
|
+
const sessionOut = execSync(`npx wrangler kv namespace create "SESSION_${prefix}"`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
655
|
+
if (sessionOut.match(/"id":\s*"([a-f0-9]{32})"/)) sessionId = sessionOut.match(/"id":\s*"([a-f0-9]{32})"/)[1];
|
|
656
|
+
const cacheOut = execSync(`npx wrangler kv namespace create "HTML_CACHE_${prefix}"`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
657
|
+
if (cacheOut.match(/"id":\s*"([a-f0-9]{32})"/)) htmlCacheId = cacheOut.match(/"id":\s*"([a-f0-9]{32})"/)[1];
|
|
658
|
+
} catch (err) { }
|
|
659
|
+
|
|
660
|
+
const siteWranglerPath = join(siteDir, 'wrangler.json');
|
|
661
|
+
const siteConfig = {
|
|
662
|
+
$schema: "node_modules/wrangler/config-schema.json",
|
|
663
|
+
name: `${basename(siteDir).toLowerCase().replace(/[^a-z0-9-]/g, '-')}-site`,
|
|
664
|
+
compatibility_date: "2024-09-23",
|
|
665
|
+
compatibility_flags: ["nodejs_compat"],
|
|
666
|
+
dev: { host: 'localhost', port: 4321 },
|
|
667
|
+
cache: { enabled: true },
|
|
668
|
+
assets: { directory: "./dist/client", binding: "ASSETS", run_worker_first: true },
|
|
669
|
+
kv_namespaces: [{ binding: "SESSION", id: sessionId }, { binding: "HTML_CACHE", id: htmlCacheId }],
|
|
670
|
+
services: [{ binding: "CMS_WORKER", service: currentConfig.name }],
|
|
671
|
+
routes: [{ pattern: siteDomain, zone_name: siteDomain, custom_domain: true, enabled: true, previews_enabled: false }],
|
|
672
|
+
vars: { PUBLIC_CMS_URL: `https://${cmsDomain}`, PUBLIC_API_URL: `https://${cmsDomain}` }
|
|
673
|
+
};
|
|
674
|
+
writeFileSync(siteWranglerPath, JSON.stringify(siteConfig, null, 2));
|
|
675
|
+
|
|
676
|
+
const sitePkgPath = join(siteDir, 'package.json');
|
|
677
|
+
if (existsSync(sitePkgPath)) {
|
|
678
|
+
const sitePkg = JSON.parse(readFileSync(sitePkgPath, 'utf-8'));
|
|
679
|
+
sitePkg.dependencies = sitePkg.dependencies || {};
|
|
680
|
+
sitePkg.dependencies['astro'] = ASTRO_LATEST;
|
|
681
|
+
|
|
682
|
+
if (sitePkg.dependencies['@astrojs/node']) delete sitePkg.dependencies['@astrojs/node'];
|
|
683
|
+
sitePkg.dependencies['@astrojs/cloudflare'] = ASTRO_CF_LATEST;
|
|
684
|
+
|
|
685
|
+
sitePkg.devDependencies = sitePkg.devDependencies || {};
|
|
686
|
+
sitePkg.devDependencies['wrangler'] = WRANGLER_LATEST;
|
|
687
|
+
|
|
688
|
+
writeFileSync(sitePkgPath, JSON.stringify(sitePkg, null, 2));
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
convertAstroConfigToCloudflare(siteDir);
|
|
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));
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
s.stop('Workspace successfully upgraded to Cloudflare Edge!');
|
|
703
|
+
outro('Promotion complete. 🚀');
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Helper: Copies the Astro starter template and hydrates runtime tokens
|
|
708
|
+
*/
|
|
709
|
+
async function generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, workerName = 'kama-worker', isCloudflare = false) {
|
|
710
|
+
const siteDir = join(rootDir, 'sites', siteFolder);
|
|
711
|
+
const templateDir = join(__dirname, '../templates/astro-starter');
|
|
712
|
+
|
|
713
|
+
if (!existsSync(templateDir)) {
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
cpSync(templateDir, siteDir, { recursive: true });
|
|
718
|
+
|
|
719
|
+
const tsConfig = {
|
|
720
|
+
extends: "astro/tsconfigs/strict",
|
|
721
|
+
compilerOptions: { baseUrl: ".", paths: { "@/*": ["src/*"] } }
|
|
722
|
+
};
|
|
723
|
+
if (isCloudflare) tsConfig.compilerOptions.types = ["@cloudflare/workers-types"];
|
|
724
|
+
writeFileSync(join(siteDir, 'tsconfig.json'), JSON.stringify(tsConfig, null, 2));
|
|
725
|
+
|
|
726
|
+
const templatePkgPath = join(siteDir, 'package.json.template');
|
|
727
|
+
if (existsSync(templatePkgPath)) {
|
|
728
|
+
const rawContent = readFileSync(templatePkgPath, 'utf-8')
|
|
729
|
+
.replace(/\{\{SITE_NAME\}\}/g, siteFolder)
|
|
730
|
+
.replace(/\{\{KAMA_VERSION\}\}/g, currentKamaVersion);
|
|
731
|
+
|
|
732
|
+
const sitePkg = JSON.parse(rawContent);
|
|
733
|
+
|
|
734
|
+
sitePkg.dependencies = sitePkg.dependencies || {};
|
|
735
|
+
sitePkg.dependencies['astro'] = ASTRO_LATEST;
|
|
736
|
+
|
|
737
|
+
if (!isCloudflare) {
|
|
738
|
+
if (sitePkg.dependencies['@astrojs/cloudflare']) delete sitePkg.dependencies['@astrojs/cloudflare'];
|
|
739
|
+
if (sitePkg.dependencies['wrangler']) delete sitePkg.dependencies['wrangler'];
|
|
740
|
+
if (sitePkg.devDependencies && sitePkg.devDependencies['wrangler']) delete sitePkg.devDependencies['wrangler'];
|
|
741
|
+
|
|
742
|
+
sitePkg.dependencies['@astrojs/node'] = ASTRO_NODE_LATEST;
|
|
743
|
+
} else {
|
|
744
|
+
sitePkg.dependencies['@astrojs/cloudflare'] = ASTRO_CF_LATEST;
|
|
745
|
+
if (sitePkg.dependencies['wrangler']) delete sitePkg.dependencies['wrangler'];
|
|
746
|
+
sitePkg.devDependencies = sitePkg.devDependencies || {};
|
|
747
|
+
sitePkg.devDependencies['wrangler'] = WRANGLER_LATEST;
|
|
748
|
+
|
|
749
|
+
convertAstroConfigToCloudflare(siteDir);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
writeFileSync(join(siteDir, 'package.json'), JSON.stringify(sitePkg, null, 2));
|
|
753
|
+
unlinkSync(templatePkgPath);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const siteLabel = siteFolder.split(/[-_]/).map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
|
|
757
|
+
const safeSiteLabel = siteLabel.replace(/'/g, "''");
|
|
758
|
+
const channelKey = siteFolder.replace(/[^a-z0-9_-]/gi, '-').toLowerCase();
|
|
759
|
+
const homeSlug = `welcome-to-${channelKey}`;
|
|
760
|
+
const treeKey = 'main-tree';
|
|
761
|
+
const safeWorkerName = `${siteFolder.toLowerCase().replace(/[^a-z0-9-]/g, '-')}-site`;
|
|
762
|
+
|
|
763
|
+
if (isCloudflare) {
|
|
764
|
+
let sessionId = "REPLACE_WITH_YOUR_SESSION_KV_ID";
|
|
765
|
+
let htmlCacheId = "REPLACE_WITH_YOUR_HTML_CACHE_KV_ID";
|
|
766
|
+
|
|
767
|
+
try {
|
|
768
|
+
const prefix = siteFolder.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase();
|
|
769
|
+
const sessionOut = execSync(`npx wrangler kv namespace create "SESSION_${prefix}"`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
770
|
+
if (sessionOut.match(/"id":\s*"([a-f0-9]{32})"/)) sessionId = sessionOut.match(/"id":\s*"([a-f0-9]{32})"/)[1];
|
|
771
|
+
const cacheOut = execSync(`npx wrangler kv namespace create "HTML_CACHE_${prefix}"`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
772
|
+
if (cacheOut.match(/"id":\s*"([a-f0-9]{32})"/)) htmlCacheId = cacheOut.match(/"id":\s*"([a-f0-9]{32})"/)[1];
|
|
773
|
+
} catch (err) { }
|
|
774
|
+
|
|
775
|
+
const siteConfig = {
|
|
776
|
+
$schema: "node_modules/wrangler/config-schema.json",
|
|
777
|
+
name: safeWorkerName,
|
|
778
|
+
compatibility_date: "2024-09-23",
|
|
779
|
+
compatibility_flags: ["nodejs_compat"],
|
|
780
|
+
dev: { host: "localhost", port: 4321 },
|
|
781
|
+
cache: { enabled: true },
|
|
782
|
+
assets: { directory: "./dist/client", binding: "ASSETS", run_worker_first: true },
|
|
783
|
+
kv_namespaces: [{ binding: "SESSION", id: sessionId }, { binding: "HTML_CACHE", id: htmlCacheId }],
|
|
784
|
+
services: [{ binding: "CMS_WORKER", service: workerName }],
|
|
785
|
+
vars: { PUBLIC_CMS_URL: cmsFullUrl, PUBLIC_API_URL: cmsFullUrl, PUBLIC_CHANNEL_ID: channelKey, PUBLIC_TREE_ID: treeKey, PUBLIC_SITE_NAME: siteLabel }
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
if (siteDomain !== 'localhost:4321') siteConfig.routes = [{ pattern: siteDomain, custom_domain: true, enabled: true, previews_enabled: false }];
|
|
789
|
+
writeFileSync(join(siteDir, 'wrangler.json'), JSON.stringify(siteConfig, null, 2));
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const siteUrl = (siteDomain === 'localhost:4321' || !isCloudflare) ? `http://${siteDomain}` : `https://${siteDomain}`;
|
|
793
|
+
|
|
794
|
+
const seedSql = `
|
|
795
|
+
-- Seed data for site: ${safeSiteLabel}
|
|
796
|
+
|
|
797
|
+
-- 1. Create content node
|
|
798
|
+
INSERT INTO content_nodes (contentType, title, slug, language, status, hasUnpublishedChanges, disabledRegionsJson, dataJson, draftDataJson, publishedDataJson, publishedAt)
|
|
799
|
+
SELECT 'page', 'Welcome to ${safeSiteLabel}', '${homeSlug}', '', 'published', 0, '{}',
|
|
800
|
+
'{"titleSuffix":"– ${safeSiteLabel}","layoutId":"single-column","subpageLayoutId":"two-column","logoText":"${safeSiteLabel}","schemaType":"WebPage"}',
|
|
801
|
+
'{"titleSuffix":"– ${safeSiteLabel}","layoutId":"single-column","subpageLayoutId":"two-column","logoText":"${safeSiteLabel}","schemaType":"WebPage"}',
|
|
802
|
+
'{"titleSuffix":"– ${safeSiteLabel}","layoutId":"single-column","subpageLayoutId":"two-column","logoText":"${safeSiteLabel}","schemaType":"WebPage"}',
|
|
803
|
+
CURRENT_TIMESTAMP
|
|
804
|
+
WHERE NOT EXISTS (SELECT 1 FROM content_nodes WHERE slug = '${homeSlug}' AND title = 'Welcome to ${safeSiteLabel}');
|
|
805
|
+
|
|
806
|
+
-- 2. Create channel
|
|
807
|
+
INSERT OR IGNORE INTO channels (key, label, channelType, baseUrl, defaultLanguage, isHidden, dataJson)
|
|
808
|
+
VALUES ('${channelKey}', '${safeSiteLabel}', 'website', '${siteUrl}', '', 0, '{}');
|
|
809
|
+
|
|
810
|
+
-- 3. Link channelRoot in channel JSON
|
|
811
|
+
UPDATE channels
|
|
812
|
+
SET dataJson = json_patch(
|
|
813
|
+
COALESCE(NULLIF(dataJson, ''), '{}'),
|
|
814
|
+
json_object(
|
|
815
|
+
'channelRoot', json_object(
|
|
816
|
+
'href', '#node:' || (SELECT id FROM content_nodes WHERE slug = '${homeSlug}' AND title = 'Welcome to ${safeSiteLabel}' ORDER BY id DESC LIMIT 1),
|
|
817
|
+
'kind', 'node',
|
|
818
|
+
'target', '_self',
|
|
819
|
+
'label', 'Welcome to ${safeSiteLabel}',
|
|
820
|
+
'nodeId', (SELECT id FROM content_nodes WHERE slug = '${homeSlug}' AND title = 'Welcome to ${safeSiteLabel}' ORDER BY id DESC LIMIT 1)
|
|
821
|
+
)
|
|
822
|
+
)
|
|
823
|
+
)
|
|
824
|
+
WHERE key = '${channelKey}';
|
|
825
|
+
|
|
826
|
+
-- 4. Create Trees & Relations
|
|
827
|
+
INSERT OR IGNORE INTO trees (key, label, treeType, channelId, isHidden, dataJson)
|
|
828
|
+
VALUES ('${treeKey}', 'Main Tree', 'navigation', (SELECT id FROM channels WHERE key = '${channelKey}'), 0, '{}');
|
|
829
|
+
|
|
830
|
+
INSERT OR IGNORE INTO channel_trees (channelId, treeId, key)
|
|
831
|
+
VALUES ((SELECT id FROM channels WHERE key = '${channelKey}'), (SELECT id FROM trees WHERE key = '${treeKey}'), 'main');
|
|
832
|
+
|
|
833
|
+
INSERT OR IGNORE INTO node_channels (nodeId, channelId)
|
|
834
|
+
VALUES ((SELECT id FROM content_nodes WHERE slug = '${homeSlug}' ORDER BY id DESC LIMIT 1), (SELECT id FROM channels WHERE key = '${channelKey}'));
|
|
835
|
+
|
|
836
|
+
INSERT OR IGNORE INTO tree_nodes (treeId, contentId, parentId, sortOrder)
|
|
837
|
+
VALUES ((SELECT id FROM trees WHERE key = '${treeKey}'), (SELECT id FROM content_nodes WHERE slug = '${homeSlug}' ORDER BY id DESC LIMIT 1), NULL, 0);
|
|
838
|
+
|
|
839
|
+
-- 5. Node Routing (TWO records: Canonical '/' and Redirect '/slug' -> '/')
|
|
840
|
+
INSERT OR IGNORE INTO node_routes (path, channelId, nodeId, treeId, isCanonical, redirectUrl)
|
|
841
|
+
VALUES (
|
|
842
|
+
'/',
|
|
843
|
+
(SELECT id FROM channels WHERE key = '${channelKey}'),
|
|
844
|
+
(SELECT id FROM content_nodes WHERE slug = '${homeSlug}' ORDER BY id DESC LIMIT 1),
|
|
845
|
+
(SELECT id FROM trees WHERE key = '${treeKey}'),
|
|
846
|
+
1,
|
|
847
|
+
NULL
|
|
848
|
+
);
|
|
849
|
+
|
|
850
|
+
INSERT OR IGNORE INTO node_routes (path, channelId, nodeId, treeId, isCanonical, redirectUrl)
|
|
851
|
+
VALUES (
|
|
852
|
+
'/${homeSlug}',
|
|
853
|
+
(SELECT id FROM channels WHERE key = '${channelKey}'),
|
|
854
|
+
(SELECT id FROM content_nodes WHERE slug = '${homeSlug}' ORDER BY id DESC LIMIT 1),
|
|
855
|
+
NULL,
|
|
856
|
+
0,
|
|
857
|
+
'/'
|
|
858
|
+
);
|
|
859
|
+
`;
|
|
860
|
+
|
|
861
|
+
const workerDir = join(rootDir, 'kama-worker');
|
|
862
|
+
if (existsSync(workerDir)) appendFileSync(join(workerDir, 'seed.sql'), seedSql);
|
|
863
|
+
|
|
864
|
+
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_SITE_NAME="${siteLabel.replace(/"/g, '\\"')}"\n`);
|
|
341
865
|
}
|
|
342
866
|
|
|
343
867
|
main().catch(console.error);
|