@brightsideoy/kama 0.1.0-alpha.99 → 1.0.0-alpha.11
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 +738 -261
- package/content/definitions/blocks/button.yaml +34 -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/node-list.yaml +61 -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 +65 -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-BVkEzCNs.js +164 -0
- package/dist/assets/index-CGLx9oqF.css +1 -0
- package/dist/build-seed.js +486 -0
- package/dist/dist-kIJlDMvm.js +27162 -0
- package/dist/index.html +4 -2
- package/dist/index.js +26186 -8576
- package/dist/schema-compiler.js +48 -0
- package/documentation/SCHEMAS.md +182 -0
- package/package.json +24 -25
- package/schema.sql +67 -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 +143 -0
- package/templates/astro-starter/src/components/blocks/ColumnLayout.astro +67 -0
- package/templates/astro-starter/src/components/blocks/HeaderImage.astro +126 -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 +91 -0
- package/templates/astro-starter/src/components/blocks/NodeList.astro +278 -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 +180 -0
- package/templates/astro-starter/src/layouts/SkeletonLayout.astro +56 -0
- package/templates/astro-starter/src/layouts/StandardLayout.astro +63 -0
- package/templates/astro-starter/src/lib/cms.ts +219 -0
- package/templates/astro-starter/src/lib/media/kama-image-service.ts +67 -0
- package/templates/astro-starter/src/lib/media/media-proxy.ts +148 -0
- package/templates/astro-starter/src/lib/media/media-url.ts +112 -0
- package/templates/astro-starter/src/middleware.ts +508 -0
- package/templates/astro-starter/src/pages/404.astro +46 -0
- package/templates/astro-starter/src/pages/[...slug].astro +318 -0
- package/templates/astro-starter/src/pages/api/load-nodes.ts +40 -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,820 @@
|
|
|
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
|
+
import crypto from 'node:crypto'; // 🎯 NEW: Cryptographically secure token generator
|
|
7
|
+
|
|
8
|
+
// 🎯 Modern CLI UI Components
|
|
9
|
+
import { intro, outro, text, select, confirm, spinner, note, isCancel, cancel } from '@clack/prompts';
|
|
10
|
+
|
|
11
|
+
// --- UI HELPERS (Zero Dependency Colors) ---
|
|
12
|
+
const pc = {
|
|
13
|
+
green: (t) => `\x1b[32m${t}\x1b[0m`,
|
|
14
|
+
cyan: (t) => `\x1b[36m${t}\x1b[0m`,
|
|
15
|
+
dim: (t) => `\x1b[2m${t}\x1b[0m`,
|
|
16
|
+
yellow: (t) => `\x1b[33m${t}\x1b[0m`,
|
|
17
|
+
red: (t) => `\x1b[31m${t}\x1b[0m`,
|
|
18
|
+
bgCyan: (t) => `\x1b[46m\x1b[30m ${t} \x1b[0m`,
|
|
19
|
+
bold: (t) => `\x1b[1m${t}\x1b[0m`,
|
|
20
|
+
reset: '\x1b[0m'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Graceful exit handler for Ctrl+C
|
|
24
|
+
function handleCancel(value) {
|
|
25
|
+
if (isCancel(value)) {
|
|
26
|
+
cancel('Operation cancelled.');
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 🎯 Always use the absolute newest ecosystem
|
|
33
|
+
const ASTRO_LATEST = "latest";
|
|
34
|
+
const ASTRO_NODE_LATEST = "latest";
|
|
35
|
+
const ASTRO_CF_LATEST = "latest";
|
|
36
|
+
const WRANGLER_LATEST = "latest";
|
|
37
|
+
|
|
38
|
+
// Dynamically resolve current package version
|
|
39
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
40
|
+
const __dirname = dirname(__filename);
|
|
41
|
+
let currentKamaVersion = 'alpha';
|
|
42
|
+
try {
|
|
43
|
+
const pkgPath = join(__dirname, '../package.json');
|
|
44
|
+
if (existsSync(pkgPath)) {
|
|
45
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
46
|
+
if (pkg.version) currentKamaVersion = `^${pkg.version}`;
|
|
47
|
+
}
|
|
48
|
+
} catch (e) {
|
|
49
|
+
console.warn("⚠️ Could not detect Kama version, defaulting to 'alpha'");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 🎯 SECURE SECRET MANAGER
|
|
54
|
+
* Gracefully retrieves an existing workspace secret, or generates and injects one if missing.
|
|
55
|
+
*/
|
|
56
|
+
function getOrGenerateWorkspaceSecret(workerDir) {
|
|
57
|
+
const envPath = join(workerDir, '.env');
|
|
58
|
+
const devVarsPath = join(workerDir, '.dev.vars');
|
|
59
|
+
|
|
60
|
+
let secret = null;
|
|
61
|
+
|
|
62
|
+
// Check local Node .env
|
|
63
|
+
if (existsSync(envPath)) {
|
|
64
|
+
const match = readFileSync(envPath, 'utf-8').match(/^FRONTEND_API_SECRET=["']?([^"'\n]+)/m);
|
|
65
|
+
if (match) secret = match[1];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check Cloudflare .dev.vars
|
|
69
|
+
if (!secret && existsSync(devVarsPath)) {
|
|
70
|
+
const match = readFileSync(devVarsPath, 'utf-8').match(/^FRONTEND_API_SECRET=["']?([^"'\n]+)/m);
|
|
71
|
+
if (match) secret = match[1];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Generate and inject if missing
|
|
75
|
+
if (!secret) {
|
|
76
|
+
secret = crypto.randomBytes(32).toString('hex');
|
|
77
|
+
const secretLine = `\n# KAMA Auto-Generated Server-to-Server Security\nFRONTEND_API_SECRET="${secret}"\n`;
|
|
78
|
+
|
|
79
|
+
if (existsSync(devVarsPath)) {
|
|
80
|
+
appendFileSync(devVarsPath, secretLine);
|
|
81
|
+
} else if (existsSync(envPath)) {
|
|
82
|
+
appendFileSync(envPath, secretLine);
|
|
83
|
+
} else {
|
|
84
|
+
writeFileSync(devVarsPath, secretLine);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return secret;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 🎯 BULLETPROOF WRANGLER EXECUTION RESOLVER
|
|
93
|
+
*/
|
|
94
|
+
function getWranglerCmd() {
|
|
95
|
+
try {
|
|
96
|
+
execSync('wrangler --version', { stdio: 'ignore' });
|
|
97
|
+
return 'wrangler';
|
|
98
|
+
} catch (e) {
|
|
99
|
+
return 'npx --yes --package=wrangler wrangler';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 🎯 ROBUST WRANGLER RUNNER
|
|
105
|
+
*/
|
|
106
|
+
function runWrangler(cmd, options = {}) {
|
|
107
|
+
const baseWrangler = getWranglerCmd();
|
|
108
|
+
try {
|
|
109
|
+
return execSync(`${baseWrangler} ${cmd}`, {
|
|
110
|
+
encoding: 'utf-8',
|
|
111
|
+
stdio: options.stdio || ['ignore', 'pipe', 'pipe'],
|
|
112
|
+
env: {
|
|
113
|
+
...process.env,
|
|
114
|
+
PATH: process.env.PATH
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
} catch (e) {
|
|
118
|
+
if (options.throw) throw e;
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 🎯 BULLETPROOF KV PROVISIONING ENGINE
|
|
125
|
+
*/
|
|
126
|
+
function getOrCreateKV(title) {
|
|
127
|
+
const out = runWrangler(`kv namespace create "${title}"`);
|
|
128
|
+
if (out) {
|
|
129
|
+
const match = out.match(/"id":\s*"([a-fA-F0-9]{32})"/);
|
|
130
|
+
if (match) return match[1];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const listOut = runWrangler(`kv namespace list`);
|
|
134
|
+
if (listOut) {
|
|
135
|
+
try {
|
|
136
|
+
const namespaces = JSON.parse(listOut);
|
|
137
|
+
const found = namespaces.find(n => n.title === title);
|
|
138
|
+
if (found) return found.id;
|
|
139
|
+
} catch (err) { }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return `REPLACE_WITH_${title}_ID`;
|
|
143
|
+
}
|
|
6
144
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
145
|
+
function showHelp() {
|
|
146
|
+
console.log(`\n${pc.bgCyan(' KAMA • ENGINE CLI ')}\n`);
|
|
147
|
+
console.log('Usage: npx kama <command> [options]\n');
|
|
148
|
+
console.log(pc.cyan('Commands:'));
|
|
149
|
+
console.log(' init Kickstart a new Monorepo (CMS Worker + Optional Astro Site)');
|
|
150
|
+
console.log(' add site Add a new Astro Frontend Site to the current workspace (sites/)');
|
|
151
|
+
console.log(' bundle Compile and bundle YAML schemas into bundled-definitions.js for Edge deployment');
|
|
152
|
+
console.log(' promote Promote the current Local workspace to Cloudflare Edge');
|
|
153
|
+
console.log(' help, -h Show this help message\n');
|
|
154
|
+
console.log(pc.cyan('Workspace Scripts (Run from project root):'));
|
|
155
|
+
console.log(' npm update Update to latest KAMA version');
|
|
156
|
+
console.log(' npm run setup Initialize database schema (Run Once)');
|
|
157
|
+
console.log(' npm run seed -w kama-worker Seed the database with mock test data'); // 🎯 NEW LINE
|
|
158
|
+
console.log(' npm run dev Boot up the CMS backend and Astro frontend concurrently');
|
|
159
|
+
console.log(' npm run deploy:cms Deploy the backend worker and database to Cloudflare');
|
|
160
|
+
console.log(' npm run deploy:site Deploy the Astro frontend site to Cloudflare\n');
|
|
161
|
+
}
|
|
11
162
|
|
|
12
163
|
async function main() {
|
|
13
|
-
|
|
164
|
+
const args = process.argv.slice(2);
|
|
165
|
+
const command = args[0]?.toLowerCase();
|
|
166
|
+
const subCommand = args[1]?.toLowerCase();
|
|
14
167
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
168
|
+
if (['help', '--help', '-h'].includes(command)) {
|
|
169
|
+
showHelp();
|
|
170
|
+
process.exit(0);
|
|
171
|
+
}
|
|
18
172
|
|
|
19
|
-
|
|
173
|
+
console.clear();
|
|
174
|
+
intro(`${pc.bgCyan(' KAMA • ENGINE ')} ${pc.dim('v' + currentKamaVersion.replace('^', ''))}`);
|
|
20
175
|
|
|
21
|
-
if (
|
|
176
|
+
if (command === 'init') {
|
|
177
|
+
await createFullWorkspace();
|
|
178
|
+
} else if (command === 'add' && subCommand === 'site') {
|
|
22
179
|
await createNewSiteOnly();
|
|
180
|
+
} else if (command === 'promote') {
|
|
181
|
+
await promoteLocalToCloudflare();
|
|
182
|
+
} else if (command === 'bundle') {
|
|
183
|
+
await bundleWorkspaceSchemas();
|
|
23
184
|
} else {
|
|
24
|
-
await
|
|
185
|
+
const choice = handleCancel(await select({
|
|
186
|
+
message: 'What would you like to do?',
|
|
187
|
+
options: [
|
|
188
|
+
{ value: 'init', label: 'Init', hint: 'Kickstart new Workspace (CMS + Optional Site)' },
|
|
189
|
+
{ value: 'add_site', label: 'Add Site', hint: 'Add Astro Site to current workspace (sites/)' },
|
|
190
|
+
{ value: 'bundle', label: 'Bundle', hint: 'Compile YAML schemas for Edge deployment' },
|
|
191
|
+
{ value: 'promote', label: 'Promote', hint: 'Promote current Local workspace to Cloudflare Edge' },
|
|
192
|
+
{ value: 'help', label: 'Help', hint: 'Show all commands and usage' },
|
|
193
|
+
{ value: 'exit', label: 'Exit' }
|
|
194
|
+
]
|
|
195
|
+
}));
|
|
196
|
+
|
|
197
|
+
if (choice === 'help') {
|
|
198
|
+
showHelp();
|
|
199
|
+
process.exit(0);
|
|
200
|
+
} else if (choice === 'promote') {
|
|
201
|
+
await promoteLocalToCloudflare();
|
|
202
|
+
} else if (choice === 'bundle') {
|
|
203
|
+
await bundleWorkspaceSchemas();
|
|
204
|
+
} else if (choice === 'add_site') {
|
|
205
|
+
await createNewSiteOnly();
|
|
206
|
+
} else if (choice === 'init') {
|
|
207
|
+
await createFullWorkspace();
|
|
208
|
+
} else {
|
|
209
|
+
outro('Goodbye!');
|
|
210
|
+
process.exit(0);
|
|
211
|
+
}
|
|
25
212
|
}
|
|
213
|
+
}
|
|
26
214
|
|
|
27
|
-
|
|
215
|
+
function convertAstroConfigToCloudflare(siteDir) {
|
|
216
|
+
const configPath = join(siteDir, 'astro.config.mjs');
|
|
217
|
+
if (existsSync(configPath)) {
|
|
218
|
+
let content = readFileSync(configPath, 'utf-8');
|
|
219
|
+
content = content
|
|
220
|
+
.replace("import node from '@astrojs/node';", "import cloudflare from '@astrojs/cloudflare';")
|
|
221
|
+
.replace('adapter: node({ mode: "standalone" })', 'adapter: cloudflare({ imageService: process.env.NODE_ENV === "production" ? "cloudflare" : "passthrough" })');
|
|
222
|
+
writeFileSync(configPath, content, 'utf-8');
|
|
223
|
+
}
|
|
28
224
|
}
|
|
29
225
|
|
|
30
226
|
/**
|
|
31
|
-
*
|
|
227
|
+
* COMMAND: npx kama init
|
|
32
228
|
*/
|
|
33
229
|
async function createFullWorkspace() {
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
230
|
+
const cwdName = basename(process.cwd());
|
|
231
|
+
const hasExistingPkg = existsSync(join(process.cwd(), 'package.json'));
|
|
232
|
+
const defaultDirPrompt = hasExistingPkg ? '.' : cwdName;
|
|
233
|
+
|
|
234
|
+
const inputName = handleCancel(await text({
|
|
235
|
+
message: 'Project folder name (use "." for current dir):',
|
|
236
|
+
placeholder: defaultDirPrompt,
|
|
237
|
+
defaultValue: defaultDirPrompt
|
|
238
|
+
}));
|
|
239
|
+
|
|
240
|
+
const chosenFolder = inputName.trim() || defaultDirPrompt;
|
|
241
|
+
const isCurrentDir = chosenFolder === '.';
|
|
242
|
+
const projName = isCurrentDir ? cwdName : chosenFolder;
|
|
243
|
+
const rootDir = isCurrentDir ? process.cwd() : join(process.cwd(), projName);
|
|
244
|
+
|
|
245
|
+
const isCloudflare = handleCancel(await select({
|
|
246
|
+
message: 'Choose your Deployment Architecture:',
|
|
247
|
+
options: [
|
|
248
|
+
{ value: true, label: 'Cloudflare Edge', hint: 'Workers, D1, R2, CF Image Resizing' },
|
|
249
|
+
{ value: false, label: 'VPS / Local Server', hint: 'Node.js, SQLite, Sharp cropping' }
|
|
250
|
+
]
|
|
251
|
+
}));
|
|
252
|
+
|
|
253
|
+
const shouldCreateSite = handleCancel(await confirm({
|
|
254
|
+
message: 'Do you want to scaffold an initial Astro frontend site?',
|
|
255
|
+
initialValue: true
|
|
256
|
+
}));
|
|
257
|
+
|
|
258
|
+
let siteFolderName = 'kama-starter';
|
|
259
|
+
let siteDomain = 'localhost:4321';
|
|
260
|
+
let cmsDomain = 'localhost:8787';
|
|
261
|
+
|
|
262
|
+
if (shouldCreateSite) {
|
|
263
|
+
siteFolderName = handleCancel(await text({
|
|
264
|
+
message: 'Astro package/folder name (e.g., my-website.eu):',
|
|
265
|
+
placeholder: 'kama-starter',
|
|
266
|
+
defaultValue: 'kama-starter'
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (isCloudflare) {
|
|
271
|
+
cmsDomain = handleCancel(await text({
|
|
272
|
+
message: 'CMS Admin Domain (e.g., admin.yourdomain.com):',
|
|
273
|
+
defaultValue: ''
|
|
274
|
+
}));
|
|
275
|
+
if (shouldCreateSite) {
|
|
276
|
+
siteDomain = handleCancel(await text({
|
|
277
|
+
message: 'First Website Domain (e.g., yourdomain.com):',
|
|
278
|
+
defaultValue: ''
|
|
279
|
+
}));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const cmsProtocol = isCloudflare ? 'https://' : 'http://';
|
|
284
|
+
const cmsFullUrl = cmsDomain ? `${cmsProtocol}${cmsDomain.replace(/^https?:\/\//, '')}` : 'http://localhost:8787';
|
|
37
285
|
|
|
38
|
-
|
|
286
|
+
// 🎯 INITIALIZE UNIQUE SECURE API SECRET
|
|
287
|
+
const frontendApiSecret = crypto.randomBytes(32).toString('hex');
|
|
39
288
|
|
|
40
|
-
// 🎯 Derive dynamic worker, database, and bucket names from project folder name
|
|
41
289
|
const workerName = `${projName}-worker`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
42
290
|
let dbName = `${projName}-db`.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
43
291
|
const bucketName = `${projName}-media-prod`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
44
292
|
|
|
45
|
-
|
|
46
|
-
|
|
293
|
+
const s = spinner();
|
|
294
|
+
s.start('Provisioning workspace...');
|
|
295
|
+
|
|
296
|
+
if (!isCurrentDir) mkdirSync(rootDir, { recursive: true });
|
|
297
|
+
|
|
298
|
+
writeFileSync(
|
|
299
|
+
join(rootDir, '.gitignore'),
|
|
300
|
+
`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`
|
|
301
|
+
);
|
|
47
302
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
303
|
+
const sourceDefsDir = join(__dirname, '../content/definitions');
|
|
304
|
+
const targetDefsDir = join(rootDir, 'content', 'definitions');
|
|
305
|
+
if (existsSync(sourceDefsDir)) cpSync(sourceDefsDir, targetDefsDir, { recursive: true });
|
|
51
306
|
|
|
52
|
-
|
|
53
|
-
let
|
|
307
|
+
let d1DatabaseId = '00000000-0000-0000-0000-000000000000';
|
|
308
|
+
let backendCacheId = 'REPLACE_WITH_CACHE_KV_ID';
|
|
54
309
|
let isAuthenticated = false;
|
|
55
310
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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...`);
|
|
64
|
-
try {
|
|
65
|
-
execSync('npx wrangler login', { stdio: 'inherit' });
|
|
311
|
+
if (isCloudflare) {
|
|
312
|
+
s.message('Checking Cloudflare authentication...');
|
|
313
|
+
const whoami = runWrangler('whoami');
|
|
314
|
+
if (whoami && (whoami.includes('Getting User Settings') || whoami.includes('You are logged in'))) {
|
|
66
315
|
isAuthenticated = true;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
console.log(
|
|
316
|
+
} else {
|
|
317
|
+
s.stop('Not authenticated with Cloudflare.', true);
|
|
318
|
+
console.log(pc.dim('Launching browser for Cloudflare login...'));
|
|
319
|
+
const login = runWrangler('login', { stdio: 'inherit' });
|
|
320
|
+
if (login !== null) isAuthenticated = true;
|
|
321
|
+
s.start('Resuming provisioning...');
|
|
70
322
|
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (isAuthenticated) {
|
|
74
|
-
try {
|
|
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
323
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
324
|
+
if (isAuthenticated) {
|
|
325
|
+
s.message('Provisioning backend KV Cache...');
|
|
326
|
+
backendCacheId = getOrCreateKV(`CACHE_KV_${workerName.toUpperCase().replace(/-/g, '_')}`);
|
|
327
|
+
|
|
328
|
+
s.message('Checking existing Cloudflare D1 databases...');
|
|
329
|
+
const listOutput = runWrangler('d1 list --json');
|
|
330
|
+
if (listOutput) {
|
|
331
|
+
try {
|
|
332
|
+
const jsonStart = listOutput.indexOf('[');
|
|
333
|
+
const jsonEnd = listOutput.lastIndexOf(']') + 1;
|
|
334
|
+
let databases = [];
|
|
335
|
+
|
|
336
|
+
if (jsonStart !== -1 && jsonEnd !== -1) databases = JSON.parse(listOutput.substring(jsonStart, jsonEnd));
|
|
337
|
+
const existingDb = databases.find(db => db.name === dbName);
|
|
338
|
+
|
|
339
|
+
if (existingDb) {
|
|
340
|
+
s.stop(`Database '${dbName}' already exists on your account.`);
|
|
341
|
+
const useExisting = handleCancel(await confirm({ message: 'Do you want to link this existing database?', initialValue: true }));
|
|
342
|
+
|
|
343
|
+
if (useExisting) {
|
|
344
|
+
d1DatabaseId = existingDb.uuid;
|
|
345
|
+
s.start(`Linked existing database ID: ${d1DatabaseId}`);
|
|
346
|
+
} else {
|
|
347
|
+
dbName = handleCancel(await text({ message: 'Enter a new database name:' }));
|
|
348
|
+
s.start(`Creating new database '${dbName}'...`);
|
|
349
|
+
const d1Output = runWrangler(`d1 create ${dbName}`);
|
|
350
|
+
const match = d1Output ? 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}/) : null;
|
|
351
|
+
if (match) d1DatabaseId = match[0];
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
s.message(`Attempting to auto-create Cloudflare D1 Database '${dbName}'...`);
|
|
355
|
+
const d1Output = runWrangler(`d1 create ${dbName}`);
|
|
356
|
+
const match = d1Output ? 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}/) : null;
|
|
357
|
+
if (match) d1DatabaseId = match[0];
|
|
103
358
|
}
|
|
104
|
-
}
|
|
105
|
-
} else {
|
|
106
|
-
console.log(`\n🗄️ Attempting to auto-create Cloudflare D1 Database '${dbName}'...`);
|
|
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
|
-
}
|
|
359
|
+
} catch (error) { }
|
|
113
360
|
}
|
|
114
|
-
} catch (error) {
|
|
115
|
-
console.log(`⚠️ Database operation failed. We'll leave a placeholder.`);
|
|
116
361
|
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
s.message('Writing configuration files...');
|
|
365
|
+
|
|
366
|
+
const rootPkg = {
|
|
367
|
+
name: projName,
|
|
368
|
+
private: true,
|
|
369
|
+
type: 'module',
|
|
370
|
+
workspaces: ['kama-worker', 'sites/*'],
|
|
371
|
+
scripts: { 'setup': 'npm run setup:local -w kama-worker' },
|
|
372
|
+
dependencies: { '@brightsideoy/kama': currentKamaVersion },
|
|
373
|
+
devDependencies: {}
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
if (isCloudflare) rootPkg.devDependencies['wrangler'] = WRANGLER_LATEST;
|
|
377
|
+
|
|
378
|
+
if (shouldCreateSite) {
|
|
379
|
+
rootPkg.devDependencies['concurrently'] = '^9.0.0';
|
|
380
|
+
rootPkg.scripts['dev:site'] = `npm run dev -w sites/${siteFolderName}`;
|
|
381
|
+
rootPkg.scripts['predev'] = 'npx kama bundle';
|
|
382
|
+
rootPkg.scripts['dev'] = 'npx concurrently "npm run dev -w kama-worker" "npm run dev -w sites/' + siteFolderName + '"';
|
|
383
|
+
rootPkg.scripts['deploy:site'] = `npm run deploy -w sites/${siteFolderName}`;
|
|
384
|
+
rootPkg.scripts['deploy:cms'] = `npm run deploy -w kama-worker`;
|
|
117
385
|
} else {
|
|
118
|
-
|
|
386
|
+
rootPkg.scripts['predev'] = 'npx kama bundle';
|
|
387
|
+
rootPkg.scripts['dev'] = 'npm run dev -w kama-worker';
|
|
388
|
+
rootPkg.scripts['deploy:cms'] = `npm run deploy -w kama-worker`;
|
|
119
389
|
}
|
|
120
|
-
// ------------------------------
|
|
121
390
|
|
|
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
|
-
);
|
|
391
|
+
writeFileSync(join(rootDir, 'package.json'), JSON.stringify(rootPkg, null, 2));
|
|
140
392
|
|
|
141
|
-
|
|
142
|
-
console.log(`\n☁️ Configuring CMS Worker '${workerName}' (${cmsDomain})...`);
|
|
143
|
-
const cmsDir = join(rootDir, 'cms-worker');
|
|
393
|
+
const cmsDir = join(rootDir, 'kama-worker');
|
|
144
394
|
mkdirSync(cmsDir, { recursive: true });
|
|
145
395
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
wrangler:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
// 🎯 Fixed CMS wrangler.json (Added PUBLIC_FRONTEND_URL & CLOUDFLARE_ZONE_ID to public vars)
|
|
175
|
-
writeFileSync(
|
|
176
|
-
join(cmsDir, 'wrangler.json'),
|
|
177
|
-
JSON.stringify({
|
|
396
|
+
const emptyDefs = `{ nodes: {}, trees: {}, blocks: {}, nodeLayouts: {}, blockLayouts: {}, channels: {} }`;
|
|
397
|
+
writeFileSync(join(cmsDir, 'bundled-definitions.js'), `const defs = ${emptyDefs};\nexport const BUNDLED_DEFINITIONS = defs;\nexport default defs;\n`);
|
|
398
|
+
|
|
399
|
+
if (isCloudflare) {
|
|
400
|
+
const templateIndexPath = join(__dirname, '../templates/kama-worker/index.js');
|
|
401
|
+
if (existsSync(templateIndexPath)) cpSync(templateIndexPath, join(cmsDir, 'index.js'));
|
|
402
|
+
|
|
403
|
+
writeFileSync(
|
|
404
|
+
join(cmsDir, 'package.json'),
|
|
405
|
+
JSON.stringify({
|
|
406
|
+
name: workerName,
|
|
407
|
+
type: 'module',
|
|
408
|
+
version: '1.0.0',
|
|
409
|
+
scripts: {
|
|
410
|
+
// 🎯 Uses native node with the compiled dist/build-seed.js
|
|
411
|
+
"seed": "node ../node_modules/@brightsideoy/kama/dist/build-seed.js && wrangler d1 execute " + dbName + " --local --file=seed.sql",
|
|
412
|
+
"setup:local": `wrangler d1 execute ${dbName} --local --file=../node_modules/@brightsideoy/kama/schema.sql > /dev/null 2>&1`,
|
|
413
|
+
"setup:remote": `wrangler d1 execute ${dbName} --remote --file=../node_modules/@brightsideoy/kama/schema.sql`,
|
|
414
|
+
"dev": "wrangler dev --port 8787 --inspector-port 9239",
|
|
415
|
+
"deploy": "npm run setup:remote && wrangler deploy"
|
|
416
|
+
},
|
|
417
|
+
dependencies: { '@brightsideoy/kama': currentKamaVersion, '@libsql/client': '^0.14.0' },
|
|
418
|
+
devDependencies: { "wrangler": WRANGLER_LATEST }, // 🎯 Removed TSX requirement
|
|
419
|
+
}, null, 2)
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
const cmsWranglerConfig = {
|
|
178
423
|
$schema: '../node_modules/wrangler/config-schema.json',
|
|
179
424
|
name: workerName,
|
|
180
425
|
main: 'index.js',
|
|
181
|
-
compatibility_date: '
|
|
426
|
+
compatibility_date: '2024-09-23',
|
|
182
427
|
compatibility_flags: ['nodejs_compat'],
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
},
|
|
187
|
-
images: {
|
|
188
|
-
binding: 'IMAGES'
|
|
189
|
-
},
|
|
190
|
-
routes: [{ pattern: cmsDomain, custom_domain: true }],
|
|
428
|
+
dev: { host: 'localhost', port: 8787 },
|
|
429
|
+
assets: { directory: '../node_modules/@brightsideoy/kama/dist', binding: 'ASSETS' },
|
|
430
|
+
images: { binding: 'IMAGES' },
|
|
431
|
+
kv_namespaces: [{ binding: 'CACHE_KV', id: backendCacheId }],
|
|
191
432
|
vars: {
|
|
192
|
-
CDN_DOMAIN: cmsDomain,
|
|
193
|
-
PUBLIC_FRONTEND_URL: `https://${siteDomain}
|
|
194
|
-
CLOUDFLARE_ZONE_ID: "REPLACE_WITH_YOUR_32_CHAR_ZONE_ID"
|
|
433
|
+
CDN_DOMAIN: cmsDomain || 'localhost:8787',
|
|
434
|
+
PUBLIC_FRONTEND_URL: (shouldCreateSite) ? (siteDomain && siteDomain !== 'localhost:4321' ? `https://${siteDomain}` : 'http://localhost:4321') : 'http://localhost:4321',
|
|
435
|
+
CLOUDFLARE_ZONE_ID: "REPLACE_WITH_YOUR_32_CHAR_ZONE_ID",
|
|
436
|
+
KAMA_RUNTIME: 'cloudflare'
|
|
195
437
|
},
|
|
196
|
-
d1_databases: [{
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}],
|
|
201
|
-
r2_buckets: [{
|
|
202
|
-
binding: 'MEDIA_BUCKET',
|
|
203
|
-
bucket_name: bucketName,
|
|
204
|
-
}],
|
|
205
|
-
}, null, 2)
|
|
206
|
-
);
|
|
438
|
+
d1_databases: [{ binding: 'DB', database_name: dbName, database_id: d1DatabaseId }],
|
|
439
|
+
r2_buckets: [{ binding: 'MEDIA_BUCKET', bucket_name: bucketName }],
|
|
440
|
+
routes: cmsDomain ? [{ pattern: cmsDomain, custom_domain: true }] : [{ pattern: "", custom_domain: true }]
|
|
441
|
+
};
|
|
207
442
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
443
|
+
writeFileSync(join(cmsDir, 'wrangler.json'), JSON.stringify(cmsWranglerConfig, null, 2));
|
|
444
|
+
|
|
445
|
+
// 🎯 Inject API Secret directly into .dev.vars
|
|
446
|
+
const publicFrontendUrl = (shouldCreateSite) ? (siteDomain && siteDomain !== 'localhost:4321' ? `https://${siteDomain}` : 'http://localhost:4321') : 'http://localhost:4321';
|
|
447
|
+
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=${publicFrontendUrl}\n\n# Server-to-Server API Security\nFRONTEND_API_SECRET="${frontendApiSecret}"\n`);
|
|
448
|
+
} else {
|
|
449
|
+
const templateServerPath = join(__dirname, '../templates/kama-worker/server.js');
|
|
450
|
+
if (existsSync(templateServerPath)) cpSync(templateServerPath, join(cmsDir, 'server.js'));
|
|
451
|
+
|
|
452
|
+
writeFileSync(
|
|
453
|
+
join(cmsDir, 'package.json'),
|
|
454
|
+
JSON.stringify({
|
|
455
|
+
name: workerName,
|
|
456
|
+
type: 'module',
|
|
457
|
+
version: '1.0.0',
|
|
458
|
+
scripts: {
|
|
459
|
+
// 🎯 Uses native node with the compiled dist/build-seed.js
|
|
460
|
+
"seed": "node ../node_modules/@brightsideoy/kama/dist/build-seed.js && sqlite3 kama.db < seed.sql",
|
|
461
|
+
"setup:local": "sqlite3 kama.db < ../node_modules/@brightsideoy/kama/schema.sql",
|
|
462
|
+
"dev": "tsx server.js",
|
|
463
|
+
"start": "node server.js"
|
|
464
|
+
},
|
|
465
|
+
dependencies: { '@brightsideoy/kama': currentKamaVersion, '@hono/node-server': '^1.13.0', '@libsql/client': '^0.14.0', 'sharp': '^0.33.5', 'dotenv': '^16.4.5' },
|
|
466
|
+
devDependencies: { "tsx": "^4.19.0" }, // 🎯 Kept because "dev" uses it
|
|
467
|
+
}, null, 2)
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
// 🎯 Inject API Secret directly into .env
|
|
471
|
+
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\n# Server-to-Server API Security\nFRONTEND_API_SECRET="${frontendApiSecret}"\n`);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
s.message('Bundling schemas...');
|
|
475
|
+
await bundleWorkspaceSchemas(rootDir, true);
|
|
213
476
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
477
|
+
if (shouldCreateSite) {
|
|
478
|
+
s.message(`Scaffolding Astro site (${siteFolderName})...`);
|
|
479
|
+
// 🎯 Pass secret to the frontend generator
|
|
480
|
+
await generateAstroSite(rootDir, siteFolderName, siteDomain, cmsFullUrl, workerName, isCloudflare, frontendApiSecret);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
s.stop('Workspace provisioned successfully.');
|
|
484
|
+
|
|
485
|
+
let nextSteps = '';
|
|
486
|
+
let stepCount = 1;
|
|
487
|
+
if (!isCurrentDir) nextSteps += `${stepCount++}. cd ${projName}\n`;
|
|
488
|
+
nextSteps += `${stepCount++}. npm install\n`;
|
|
489
|
+
nextSteps += `${stepCount++}. npm run setup ${pc.dim('<- Run this once to init the database schema!')}\n`;
|
|
490
|
+
nextSteps += `${stepCount++}. npm run dev\n`;
|
|
217
491
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
492
|
+
if (isCloudflare) {
|
|
493
|
+
nextSteps += `\n${pc.dim('Cloudflare Note:')}\n`;
|
|
494
|
+
nextSteps += `${pc.dim('Update CLOUDFLARE_ZONE_ID in wrangler.json')}\n`;
|
|
495
|
+
nextSteps += `${pc.dim('Set Cache purge token: npx wrangler secret put CLOUDFLARE_API_TOKEN')}\n`;
|
|
496
|
+
nextSteps += `${pc.dim('Set Frontend API secret: npx wrangler secret put FRONTEND_API_SECRET')}`;
|
|
223
497
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
498
|
+
|
|
499
|
+
note(nextSteps, 'Next steps');
|
|
500
|
+
outro(`KAMA • ENGINE is ready! 🚀`);
|
|
227
501
|
}
|
|
228
502
|
|
|
229
503
|
/**
|
|
230
|
-
*
|
|
504
|
+
* COMMAND: npx kama add site
|
|
231
505
|
*/
|
|
232
506
|
async function createNewSiteOnly() {
|
|
233
507
|
const rootDir = process.cwd();
|
|
234
508
|
|
|
235
509
|
if (!existsSync(join(rootDir, 'package.json'))) {
|
|
236
|
-
|
|
237
|
-
|
|
510
|
+
cancel('Error: Must run this command inside the root of your workspace.');
|
|
511
|
+
process.exit(1);
|
|
238
512
|
}
|
|
239
513
|
|
|
240
514
|
let targetWorkerName = 'kama-worker';
|
|
241
|
-
|
|
515
|
+
let detectedCmsDomain = '';
|
|
516
|
+
const workerDir = join(rootDir, 'kama-worker');
|
|
517
|
+
|
|
518
|
+
const cmsWranglerPath = join(workerDir, 'wrangler.json');
|
|
242
519
|
if (existsSync(cmsWranglerPath)) {
|
|
243
520
|
try {
|
|
244
521
|
const cmsWrangler = JSON.parse(readFileSync(cmsWranglerPath, 'utf-8'));
|
|
245
522
|
if (cmsWrangler.name) targetWorkerName = cmsWrangler.name;
|
|
523
|
+
if (cmsWrangler.vars?.CDN_DOMAIN) detectedCmsDomain = cmsWrangler.vars.CDN_DOMAIN;
|
|
524
|
+
else if (cmsWrangler.routes?.[0]?.pattern) detectedCmsDomain = cmsWrangler.routes[0].pattern;
|
|
246
525
|
} catch (e) { }
|
|
247
526
|
}
|
|
248
527
|
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
528
|
+
// 🎯 Ensure secret exists in worker before provisioning site
|
|
529
|
+
const frontendApiSecret = existsSync(workerDir) ? getOrGenerateWorkspaceSecret(workerDir) : crypto.randomBytes(32).toString('hex');
|
|
530
|
+
|
|
531
|
+
const isCloudflare = handleCancel(await select({
|
|
532
|
+
message: 'Deployment Architecture for this site:',
|
|
533
|
+
options: [
|
|
534
|
+
{ value: true, label: 'Cloudflare Edge' },
|
|
535
|
+
{ value: false, label: 'VPS / Local Server' }
|
|
536
|
+
]
|
|
537
|
+
}));
|
|
538
|
+
|
|
539
|
+
const siteFolder = handleCancel(await text({
|
|
540
|
+
message: 'Astro package/folder name (e.g., my-new-site):',
|
|
541
|
+
placeholder: 'kama-starter',
|
|
542
|
+
defaultValue: 'kama-starter'
|
|
543
|
+
}));
|
|
544
|
+
|
|
545
|
+
let siteDomain = 'localhost:4321';
|
|
546
|
+
let cmsFullUrl = 'http://localhost:8787';
|
|
547
|
+
|
|
548
|
+
if (isCloudflare) {
|
|
549
|
+
siteDomain = handleCancel(await text({ message: 'Website Domain (e.g., brand.com):', defaultValue: '' }));
|
|
550
|
+
if (detectedCmsDomain) {
|
|
551
|
+
cmsFullUrl = detectedCmsDomain.startsWith('http') ? detectedCmsDomain : `https://${detectedCmsDomain.replace(/^https?:\/\//, '')}`;
|
|
552
|
+
} else {
|
|
553
|
+
const cmsDomainInput = handleCancel(await text({ message: 'CMS API Domain (e.g., admin.brand.com):', defaultValue: '' }));
|
|
554
|
+
cmsFullUrl = `https://${cmsDomainInput.replace(/^https?:\/\//, '')}`;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const s = spinner();
|
|
559
|
+
s.start(`Generating Astro site...`);
|
|
560
|
+
|
|
561
|
+
// 🎯 Pass secret to the frontend generator
|
|
562
|
+
await generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, targetWorkerName, isCloudflare, frontendApiSecret);
|
|
563
|
+
|
|
564
|
+
s.stop(`Site created in sites/${siteFolder}!`);
|
|
252
565
|
|
|
253
|
-
|
|
566
|
+
note(`1. npm install\n2. npm run dev`, 'Next steps');
|
|
567
|
+
outro('Site added successfully! 🚀');
|
|
568
|
+
}
|
|
254
569
|
|
|
255
|
-
|
|
570
|
+
/**
|
|
571
|
+
* COMMAND: npx kama bundle
|
|
572
|
+
*/
|
|
573
|
+
async function bundleWorkspaceSchemas(targetRootDir, silent = false) {
|
|
574
|
+
let rootDir = targetRootDir || process.cwd();
|
|
575
|
+
if (basename(rootDir) === 'kama-worker') rootDir = resolve(rootDir, '..');
|
|
576
|
+
|
|
577
|
+
const s = spinner();
|
|
578
|
+
if (!silent) s.start('Bundling schemas...');
|
|
579
|
+
try {
|
|
580
|
+
let compilerPath = join(__dirname, '../dist/schema-compiler.js');
|
|
581
|
+
if (!existsSync(compilerPath)) compilerPath = join(__dirname, '../scripts/schema-compiler.ts');
|
|
582
|
+
await import(pathToFileURL(compilerPath).href);
|
|
583
|
+
if (!silent) s.stop('Schemas bundled successfully.');
|
|
584
|
+
} catch (e) {
|
|
585
|
+
if (!silent) s.stop(`Schema compilation failed: ${e.message}`, true);
|
|
586
|
+
}
|
|
256
587
|
}
|
|
257
588
|
|
|
258
589
|
/**
|
|
259
|
-
*
|
|
590
|
+
* COMMAND: npx kama promote
|
|
260
591
|
*/
|
|
261
|
-
async function
|
|
262
|
-
|
|
592
|
+
async function promoteLocalToCloudflare() {
|
|
593
|
+
const rootDir = process.cwd();
|
|
594
|
+
const workerDir = join(rootDir, 'kama-worker');
|
|
595
|
+
const rootPkgPath = join(rootDir, 'package.json');
|
|
596
|
+
|
|
597
|
+
if (!existsSync(workerDir) || !existsSync(rootPkgPath)) {
|
|
598
|
+
cancel('Error: Could not find kama-worker folder or package.json.');
|
|
599
|
+
process.exit(1);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// 🎯 Safely persist the secret during promotion
|
|
603
|
+
const frontendApiSecret = getOrGenerateWorkspaceSecret(workerDir);
|
|
604
|
+
|
|
605
|
+
const s = spinner();
|
|
606
|
+
s.start('Checking Cloudflare authentication...');
|
|
607
|
+
|
|
608
|
+
const whoami = runWrangler('whoami');
|
|
609
|
+
if (whoami && (whoami.includes('Getting User Settings') || whoami.includes('You are logged in'))) {
|
|
610
|
+
s.stop('Logged in to Cloudflare.');
|
|
611
|
+
} else {
|
|
612
|
+
s.stop('Not logged in to Cloudflare.', true);
|
|
613
|
+
console.log(pc.dim('Launching browser for Cloudflare login...'));
|
|
614
|
+
const login = runWrangler('login', { stdio: 'inherit' });
|
|
615
|
+
if (login === null) {
|
|
616
|
+
cancel('Cloudflare login failed.');
|
|
617
|
+
process.exit(1);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const cmsDomain = handleCancel(await text({ message: 'CMS Admin Domain (e.g., admin.yourdomain.com):', defaultValue: '' }));
|
|
622
|
+
|
|
623
|
+
const sitesFolderDir = join(rootDir, 'sites');
|
|
624
|
+
let siteDomain = '';
|
|
625
|
+
let siteDir = null;
|
|
626
|
+
if (existsSync(sitesFolderDir)) {
|
|
627
|
+
const sites = readdirSync(sitesFolderDir);
|
|
628
|
+
if (sites.length > 0) {
|
|
629
|
+
siteDir = join(sitesFolderDir, sites[0]);
|
|
630
|
+
siteDomain = handleCancel(await text({ message: `Frontend Website Domain for ${sites[0]}:`, defaultValue: '' }));
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
s.start('Upgrading workspace to Cloudflare Edge...');
|
|
263
635
|
|
|
636
|
+
const projName = basename(rootDir);
|
|
637
|
+
let dbName = `${projName}-db`.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
638
|
+
let d1DatabaseId = '00000000-0000-0000-0000-000000000000';
|
|
639
|
+
|
|
640
|
+
const d1Output = runWrangler(`d1 create ${dbName}`);
|
|
641
|
+
if (d1Output) {
|
|
642
|
+
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}/);
|
|
643
|
+
if (match) d1DatabaseId = match[0];
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const workerName = `${projName}-worker`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
647
|
+
const bucketName = `${projName}-media-prod`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
648
|
+
|
|
649
|
+
const backendCacheId = getOrCreateKV(`CACHE_KV_${workerName.toUpperCase().replace(/-/g, '_')}`);
|
|
650
|
+
|
|
651
|
+
const rootPkg = JSON.parse(readFileSync(rootPkgPath, 'utf-8'));
|
|
652
|
+
rootPkg.devDependencies = rootPkg.devDependencies || {};
|
|
653
|
+
rootPkg.devDependencies['wrangler'] = WRANGLER_LATEST;
|
|
654
|
+
writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, 2));
|
|
655
|
+
|
|
656
|
+
const workerWranglerPath = join(workerDir, 'wrangler.json');
|
|
657
|
+
let currentConfig = existsSync(workerWranglerPath) ? JSON.parse(readFileSync(workerWranglerPath, 'utf-8')) : {
|
|
658
|
+
$schema: '../node_modules/wrangler/config-schema.json',
|
|
659
|
+
name: workerName,
|
|
660
|
+
main: 'index.js',
|
|
661
|
+
compatibility_date: '2024-09-23',
|
|
662
|
+
compatibility_flags: ['nodejs_compat'],
|
|
663
|
+
assets: { directory: '../node_modules/@brightsideoy/kama/dist', binding: 'ASSETS' },
|
|
664
|
+
images: { binding: 'IMAGES' },
|
|
665
|
+
vars: { CLOUDFLARE_ZONE_ID: "REPLACE_WITH_YOUR_32_CHAR_ZONE_ID", KAMA_RUNTIME: 'cloudflare' }
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
currentConfig.dev = currentConfig.dev || { host: 'localhost', port: 8787 };
|
|
669
|
+
currentConfig.vars.CDN_DOMAIN = cmsDomain;
|
|
670
|
+
if (siteDomain) currentConfig.vars.PUBLIC_FRONTEND_URL = `https://${siteDomain}`;
|
|
671
|
+
currentConfig.routes = [{ pattern: cmsDomain, custom_domain: true }];
|
|
672
|
+
currentConfig.d1_databases = [{ binding: 'DB', database_name: dbName, database_id: d1DatabaseId }];
|
|
673
|
+
currentConfig.r2_buckets = [{ binding: 'MEDIA_BUCKET', bucket_name: bucketName }];
|
|
674
|
+
currentConfig.kv_namespaces = [{ binding: 'CACHE_KV', id: backendCacheId }];
|
|
675
|
+
|
|
676
|
+
writeFileSync(workerWranglerPath, JSON.stringify(currentConfig, null, 2));
|
|
677
|
+
|
|
678
|
+
if (!existsSync(join(workerDir, '.dev.vars'))) {
|
|
679
|
+
writeFileSync(join(workerDir, '.dev.vars'), `CLOUDFLARE_API_TOKEN=cfut_YOUR_CUSTOM_PURGE_TOKEN\nCDN_DOMAIN=\nPUBLIC_FRONTEND_URL=http://localhost:4321\n\n# Server-to-Server API Security\nFRONTEND_API_SECRET="${frontendApiSecret}"\n`);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const workerPkgPath = join(workerDir, 'package.json');
|
|
683
|
+
if (existsSync(workerPkgPath)) {
|
|
684
|
+
const workerPkg = JSON.parse(readFileSync(workerPkgPath, 'utf-8'));
|
|
685
|
+
workerPkg.scripts.dev = "wrangler dev --port 8787 --inspector-port 9239";
|
|
686
|
+
workerPkg.scripts.deploy = "npm run setup:remote && wrangler deploy";
|
|
687
|
+
workerPkg.scripts["setup:local"] = `wrangler d1 execute ${dbName} --local --file=../node_modules/@brightsideoy/kama/schema.sql`;
|
|
688
|
+
workerPkg.scripts["setup:remote"] = `wrangler d1 execute ${dbName} --remote --file=../node_modules/@brightsideoy/kama/schema.sql`;
|
|
689
|
+
|
|
690
|
+
// 🎯 Uses native node with the compiled dist/build-seed.js
|
|
691
|
+
workerPkg.scripts["seed"] = `node ../node_modules/@brightsideoy/kama/dist/build-seed.js && wrangler d1 execute ${dbName} --local --file=seed.sql`;
|
|
692
|
+
|
|
693
|
+
workerPkg.devDependencies = workerPkg.devDependencies || {};
|
|
694
|
+
workerPkg.devDependencies.wrangler = WRANGLER_LATEST;
|
|
695
|
+
// 🎯 explicit deletion of TSX addition on promotion
|
|
696
|
+
if (workerPkg.devDependencies.tsx) delete workerPkg.devDependencies.tsx;
|
|
697
|
+
|
|
698
|
+
writeFileSync(workerPkgPath, JSON.stringify(workerPkg, null, 2));
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (siteDir && siteDomain) {
|
|
702
|
+
const prefix = basename(siteDir).replace(/[^a-zA-Z0-9]/g, '_').toUpperCase();
|
|
703
|
+
const sessionId = getOrCreateKV(`SESSION_${prefix}`);
|
|
704
|
+
const htmlCacheId = getOrCreateKV(`HTML_CACHE_${prefix}`);
|
|
705
|
+
|
|
706
|
+
const siteWranglerPath = join(siteDir, 'wrangler.json');
|
|
707
|
+
const siteConfig = {
|
|
708
|
+
$schema: "node_modules/wrangler/config-schema.json",
|
|
709
|
+
name: `${basename(siteDir).toLowerCase().replace(/[^a-z0-9-]/g, '-')}-site`,
|
|
710
|
+
compatibility_date: "2024-09-23",
|
|
711
|
+
compatibility_flags: ["nodejs_compat"],
|
|
712
|
+
dev: { host: 'localhost', port: 4321 },
|
|
713
|
+
cache: { enabled: true },
|
|
714
|
+
assets: { directory: "./dist/client", binding: "ASSETS", run_worker_first: true },
|
|
715
|
+
kv_namespaces: [{ binding: "SESSION", id: sessionId }, { binding: "HTML_CACHE", id: htmlCacheId }],
|
|
716
|
+
services: [{ binding: "CMS_WORKER", service: currentConfig.name }],
|
|
717
|
+
routes: [{ pattern: siteDomain, zone_name: siteDomain, custom_domain: true, enabled: true, previews_enabled: false }],
|
|
718
|
+
vars: { PUBLIC_CMS_URL: `https://${cmsDomain}`, PUBLIC_API_URL: `https://${cmsDomain}` }
|
|
719
|
+
};
|
|
720
|
+
writeFileSync(siteWranglerPath, JSON.stringify(siteConfig, null, 2));
|
|
721
|
+
|
|
722
|
+
const sitePkgPath = join(siteDir, 'package.json');
|
|
723
|
+
if (existsSync(sitePkgPath)) {
|
|
724
|
+
const sitePkg = JSON.parse(readFileSync(sitePkgPath, 'utf-8'));
|
|
725
|
+
sitePkg.dependencies = sitePkg.dependencies || {};
|
|
726
|
+
sitePkg.dependencies['astro'] = ASTRO_LATEST;
|
|
727
|
+
if (sitePkg.dependencies['@astrojs/node']) delete sitePkg.dependencies['@astrojs/node'];
|
|
728
|
+
sitePkg.dependencies['@astrojs/cloudflare'] = ASTRO_CF_LATEST;
|
|
729
|
+
sitePkg.devDependencies = sitePkg.devDependencies || {};
|
|
730
|
+
sitePkg.devDependencies['wrangler'] = WRANGLER_LATEST;
|
|
731
|
+
writeFileSync(sitePkgPath, JSON.stringify(sitePkg, null, 2));
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
convertAstroConfigToCloudflare(siteDir);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
s.stop('Workspace successfully upgraded to Cloudflare Edge!');
|
|
738
|
+
outro('Promotion complete. 🚀\nRemember to run `npx wrangler secret put FRONTEND_API_SECRET` in both your worker and site directories before production deployment!');
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Helper: Copies the Astro starter template and hydrates runtime tokens
|
|
743
|
+
*/
|
|
744
|
+
async function generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, workerName = 'kama-worker', isCloudflare = false, frontendApiSecret = '') {
|
|
264
745
|
const siteDir = join(rootDir, 'sites', siteFolder);
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
746
|
+
const templateDir = join(__dirname, '../templates/astro-starter');
|
|
747
|
+
|
|
748
|
+
if (!existsSync(templateDir)) return;
|
|
749
|
+
|
|
750
|
+
cpSync(templateDir, siteDir, { recursive: true });
|
|
751
|
+
|
|
752
|
+
const tsConfig = { extends: "astro/tsconfigs/strict", compilerOptions: { baseUrl: ".", paths: { "@/*": ["src/*"] } } };
|
|
753
|
+
if (isCloudflare) tsConfig.compilerOptions.types = ["@cloudflare/workers-types"];
|
|
754
|
+
writeFileSync(join(siteDir, 'tsconfig.json'), JSON.stringify(tsConfig, null, 2));
|
|
755
|
+
|
|
756
|
+
const templatePkgPath = join(siteDir, 'package.json.template');
|
|
757
|
+
if (existsSync(templatePkgPath)) {
|
|
758
|
+
const rawContent = readFileSync(templatePkgPath, 'utf-8')
|
|
759
|
+
.replace(/\{\{SITE_NAME\}\}/g, siteFolder)
|
|
760
|
+
.replace(/\{\{KAMA_VERSION\}\}/g, currentKamaVersion);
|
|
761
|
+
const sitePkg = JSON.parse(rawContent);
|
|
762
|
+
|
|
763
|
+
sitePkg.dependencies = sitePkg.dependencies || {};
|
|
764
|
+
sitePkg.dependencies['astro'] = ASTRO_LATEST;
|
|
765
|
+
|
|
766
|
+
if (!isCloudflare) {
|
|
767
|
+
if (sitePkg.dependencies['@astrojs/cloudflare']) delete sitePkg.dependencies['@astrojs/cloudflare'];
|
|
768
|
+
if (sitePkg.dependencies['wrangler']) delete sitePkg.dependencies['wrangler'];
|
|
769
|
+
if (sitePkg.devDependencies && sitePkg.devDependencies['wrangler']) delete sitePkg.devDependencies['wrangler'];
|
|
770
|
+
sitePkg.dependencies['@astrojs/node'] = ASTRO_NODE_LATEST;
|
|
771
|
+
} else {
|
|
772
|
+
sitePkg.dependencies['@astrojs/cloudflare'] = ASTRO_CF_LATEST;
|
|
773
|
+
if (sitePkg.dependencies['wrangler']) delete sitePkg.dependencies['wrangler'];
|
|
774
|
+
sitePkg.devDependencies = sitePkg.devDependencies || {};
|
|
775
|
+
sitePkg.devDependencies['wrangler'] = WRANGLER_LATEST;
|
|
776
|
+
convertAstroConfigToCloudflare(siteDir);
|
|
777
|
+
}
|
|
269
778
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
name: siteFolder,
|
|
274
|
-
type: 'module',
|
|
275
|
-
version: '0.0.1',
|
|
276
|
-
engines: { node: '>=22.12.0' },
|
|
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
|
-
);
|
|
779
|
+
writeFileSync(join(siteDir, 'package.json'), JSON.stringify(sitePkg, null, 2));
|
|
780
|
+
unlinkSync(templatePkgPath);
|
|
781
|
+
}
|
|
291
782
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
783
|
+
const siteLabel = siteFolder.split(/[-_]/).map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
|
|
784
|
+
const channelKey = siteFolder.replace(/[^a-z0-9_-]/gi, '-').toLowerCase();
|
|
785
|
+
const treeKey = 'main-tree';
|
|
786
|
+
const safeWorkerName = `${siteFolder.toLowerCase().replace(/[^a-z0-9-]/g, '-')}-site`;
|
|
787
|
+
|
|
788
|
+
if (isCloudflare) {
|
|
789
|
+
const prefix = siteFolder.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase();
|
|
790
|
+
const sessionId = getOrCreateKV(`SESSION_${prefix}`);
|
|
791
|
+
const htmlCacheId = getOrCreateKV(`HTML_CACHE_${prefix}`);
|
|
792
|
+
|
|
793
|
+
const siteConfig = {
|
|
794
|
+
$schema: "node_modules/wrangler/config-schema.json",
|
|
795
|
+
name: safeWorkerName,
|
|
796
|
+
compatibility_date: "2024-09-23",
|
|
797
|
+
compatibility_flags: ["nodejs_compat"],
|
|
798
|
+
dev: { host: "localhost", port: 4321 },
|
|
799
|
+
cache: { enabled: true },
|
|
800
|
+
assets: { directory: "./dist/client", binding: "ASSETS", run_worker_first: true },
|
|
801
|
+
kv_namespaces: [{ binding: "SESSION", id: sessionId }, { binding: "HTML_CACHE", id: htmlCacheId }],
|
|
802
|
+
services: [{ binding: "CMS_WORKER", service: workerName }],
|
|
803
|
+
vars: { PUBLIC_CMS_URL: cmsFullUrl, PUBLIC_API_URL: cmsFullUrl, PUBLIC_CHANNEL_ID: channelKey, PUBLIC_TREE_ID: treeKey, PUBLIC_SITE_NAME: siteLabel }
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
if (siteDomain !== 'localhost:4321') siteConfig.routes = [{ pattern: siteDomain, custom_domain: true, enabled: true, previews_enabled: false }];
|
|
807
|
+
writeFileSync(join(siteDir, 'wrangler.json'), JSON.stringify(siteConfig, null, 2));
|
|
808
|
+
}
|
|
315
809
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
export default defineConfig({
|
|
320
|
-
output: 'server',
|
|
321
|
-
trailingSlash: 'ignore',
|
|
322
|
-
image: {
|
|
323
|
-
remotePatterns: [
|
|
324
|
-
{ protocol: 'http', hostname: 'localhost', port: '3000' },
|
|
325
|
-
{ protocol: 'https', hostname: '${cmsDomain}' }
|
|
326
|
-
]
|
|
327
|
-
},
|
|
328
|
-
build: {
|
|
329
|
-
inlineStylesheets: 'always',
|
|
330
|
-
},
|
|
331
|
-
adapter: cloudflare({
|
|
332
|
-
imageService: 'cloudflare',
|
|
333
|
-
})
|
|
334
|
-
});`;
|
|
335
|
-
writeFileSync(join(siteDir, 'astro.config.mjs'), astroConfig);
|
|
810
|
+
// 🎯 Dynamically append the secret to the Astro environment setup
|
|
811
|
+
let envContent = `# 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`;
|
|
336
812
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
813
|
+
if (frontendApiSecret) {
|
|
814
|
+
envContent += `\n# Server-to-Server API Security\nFRONTEND_API_SECRET="${frontendApiSecret}"\n`;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
writeFileSync(join(siteDir, '.env'), envContent);
|
|
341
818
|
}
|
|
342
819
|
|
|
343
820
|
main().catch(console.error);
|