@brightsideoy/kama 1.0.0-alpha.2 ā 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/bin/cli.js +263 -145
- package/content/definitions/field-sets/channel-base.yaml +7 -0
- package/dist/assets/index-C3LzwJjY.css +1 -0
- package/dist/assets/index-DhguVxzs.js +164 -0
- package/dist/{dist-Dy7-HUEG.js ā dist-cK3xGnBZ.js} +940 -785
- package/dist/index.html +2 -2
- package/dist/index.js +982 -823
- package/dist/schema-compiler.js +5 -5
- package/package.json +2 -1
- package/schema.sql +19 -1
- package/templates/astro-starter/src/components/global/Breadcrumb.astro +50 -9
- package/templates/astro-starter/src/components/global/Footer.astro +7 -46
- package/templates/astro-starter/src/components/global/Header.astro +10 -16
- package/templates/astro-starter/src/components/global/NavigationMenu.astro +57 -46
- package/templates/astro-starter/src/layouts/StandardLayout.astro +5 -5
- package/templates/astro-starter/src/lib/cms.ts +22 -2
- package/templates/astro-starter/src/middleware.ts +7 -50
- package/templates/astro-starter/src/pages/[...slug].astro +30 -24
- package/templates/kama-worker/index.js +3 -3
- package/templates/kama-worker/server.js +11 -5
- package/dist/assets/index-F2rfUymo.css +0 -1
- package/dist/assets/index-lXxH3VFK.js +0 -164
package/bin/cli.js
CHANGED
|
@@ -3,7 +3,30 @@ import { execSync } from 'node:child_process';
|
|
|
3
3
|
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, cpSync, unlinkSync, appendFileSync } from 'node:fs';
|
|
4
4
|
import { join, dirname, basename, resolve } from 'node:path';
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
|
-
|
|
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
|
+
}
|
|
7
30
|
|
|
8
31
|
// šÆ Always use the absolute newest ecosystem
|
|
9
32
|
const ASTRO_LATEST = "latest";
|
|
@@ -25,28 +48,25 @@ try {
|
|
|
25
48
|
console.warn("ā ļø Could not detect Kama version, defaulting to 'alpha'");
|
|
26
49
|
}
|
|
27
50
|
|
|
28
|
-
const rl = readline.createInterface({
|
|
29
|
-
input: process.stdin,
|
|
30
|
-
output: process.stdout,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
51
|
function showHelp() {
|
|
34
|
-
console.log('
|
|
52
|
+
console.log(`\n${pc.bgCyan(' KAMA ⢠ENGINE CLI ')}\n`);
|
|
35
53
|
console.log('Usage: npx kama <command> [options]\n');
|
|
36
|
-
|
|
54
|
+
|
|
55
|
+
console.log(pc.cyan('Commands:'));
|
|
37
56
|
console.log(' init Kickstart a new Monorepo (CMS Worker + Optional Astro Site)');
|
|
38
57
|
console.log(' add site Add a new Astro Frontend Site to the current workspace (sites/)');
|
|
39
58
|
console.log(' bundle Compile and bundle YAML schemas into bundled-definitions.js for Edge deployment');
|
|
40
59
|
console.log(' promote Promote the current Local workspace to Cloudflare Edge');
|
|
41
60
|
console.log(' help, -h Show this help message\n');
|
|
42
61
|
|
|
43
|
-
console.log('Workspace Scripts (Run from project root):');
|
|
62
|
+
console.log(pc.cyan('Workspace Scripts (Run from project root):'));
|
|
44
63
|
console.log(' npm update Update to latest KAMA version');
|
|
64
|
+
console.log(' npm run setup Initialize database schema and seed data (Run Once)');
|
|
45
65
|
console.log(' npm run dev Boot up the CMS backend and Astro frontend concurrently');
|
|
46
66
|
console.log(' npm run deploy:cms Deploy the backend worker and database to Cloudflare');
|
|
47
67
|
console.log(' npm run deploy:site Deploy the Astro frontend site to Cloudflare\n');
|
|
48
68
|
|
|
49
|
-
console.log('Examples:');
|
|
69
|
+
console.log(pc.cyan('Examples:'));
|
|
50
70
|
console.log(' npx kama init');
|
|
51
71
|
console.log(' npx kama add site');
|
|
52
72
|
console.log(' npx kama bundle\n');
|
|
@@ -57,13 +77,13 @@ async function main() {
|
|
|
57
77
|
const command = args[0]?.toLowerCase();
|
|
58
78
|
const subCommand = args[1]?.toLowerCase();
|
|
59
79
|
|
|
60
|
-
if (
|
|
80
|
+
if (['help', '--help', '-h'].includes(command)) {
|
|
61
81
|
showHelp();
|
|
62
|
-
rl.close();
|
|
63
82
|
process.exit(0);
|
|
64
83
|
}
|
|
65
84
|
|
|
66
|
-
console.
|
|
85
|
+
console.clear();
|
|
86
|
+
intro(`${pc.bgCyan(' KAMA ⢠ENGINE ')} ${pc.dim('v' + currentKamaVersion.replace('^', ''))}`);
|
|
67
87
|
|
|
68
88
|
if (command === 'init') {
|
|
69
89
|
await createFullWorkspace();
|
|
@@ -74,29 +94,34 @@ async function main() {
|
|
|
74
94
|
} else if (command === 'bundle') {
|
|
75
95
|
await bundleWorkspaceSchemas();
|
|
76
96
|
} else {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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') {
|
|
87
110
|
showHelp();
|
|
88
|
-
|
|
111
|
+
process.exit(0);
|
|
112
|
+
} else if (choice === 'promote') {
|
|
89
113
|
await promoteLocalToCloudflare();
|
|
90
|
-
} else if (choice === '
|
|
114
|
+
} else if (choice === 'bundle') {
|
|
91
115
|
await bundleWorkspaceSchemas();
|
|
92
|
-
} else if (choice === '
|
|
116
|
+
} else if (choice === 'add_site') {
|
|
93
117
|
await createNewSiteOnly();
|
|
94
|
-
} else {
|
|
118
|
+
} else if (choice === 'init') {
|
|
95
119
|
await createFullWorkspace();
|
|
120
|
+
} else {
|
|
121
|
+
outro('Goodbye!');
|
|
122
|
+
process.exit(0);
|
|
96
123
|
}
|
|
97
124
|
}
|
|
98
|
-
|
|
99
|
-
rl.close();
|
|
100
125
|
}
|
|
101
126
|
|
|
102
127
|
/**
|
|
@@ -120,37 +145,54 @@ function convertAstroConfigToCloudflare(siteDir) {
|
|
|
120
145
|
async function createFullWorkspace() {
|
|
121
146
|
const cwdName = basename(process.cwd());
|
|
122
147
|
const hasExistingPkg = existsSync(join(process.cwd(), 'package.json'));
|
|
123
|
-
|
|
124
148
|
const defaultDirPrompt = hasExistingPkg ? '.' : cwdName;
|
|
125
|
-
|
|
149
|
+
|
|
150
|
+
const inputName = handleCancel(await text({
|
|
151
|
+
message: 'Project folder name (use "." for current dir):',
|
|
152
|
+
placeholder: defaultDirPrompt,
|
|
153
|
+
defaultValue: defaultDirPrompt
|
|
154
|
+
}));
|
|
126
155
|
|
|
127
156
|
const chosenFolder = inputName.trim() || defaultDirPrompt;
|
|
128
157
|
const isCurrentDir = chosenFolder === '.';
|
|
129
|
-
|
|
130
158
|
const projName = isCurrentDir ? cwdName : chosenFolder;
|
|
131
159
|
const rootDir = isCurrentDir ? process.cwd() : join(process.cwd(), projName);
|
|
132
160
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
+
}));
|
|
138
168
|
|
|
139
|
-
const
|
|
140
|
-
|
|
169
|
+
const shouldCreateSite = handleCancel(await confirm({
|
|
170
|
+
message: 'Do you want to scaffold an initial Astro frontend site?',
|
|
171
|
+
initialValue: true
|
|
172
|
+
}));
|
|
141
173
|
|
|
142
174
|
let siteFolderName = 'kama-starter';
|
|
143
175
|
let siteDomain = 'localhost:4321';
|
|
144
176
|
let cmsDomain = 'localhost:8787';
|
|
145
177
|
|
|
146
178
|
if (shouldCreateSite) {
|
|
147
|
-
siteFolderName = (await
|
|
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
|
+
}));
|
|
148
184
|
}
|
|
149
185
|
|
|
150
186
|
if (isCloudflare) {
|
|
151
|
-
cmsDomain = (await
|
|
187
|
+
cmsDomain = handleCancel(await text({
|
|
188
|
+
message: 'CMS Admin Domain (e.g., admin.yourdomain.com):',
|
|
189
|
+
defaultValue: ''
|
|
190
|
+
}));
|
|
152
191
|
if (shouldCreateSite) {
|
|
153
|
-
siteDomain = (await
|
|
192
|
+
siteDomain = handleCancel(await text({
|
|
193
|
+
message: 'First Website Domain (e.g., yourdomain.com):',
|
|
194
|
+
defaultValue: ''
|
|
195
|
+
}));
|
|
154
196
|
}
|
|
155
197
|
}
|
|
156
198
|
|
|
@@ -161,11 +203,11 @@ async function createFullWorkspace() {
|
|
|
161
203
|
let dbName = `${projName}-db`.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
162
204
|
const bucketName = `${projName}-media-prod`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
163
205
|
|
|
206
|
+
const s = spinner();
|
|
207
|
+
s.start('Provisioning workspace...');
|
|
208
|
+
|
|
164
209
|
if (!isCurrentDir) {
|
|
165
|
-
console.log(`\nš Creating workspace folder at ./${projName}...`);
|
|
166
210
|
mkdirSync(rootDir, { recursive: true });
|
|
167
|
-
} else {
|
|
168
|
-
console.log(`\nš Initializing workspace in current directory (${rootDir})...`);
|
|
169
211
|
}
|
|
170
212
|
|
|
171
213
|
writeFileSync(
|
|
@@ -173,40 +215,35 @@ async function createFullWorkspace() {
|
|
|
173
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`
|
|
174
216
|
);
|
|
175
217
|
|
|
176
|
-
console.log(`\nš Copying default content definitions...`);
|
|
177
218
|
const sourceDefsDir = join(__dirname, '../content/definitions');
|
|
178
219
|
const targetDefsDir = join(rootDir, 'content', 'definitions');
|
|
179
220
|
if (existsSync(sourceDefsDir)) {
|
|
180
221
|
cpSync(sourceDefsDir, targetDefsDir, { recursive: true });
|
|
181
|
-
} else {
|
|
182
|
-
console.warn(`ā ļø Could not find default content definitions at ${sourceDefsDir}`);
|
|
183
222
|
}
|
|
184
223
|
|
|
185
224
|
let d1DatabaseId = '00000000-0000-0000-0000-000000000000';
|
|
186
225
|
let isAuthenticated = false;
|
|
187
226
|
|
|
188
227
|
if (isCloudflare) {
|
|
189
|
-
|
|
228
|
+
s.message('Checking Cloudflare authentication...');
|
|
190
229
|
try {
|
|
191
|
-
// Pass stdio: 'ignore' so child process doesn't drain parent stdin stream!
|
|
192
230
|
execSync('npx wrangler whoami', { stdio: 'ignore' });
|
|
193
231
|
isAuthenticated = true;
|
|
194
|
-
console.log(`ā
Logged in to Cloudflare.`);
|
|
195
232
|
} catch (error) {
|
|
196
|
-
|
|
197
|
-
console.log(
|
|
233
|
+
s.stop('Not authenticated with Cloudflare.', true);
|
|
234
|
+
console.log(pc.dim('Launching browser for Cloudflare login...'));
|
|
198
235
|
try {
|
|
199
|
-
execSync('npx wrangler login', { stdio: '
|
|
236
|
+
execSync('npx wrangler login', { stdio: 'inherit' });
|
|
200
237
|
isAuthenticated = true;
|
|
201
|
-
console.log(`ā
Successfully logged in!`);
|
|
202
238
|
} catch (loginError) {
|
|
203
|
-
|
|
239
|
+
// Ignore
|
|
204
240
|
}
|
|
241
|
+
s.start('Resuming provisioning...');
|
|
205
242
|
}
|
|
206
243
|
|
|
207
244
|
if (isAuthenticated) {
|
|
208
245
|
try {
|
|
209
|
-
|
|
246
|
+
s.message('Checking existing Cloudflare D1 databases...');
|
|
210
247
|
const listOutput = execSync(`npx wrangler d1 list --json`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
211
248
|
const jsonStart = listOutput.indexOf('[');
|
|
212
249
|
const jsonEnd = listOutput.lastIndexOf(']') + 1;
|
|
@@ -219,44 +256,44 @@ async function createFullWorkspace() {
|
|
|
219
256
|
const existingDb = databases.find(db => db.name === dbName);
|
|
220
257
|
|
|
221
258
|
if (existingDb) {
|
|
222
|
-
|
|
223
|
-
const useExisting = await
|
|
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
|
+
}));
|
|
224
264
|
|
|
225
|
-
if (useExisting
|
|
265
|
+
if (useExisting) {
|
|
226
266
|
d1DatabaseId = existingDb.uuid;
|
|
227
|
-
|
|
267
|
+
s.start(`Linked existing database ID: ${d1DatabaseId}`);
|
|
228
268
|
} else {
|
|
229
|
-
dbName = (await
|
|
230
|
-
|
|
269
|
+
dbName = handleCancel(await text({
|
|
270
|
+
message: 'Enter a new database name:'
|
|
271
|
+
}));
|
|
272
|
+
s.start(`Creating new database '${dbName}'...`);
|
|
231
273
|
const d1Output = execSync(`npx wrangler d1 create ${dbName}`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
232
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}/);
|
|
233
275
|
if (match) {
|
|
234
276
|
d1DatabaseId = match[0];
|
|
235
|
-
console.log(`ā
Success! Database ID bound: ${d1DatabaseId}`);
|
|
236
277
|
}
|
|
237
278
|
}
|
|
238
279
|
} else {
|
|
239
|
-
|
|
280
|
+
s.message(`Attempting to auto-create Cloudflare D1 Database '${dbName}'...`);
|
|
240
281
|
const d1Output = execSync(`npx wrangler d1 create ${dbName}`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
241
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}/);
|
|
242
283
|
if (match) {
|
|
243
284
|
d1DatabaseId = match[0];
|
|
244
|
-
console.log(`ā
Success! Database ID bound: ${d1DatabaseId}`);
|
|
245
285
|
}
|
|
246
286
|
}
|
|
247
287
|
} catch (error) {
|
|
248
|
-
|
|
288
|
+
// Placeholder
|
|
249
289
|
}
|
|
250
|
-
} else {
|
|
251
|
-
console.log(`ā ļø Skipping database creation. We'll leave a placeholder in wrangler.json.`);
|
|
252
290
|
}
|
|
253
|
-
} else {
|
|
254
|
-
console.log(`\nš» Local VPS mode selected. Skipping Cloudflare authentication and domain setup...`);
|
|
255
291
|
}
|
|
256
292
|
|
|
293
|
+
s.message('Writing configuration files...');
|
|
294
|
+
|
|
257
295
|
const rootScripts = {
|
|
258
|
-
'
|
|
259
|
-
'deploy:cms': 'npm run deploy -w kama-worker',
|
|
296
|
+
'setup': 'npm run setup:local -w kama-worker',
|
|
260
297
|
};
|
|
261
298
|
|
|
262
299
|
const rootPkg = {
|
|
@@ -279,16 +316,17 @@ async function createFullWorkspace() {
|
|
|
279
316
|
rootPkg.devDependencies['concurrently'] = '^9.0.0';
|
|
280
317
|
rootPkg.scripts['dev:site'] = `npm run dev -w sites/${siteFolderName}`;
|
|
281
318
|
rootPkg.scripts['predev'] = 'npx kama bundle';
|
|
282
|
-
rootPkg.scripts['dev'] = 'npx concurrently "npm run dev
|
|
319
|
+
rootPkg.scripts['dev'] = 'npx concurrently "npm run dev -w kama-worker" "npm run dev -w sites/' + siteFolderName + '"';
|
|
283
320
|
rootPkg.scripts['deploy:site'] = `npm run deploy -w sites/${siteFolderName}`;
|
|
321
|
+
rootPkg.scripts['deploy:cms'] = `npm run deploy -w kama-worker`;
|
|
284
322
|
} else {
|
|
285
323
|
rootPkg.scripts['predev'] = 'npx kama bundle';
|
|
286
|
-
rootPkg.scripts['dev'] = 'npm run dev
|
|
324
|
+
rootPkg.scripts['dev'] = 'npm run dev -w kama-worker';
|
|
325
|
+
rootPkg.scripts['deploy:cms'] = `npm run deploy -w kama-worker`;
|
|
287
326
|
}
|
|
288
327
|
|
|
289
328
|
writeFileSync(join(rootDir, 'package.json'), JSON.stringify(rootPkg, null, 2));
|
|
290
329
|
|
|
291
|
-
console.log(`\nāļø Configuring CMS Worker '${workerName}' (${cmsDomain || 'localhost:8787'})...`);
|
|
292
330
|
const cmsDir = join(rootDir, 'kama-worker');
|
|
293
331
|
mkdirSync(cmsDir, { recursive: true });
|
|
294
332
|
|
|
@@ -310,12 +348,10 @@ async function createFullWorkspace() {
|
|
|
310
348
|
type: 'module',
|
|
311
349
|
version: '1.0.0',
|
|
312
350
|
scripts: {
|
|
313
|
-
"
|
|
314
|
-
"
|
|
315
|
-
"
|
|
316
|
-
"
|
|
317
|
-
"dev": "npm run schema:local && npm run seed:local && wrangler dev --port 8787 --inspector-port 9239",
|
|
318
|
-
"deploy": "npm run schema:remote && npm run seed:remote && wrangler deploy"
|
|
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"
|
|
319
355
|
},
|
|
320
356
|
dependencies: {
|
|
321
357
|
'@brightsideoy/kama': currentKamaVersion,
|
|
@@ -350,13 +386,7 @@ async function createFullWorkspace() {
|
|
|
350
386
|
|
|
351
387
|
writeFileSync(join(cmsDir, 'wrangler.json'), JSON.stringify(cmsWranglerConfig, null, 2));
|
|
352
388
|
|
|
353
|
-
writeFileSync(join(cmsDir, '.dev.vars'), `# Cloudflare Edge Cache Purging Secrets
|
|
354
|
-
CLOUDFLARE_API_TOKEN=cfut_YOUR_CUSTOM_PURGE_TOKEN
|
|
355
|
-
|
|
356
|
-
# Local Development Overrides
|
|
357
|
-
CDN_DOMAIN=
|
|
358
|
-
PUBLIC_FRONTEND_URL=http://localhost:4321
|
|
359
|
-
`);
|
|
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`);
|
|
360
390
|
} else {
|
|
361
391
|
const templateServerPath = join(__dirname, '../templates/kama-worker/server.js');
|
|
362
392
|
if (existsSync(templateServerPath)) {
|
|
@@ -370,9 +400,8 @@ PUBLIC_FRONTEND_URL=http://localhost:4321
|
|
|
370
400
|
type: 'module',
|
|
371
401
|
version: '1.0.0',
|
|
372
402
|
scripts: {
|
|
373
|
-
"
|
|
374
|
-
"
|
|
375
|
-
"dev": "npm run schema:local && npm run seed:local && tsx server.js",
|
|
403
|
+
"setup:local": "sqlite3 kama.db < ../node_modules/@brightsideoy/kama/schema.sql && sqlite3 kama.db < seed.sql",
|
|
404
|
+
"dev": "tsx server.js",
|
|
376
405
|
"start": "node server.js"
|
|
377
406
|
},
|
|
378
407
|
dependencies: {
|
|
@@ -391,29 +420,31 @@ PUBLIC_FRONTEND_URL=http://localhost:4321
|
|
|
391
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`);
|
|
392
421
|
}
|
|
393
422
|
|
|
394
|
-
|
|
423
|
+
s.message('Bundling schemas...');
|
|
424
|
+
await bundleWorkspaceSchemas(rootDir, true);
|
|
395
425
|
|
|
396
426
|
if (shouldCreateSite) {
|
|
427
|
+
s.message(`Scaffolding Astro site (${siteFolderName})...`);
|
|
397
428
|
await generateAstroSite(rootDir, siteFolderName, siteDomain, cmsFullUrl, workerName, isCloudflare);
|
|
398
|
-
} else {
|
|
399
|
-
console.log(`\nā¹ļø Skipping Astro site creation. You can add a site later by running 'npx kama add site' inside this workspace.`);
|
|
400
429
|
}
|
|
401
430
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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`;
|
|
405
439
|
|
|
406
440
|
if (isCloudflare) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
410
|
-
console.log(` 4. Update CLOUDFLARE_ZONE_ID in kama-worker/wrangler.json`);
|
|
411
|
-
console.log(` 5. Securely store your purge token: cd kama-worker && npx wrangler secret put CLOUDFLARE_API_TOKEN`);
|
|
412
|
-
console.log(` 6. Run 'npm run dev' from the workspace root to start everything.`);
|
|
413
|
-
} else {
|
|
414
|
-
console.log(` 3. Run 'npm run dev' from the workspace root to start the local backend and frontend.`);
|
|
415
|
-
console.log(`\nš” Note: You can deploy to Cloudflare later by running 'npx kama promote'.`);
|
|
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')}`;
|
|
416
444
|
}
|
|
445
|
+
|
|
446
|
+
note(nextSteps, 'Next steps');
|
|
447
|
+
outro(`KAMA ⢠ENGINE is ready! š`);
|
|
417
448
|
}
|
|
418
449
|
|
|
419
450
|
/**
|
|
@@ -423,8 +454,8 @@ async function createNewSiteOnly() {
|
|
|
423
454
|
const rootDir = process.cwd();
|
|
424
455
|
|
|
425
456
|
if (!existsSync(join(rootDir, 'package.json'))) {
|
|
426
|
-
|
|
427
|
-
|
|
457
|
+
cancel('Error: Must run this command inside the root of your workspace.');
|
|
458
|
+
process.exit(1);
|
|
428
459
|
}
|
|
429
460
|
|
|
430
461
|
let targetWorkerName = 'kama-worker';
|
|
@@ -440,45 +471,64 @@ async function createNewSiteOnly() {
|
|
|
440
471
|
} catch (e) { }
|
|
441
472
|
}
|
|
442
473
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
+
}));
|
|
448
487
|
|
|
449
|
-
const siteFolder = (await rl.question('Astro package/folder name (e.g., my-new-site) [kama-starter]: ')).trim() || 'kama-starter';
|
|
450
488
|
let siteDomain = 'localhost:4321';
|
|
451
489
|
let cmsFullUrl = 'http://localhost:8787';
|
|
452
490
|
|
|
453
491
|
if (isCloudflare) {
|
|
454
|
-
siteDomain = (await
|
|
492
|
+
siteDomain = handleCancel(await text({
|
|
493
|
+
message: 'Website Domain (e.g., brand.com):',
|
|
494
|
+
defaultValue: ''
|
|
495
|
+
}));
|
|
455
496
|
if (detectedCmsDomain) {
|
|
456
497
|
cmsFullUrl = detectedCmsDomain.startsWith('http') ? detectedCmsDomain : `https://${detectedCmsDomain.replace(/^https?:\/\//, '')}`;
|
|
457
498
|
} else {
|
|
458
|
-
const cmsDomainInput = (await
|
|
499
|
+
const cmsDomainInput = handleCancel(await text({
|
|
500
|
+
message: 'CMS API Domain (e.g., admin.brand.com):',
|
|
501
|
+
defaultValue: ''
|
|
502
|
+
}));
|
|
459
503
|
cmsFullUrl = `https://${cmsDomainInput.replace(/^https?:\/\//, '')}`;
|
|
460
504
|
}
|
|
461
505
|
}
|
|
462
506
|
|
|
507
|
+
const s = spinner();
|
|
508
|
+
s.start(`Generating Astro site...`);
|
|
463
509
|
await generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, targetWorkerName, isCloudflare);
|
|
510
|
+
s.stop(`Site created in sites/${siteFolder}!`);
|
|
464
511
|
|
|
465
|
-
|
|
466
|
-
|
|
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! š');
|
|
467
514
|
}
|
|
468
515
|
|
|
469
516
|
/**
|
|
470
517
|
* COMMAND: npx kama bundle
|
|
471
518
|
*/
|
|
472
|
-
async function bundleWorkspaceSchemas(targetRootDir) {
|
|
519
|
+
async function bundleWorkspaceSchemas(targetRootDir, silent = false) {
|
|
473
520
|
let rootDir = targetRootDir || process.cwd();
|
|
474
521
|
if (basename(rootDir) === 'kama-worker') rootDir = resolve(rootDir, '..');
|
|
475
522
|
|
|
523
|
+
const s = spinner();
|
|
524
|
+
if (!silent) s.start('Bundling schemas...');
|
|
476
525
|
try {
|
|
477
526
|
let compilerPath = join(__dirname, '../dist/schema-compiler.js');
|
|
478
527
|
if (!existsSync(compilerPath)) compilerPath = join(__dirname, '../scripts/schema-compiler.ts');
|
|
479
528
|
await import(pathToFileURL(compilerPath).href);
|
|
529
|
+
if (!silent) s.stop('Schemas bundled successfully.');
|
|
480
530
|
} catch (e) {
|
|
481
|
-
|
|
531
|
+
if (!silent) s.stop(`Schema compilation failed: ${e.message}`, true);
|
|
482
532
|
}
|
|
483
533
|
}
|
|
484
534
|
|
|
@@ -491,22 +541,27 @@ async function promoteLocalToCloudflare() {
|
|
|
491
541
|
const rootPkgPath = join(rootDir, 'package.json');
|
|
492
542
|
|
|
493
543
|
if (!existsSync(workerDir) || !existsSync(rootPkgPath)) {
|
|
494
|
-
|
|
495
|
-
|
|
544
|
+
cancel('Error: Could not find kama-worker folder or package.json. Run this command inside your workspace root.');
|
|
545
|
+
process.exit(1);
|
|
496
546
|
}
|
|
497
547
|
|
|
498
|
-
|
|
548
|
+
const s = spinner();
|
|
549
|
+
s.start('Checking Cloudflare authentication...');
|
|
499
550
|
|
|
500
551
|
try {
|
|
501
552
|
execSync('npx wrangler whoami', { stdio: 'ignore' });
|
|
502
|
-
|
|
553
|
+
s.stop('Logged in to Cloudflare.');
|
|
503
554
|
} catch (e) {
|
|
504
|
-
|
|
555
|
+
s.stop('Not logged in to Cloudflare.', true);
|
|
556
|
+
console.log(pc.dim('Launching browser for Cloudflare login...'));
|
|
505
557
|
try { execSync('npx wrangler login', { stdio: 'ignore' }); }
|
|
506
|
-
catch (loginError) {
|
|
558
|
+
catch (loginError) { cancel('Cloudflare login failed.'); process.exit(1); }
|
|
507
559
|
}
|
|
508
560
|
|
|
509
|
-
const cmsDomain = (await
|
|
561
|
+
const cmsDomain = handleCancel(await text({
|
|
562
|
+
message: 'CMS Admin Domain (e.g., admin.yourdomain.com):',
|
|
563
|
+
defaultValue: ''
|
|
564
|
+
}));
|
|
510
565
|
|
|
511
566
|
const sitesFolderDir = join(rootDir, 'sites');
|
|
512
567
|
let siteDomain = '';
|
|
@@ -515,10 +570,15 @@ async function promoteLocalToCloudflare() {
|
|
|
515
570
|
const sites = readdirSync(sitesFolderDir);
|
|
516
571
|
if (sites.length > 0) {
|
|
517
572
|
siteDir = join(sitesFolderDir, sites[0]);
|
|
518
|
-
siteDomain = (await
|
|
573
|
+
siteDomain = handleCancel(await text({
|
|
574
|
+
message: `Frontend Website Domain for ${sites[0]} (e.g., yourdomain.com):`,
|
|
575
|
+
defaultValue: ''
|
|
576
|
+
}));
|
|
519
577
|
}
|
|
520
578
|
}
|
|
521
579
|
|
|
580
|
+
s.start('Upgrading workspace to Cloudflare Edge...');
|
|
581
|
+
|
|
522
582
|
const projName = basename(rootDir);
|
|
523
583
|
let dbName = `${projName}-db`.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
524
584
|
let d1DatabaseId = '00000000-0000-0000-0000-000000000000';
|
|
@@ -576,10 +636,10 @@ async function promoteLocalToCloudflare() {
|
|
|
576
636
|
const workerPkgPath = join(workerDir, 'package.json');
|
|
577
637
|
if (existsSync(workerPkgPath)) {
|
|
578
638
|
const workerPkg = JSON.parse(readFileSync(workerPkgPath, 'utf-8'));
|
|
579
|
-
workerPkg.scripts.dev = "
|
|
580
|
-
workerPkg.scripts.deploy = "npm run
|
|
581
|
-
workerPkg.scripts["
|
|
582
|
-
workerPkg.scripts["
|
|
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`;
|
|
583
643
|
workerPkg.devDependencies = workerPkg.devDependencies || {};
|
|
584
644
|
workerPkg.devDependencies.wrangler = WRANGLER_LATEST;
|
|
585
645
|
writeFileSync(workerPkgPath, JSON.stringify(workerPkg, null, 2));
|
|
@@ -639,20 +699,18 @@ async function promoteLocalToCloudflare() {
|
|
|
639
699
|
}
|
|
640
700
|
}
|
|
641
701
|
|
|
642
|
-
|
|
702
|
+
s.stop('Workspace successfully upgraded to Cloudflare Edge!');
|
|
703
|
+
outro('Promotion complete. š');
|
|
643
704
|
}
|
|
644
705
|
|
|
645
706
|
/**
|
|
646
707
|
* Helper: Copies the Astro starter template and hydrates runtime tokens
|
|
647
708
|
*/
|
|
648
709
|
async function generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, workerName = 'kama-worker', isCloudflare = false) {
|
|
649
|
-
console.log(`\nš Scaffolding Astro site (${siteFolder}) mapped to ${siteDomain} (Service: ${workerName})...`);
|
|
650
|
-
|
|
651
710
|
const siteDir = join(rootDir, 'sites', siteFolder);
|
|
652
711
|
const templateDir = join(__dirname, '../templates/astro-starter');
|
|
653
712
|
|
|
654
713
|
if (!existsSync(templateDir)) {
|
|
655
|
-
console.error(`ā Error: Astro template not found at ${templateDir}`);
|
|
656
714
|
return;
|
|
657
715
|
}
|
|
658
716
|
|
|
@@ -698,6 +756,7 @@ async function generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, wo
|
|
|
698
756
|
const siteLabel = siteFolder.split(/[-_]/).map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
|
|
699
757
|
const safeSiteLabel = siteLabel.replace(/'/g, "''");
|
|
700
758
|
const channelKey = siteFolder.replace(/[^a-z0-9_-]/gi, '-').toLowerCase();
|
|
759
|
+
const homeSlug = `welcome-to-${channelKey}`;
|
|
701
760
|
const treeKey = 'main-tree';
|
|
702
761
|
const safeWorkerName = `${siteFolder.toLowerCase().replace(/[^a-z0-9-]/g, '-')}-site`;
|
|
703
762
|
|
|
@@ -723,27 +782,86 @@ async function generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, wo
|
|
|
723
782
|
assets: { directory: "./dist/client", binding: "ASSETS", run_worker_first: true },
|
|
724
783
|
kv_namespaces: [{ binding: "SESSION", id: sessionId }, { binding: "HTML_CACHE", id: htmlCacheId }],
|
|
725
784
|
services: [{ binding: "CMS_WORKER", service: workerName }],
|
|
726
|
-
vars: { PUBLIC_CMS_URL: cmsFullUrl, PUBLIC_API_URL: cmsFullUrl, PUBLIC_CHANNEL_ID: channelKey, PUBLIC_TREE_ID: treeKey,
|
|
785
|
+
vars: { PUBLIC_CMS_URL: cmsFullUrl, PUBLIC_API_URL: cmsFullUrl, PUBLIC_CHANNEL_ID: channelKey, PUBLIC_TREE_ID: treeKey, PUBLIC_SITE_NAME: siteLabel }
|
|
727
786
|
};
|
|
728
787
|
|
|
729
788
|
if (siteDomain !== 'localhost:4321') siteConfig.routes = [{ pattern: siteDomain, custom_domain: true, enabled: true, previews_enabled: false }];
|
|
730
789
|
writeFileSync(join(siteDir, 'wrangler.json'), JSON.stringify(siteConfig, null, 2));
|
|
731
790
|
}
|
|
732
791
|
|
|
792
|
+
const siteUrl = (siteDomain === 'localhost:4321' || !isCloudflare) ? `http://${siteDomain}` : `https://${siteDomain}`;
|
|
793
|
+
|
|
733
794
|
const seedSql = `
|
|
734
795
|
-- Seed data for site: ${safeSiteLabel}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
INSERT
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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
|
+
);
|
|
741
859
|
`;
|
|
742
860
|
|
|
743
861
|
const workerDir = join(rootDir, 'kama-worker');
|
|
744
862
|
if (existsSync(workerDir)) appendFileSync(join(workerDir, 'seed.sql'), seedSql);
|
|
745
863
|
|
|
746
|
-
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}"\
|
|
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`);
|
|
747
865
|
}
|
|
748
866
|
|
|
749
867
|
main().catch(console.error);
|