@brightsideoy/kama 1.0.0-alpha.12 → 1.0.0-alpha.16
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 +293 -155
- package/dist/assets/AssetEditor-CEHxvQj0.js +1 -0
- package/dist/assets/AssetEditor-D5j7pwcK.css +1 -0
- package/dist/assets/CmsHelp-BO_vWABq.css +1 -0
- package/dist/assets/CmsHelp-Cnr801lx.js +1 -0
- package/dist/assets/CmsPublisher-C3fRoBte.js +3 -0
- package/dist/assets/CmsPublisher-CugEqTD8.css +1 -0
- package/dist/assets/CmsUserCenter-Ac0GywlR.css +1 -0
- package/dist/assets/CmsUserCenter-DiuXfjMc.js +1 -0
- package/dist/assets/EditorSplitPreview-BzKh4wFB.css +1 -0
- package/dist/assets/EditorSplitPreview-tpZZF1o5.js +1 -0
- package/dist/assets/NodeEditor-Bd89OptF.css +1 -0
- package/dist/assets/NodeEditor-DaCIVPGT.js +147 -0
- package/dist/assets/TrashWorkspace-CTq0OFA-.js +1 -0
- package/dist/assets/TrashWorkspace-DtMgxg13.css +1 -0
- package/dist/assets/_plugin-vue_export-helper-CTn6xl4w.js +5 -0
- package/dist/assets/history-XvWNE5zO.js +1 -0
- package/dist/assets/index-BbV9FaG-.css +1 -0
- package/dist/assets/index-CHczMewv.js +14 -0
- package/dist/assets/layers-D0DLWjff.js +1 -0
- package/dist/assets/link-Bs_SUS0O.js +1 -0
- package/dist/assets/rotate-ccw-CvD7yNQj.js +1 -0
- package/dist/assets/save-C8e-QRkP.js +1 -0
- package/dist/assets/smartphone-Wy6xQi_i.js +1 -0
- package/dist/build-seed.js +37 -28
- package/dist/{dist-kIJlDMvm.js → dist-Ca4GRlV3.js} +619 -535
- package/dist/index.html +3 -2
- package/dist/index.js +619 -535
- package/dist/schema-compiler.js +1 -1
- package/package.json +1 -1
- package/schema.sql +3 -0
- package/templates/astro-starter/src/components/blocks/NodeList.astro +59 -63
- package/templates/astro-starter/src/middleware.ts +63 -35
- package/templates/astro-starter/src/pages/[...slug].astro +5 -12
- package/templates/astro-starter/src/pages/api/load-nodes.ts +42 -14
- package/templates/astro-starter/src/pages/api/partials/node-cards.astro +101 -0
- package/templates/kama-worker/setup-local-db.mjs +17 -0
- package/dist/assets/index-CGLx9oqF.css +0 -1
- package/dist/assets/index-De9O-dAG.js +0 -164
package/bin/cli.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
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
|
-
import { fileURLToPath
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import crypto from 'node:crypto'; // 🎯 NEW: Cryptographically secure token generator
|
|
7
7
|
|
|
8
8
|
// 🎯 Modern CLI UI Components
|
|
9
|
-
import { intro, outro, text, select, confirm, spinner, note, isCancel, cancel } from '@clack/prompts';
|
|
9
|
+
import { intro, outro, text, password, select, confirm, spinner, note, isCancel, cancel } from '@clack/prompts';
|
|
10
10
|
|
|
11
11
|
// --- UI HELPERS (Zero Dependency Colors) ---
|
|
12
12
|
const pc = {
|
|
@@ -106,31 +106,29 @@ function getWranglerCmd() {
|
|
|
106
106
|
function runWrangler(cmd, options = {}) {
|
|
107
107
|
const baseWrangler = getWranglerCmd();
|
|
108
108
|
try {
|
|
109
|
-
|
|
109
|
+
const result = execSync(`${baseWrangler} ${cmd}`, {
|
|
110
110
|
encoding: 'utf-8',
|
|
111
|
-
stdio: options.stdio ||
|
|
111
|
+
stdio: options.stdio || 'pipe',
|
|
112
|
+
input: options.input,
|
|
113
|
+
cwd: options.cwd || process.cwd(),
|
|
112
114
|
env: {
|
|
113
115
|
...process.env,
|
|
114
116
|
PATH: process.env.PATH
|
|
115
117
|
}
|
|
116
118
|
});
|
|
119
|
+
return typeof result === 'string' ? result : '';
|
|
117
120
|
} catch (e) {
|
|
118
|
-
if (options.throw) throw e;
|
|
121
|
+
if (options.throw) throw new Error(`Wrangler command failed: ${cmd}\n${e.stderr || e.message}`);
|
|
119
122
|
return null;
|
|
120
123
|
}
|
|
121
124
|
}
|
|
122
125
|
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
}
|
|
126
|
+
function shellQuote(value) {
|
|
127
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
128
|
+
}
|
|
132
129
|
|
|
133
|
-
|
|
130
|
+
function getOrCreateKV(title, cwd = process.cwd()) {
|
|
131
|
+
const listOut = runWrangler('kv namespace list', { cwd });
|
|
134
132
|
if (listOut) {
|
|
135
133
|
try {
|
|
136
134
|
const namespaces = JSON.parse(listOut);
|
|
@@ -139,7 +137,136 @@ function getOrCreateKV(title) {
|
|
|
139
137
|
} catch (err) { }
|
|
140
138
|
}
|
|
141
139
|
|
|
142
|
-
|
|
140
|
+
const createOut = runWrangler(`kv namespace create ${shellQuote(title)}`, { cwd, throw: true });
|
|
141
|
+
const match = createOut.match(/"id":\s*"([a-fA-F0-9]{32})"/);
|
|
142
|
+
if (match) return match[1];
|
|
143
|
+
|
|
144
|
+
throw new Error(`Unable to create or find Cloudflare KV namespace "${title}". Check Wrangler authentication and permissions.`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function getOrCreateD1(name, cwd = process.cwd()) {
|
|
148
|
+
const listOutput = runWrangler('d1 list --json', { cwd, throw: true });
|
|
149
|
+
let databases;
|
|
150
|
+
try {
|
|
151
|
+
databases = JSON.parse(listOutput);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
throw new Error('Wrangler returned invalid JSON while listing D1 databases.');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const existing = databases.find(database => database.name === name);
|
|
157
|
+
if (existing?.uuid) return existing.uuid;
|
|
158
|
+
|
|
159
|
+
const createOutput = runWrangler(`d1 create ${shellQuote(name)}`, { cwd, throw: true });
|
|
160
|
+
const created = createOutput.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}/);
|
|
161
|
+
if (created) return created[0];
|
|
162
|
+
|
|
163
|
+
throw new Error(`Cloudflare D1 database "${name}" was not created or returned a valid ID.`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function ensureR2Bucket(name, cwd = process.cwd()) {
|
|
167
|
+
if (runWrangler(`r2 bucket info ${shellQuote(name)} --json`, { cwd })) return;
|
|
168
|
+
|
|
169
|
+
const createOutput = runWrangler(`r2 bucket create ${shellQuote(name)}`, { cwd });
|
|
170
|
+
if (createOutput || runWrangler(`r2 bucket info ${shellQuote(name)} --json`, { cwd })) return;
|
|
171
|
+
|
|
172
|
+
throw new Error(`Unable to create or find Cloudflare R2 bucket "${name}". Check Wrangler authentication and permissions.`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function ensureCloudflareAuth(cwd) {
|
|
176
|
+
if (runWrangler('whoami', { cwd })) return;
|
|
177
|
+
|
|
178
|
+
console.log(pc.dim('Launching browser for Cloudflare login...'));
|
|
179
|
+
runWrangler('login', { cwd, stdio: 'inherit', throw: true });
|
|
180
|
+
if (!runWrangler('whoami', { cwd })) {
|
|
181
|
+
throw new Error('Cloudflare authentication could not be verified after login.');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function writeWorkerSecrets(workerDir, secret, cloudflareApiToken = '') {
|
|
186
|
+
const values = [`FRONTEND_API_SECRET=${secret}`];
|
|
187
|
+
if (cloudflareApiToken) values.push(`CLOUDFLARE_API_TOKEN=${cloudflareApiToken}`);
|
|
188
|
+
writeFileSync(join(workerDir, '.secrets'), `${values.join('\n')}\n`, { mode: 0o600 });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function readEnvValue(filePath, key) {
|
|
192
|
+
if (!existsSync(filePath)) return '';
|
|
193
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
194
|
+
const match = readFileSync(filePath, 'utf8').match(new RegExp(`^${escapedKey}=["']?([^"'\\r\\n]*)`, 'm'));
|
|
195
|
+
return match?.[1]?.trim() || '';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function resolveCloudflareZoneId(domain, token) {
|
|
199
|
+
if (!token) return '';
|
|
200
|
+
|
|
201
|
+
const labels = domain.split('.');
|
|
202
|
+
for (let index = 0; index < labels.length - 1; index++) {
|
|
203
|
+
const zoneName = labels.slice(index).join('.');
|
|
204
|
+
const response = await fetch(`https://api.cloudflare.com/client/v4/zones?name=${encodeURIComponent(zoneName)}&per_page=50`, {
|
|
205
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
206
|
+
});
|
|
207
|
+
const data = await response.json();
|
|
208
|
+
if (!response.ok || !data.success) {
|
|
209
|
+
throw new Error(`Could not look up Cloudflare zone for ${domain}. Check the token's Zone Read permission.`);
|
|
210
|
+
}
|
|
211
|
+
const zone = data.result?.find(item => item.name === zoneName && item.status === 'active');
|
|
212
|
+
if (zone?.id) return zone.id;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
throw new Error(`No active Cloudflare zone was found for ${domain}. Add the domain to Cloudflare or enter its Zone ID manually.`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function promptForCloudflareApiToken(existingToken = '') {
|
|
219
|
+
const token = handleCancel(await password({
|
|
220
|
+
message: `Optional Cloudflare API token (Zone Read + Zone Cache Purge${existingToken ? '; leave blank to keep existing token' : ''}):`,
|
|
221
|
+
mask: '*'
|
|
222
|
+
}));
|
|
223
|
+
return token.trim() || existingToken;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function normalizeCloudflareDomain(value, label) {
|
|
227
|
+
const input = String(value || '').trim();
|
|
228
|
+
if (!input) throw new Error(`${label} is required for Cloudflare deployment.`);
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
const url = new URL(input.includes('://') ? input : `https://${input}`);
|
|
232
|
+
if (!url.hostname.includes('.') || url.hostname === 'localhost') throw new Error();
|
|
233
|
+
return url.host.toLowerCase();
|
|
234
|
+
} catch {
|
|
235
|
+
throw new Error(`${label} must be a valid Cloudflare domain, for example example.com.`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function ensureGitIgnoreEntries(rootDir, entries) {
|
|
240
|
+
const gitignorePath = join(rootDir, '.gitignore');
|
|
241
|
+
const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf8') : '';
|
|
242
|
+
const lines = new Set(existing.split(/\r?\n/));
|
|
243
|
+
const missing = entries.filter(line => !lines.has(line));
|
|
244
|
+
if (missing.length > 0) appendFileSync(gitignorePath, `${existing && !existing.endsWith('\n') ? '\n' : ''}${missing.join('\n')}\n`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function ignoreSecretFiles(rootDir) {
|
|
248
|
+
ensureGitIgnoreEntries(rootDir, ['kama-worker/.secrets', 'kama-worker/.dev.vars', 'sites/*/.secrets', 'sites/*/.dev.vars']);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function updateEnvFile(filePath, values, removeKeys = []) {
|
|
252
|
+
const existing = existsSync(filePath) ? readFileSync(filePath, 'utf8') : '';
|
|
253
|
+
const keys = new Set(Object.keys(values));
|
|
254
|
+
const found = new Set();
|
|
255
|
+
const lines = existing.split(/\r?\n/).filter(line => {
|
|
256
|
+
const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)=/);
|
|
257
|
+
return !match || !removeKeys.includes(match[1]);
|
|
258
|
+
}).map(line => {
|
|
259
|
+
const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)=/);
|
|
260
|
+
if (!match || !keys.has(match[1])) return line;
|
|
261
|
+
found.add(match[1]);
|
|
262
|
+
return `${match[1]}=${JSON.stringify(values[match[1]])}`;
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
for (const key of keys) {
|
|
266
|
+
if (!found.has(key)) lines.push(`${key}=${JSON.stringify(values[key])}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
writeFileSync(filePath, `${lines.filter((line, index) => line || index < lines.length - 1).join('\n')}\n`);
|
|
143
270
|
}
|
|
144
271
|
|
|
145
272
|
function showHelp() {
|
|
@@ -154,9 +281,9 @@ function showHelp() {
|
|
|
154
281
|
console.log(pc.cyan('Workspace Scripts (Run from project root):'));
|
|
155
282
|
console.log(' npm update Update to latest KAMA version');
|
|
156
283
|
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');
|
|
284
|
+
console.log(' npm run seed -w kama-worker Seed the database with mock test data');
|
|
158
285
|
console.log(' npm run dev Boot up the CMS backend and Astro frontend concurrently');
|
|
159
|
-
console.log(' npm run deploy:
|
|
286
|
+
console.log(' npm run deploy:kama Deploy the backend worker and database to Cloudflare');
|
|
160
287
|
console.log(' npm run deploy:site Deploy the Astro frontend site to Cloudflare\n');
|
|
161
288
|
}
|
|
162
289
|
|
|
@@ -278,8 +405,14 @@ async function createFullWorkspace() {
|
|
|
278
405
|
defaultValue: ''
|
|
279
406
|
}));
|
|
280
407
|
}
|
|
408
|
+
|
|
409
|
+
cmsDomain = normalizeCloudflareDomain(cmsDomain, 'CMS Admin Domain');
|
|
410
|
+
if (shouldCreateSite) siteDomain = normalizeCloudflareDomain(siteDomain, 'Frontend Website Domain');
|
|
281
411
|
}
|
|
282
412
|
|
|
413
|
+
const cloudflareApiToken = isCloudflare ? await promptForCloudflareApiToken() : '';
|
|
414
|
+
const cloudflareZoneId = isCloudflare ? await resolveCloudflareZoneId(cmsDomain, cloudflareApiToken) : '';
|
|
415
|
+
|
|
283
416
|
const cmsProtocol = isCloudflare ? 'https://' : 'http://';
|
|
284
417
|
const cmsFullUrl = cmsDomain ? `${cmsProtocol}${cmsDomain.replace(/^https?:\/\//, '')}` : 'http://localhost:8787';
|
|
285
418
|
|
|
@@ -295,82 +428,42 @@ async function createFullWorkspace() {
|
|
|
295
428
|
|
|
296
429
|
if (!isCurrentDir) mkdirSync(rootDir, { recursive: true });
|
|
297
430
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
431
|
+
ensureGitIgnoreEntries(rootDir, [
|
|
432
|
+
'node_modules/', '.env', '.DS_Store', 'kama-worker/.dev.vars', 'kama-worker/.secrets',
|
|
433
|
+
'kama-worker/.env', 'kama-worker/.wrangler/', 'kama-worker/public/', 'kama-worker/*.db',
|
|
434
|
+
'sites/*/.env', 'sites/*/.secrets', 'sites/*/.astro/', 'sites/*/dist/',
|
|
435
|
+
'kama-worker/bundled-definitions.js'
|
|
436
|
+
]);
|
|
302
437
|
|
|
303
438
|
const sourceDefsDir = join(__dirname, '../content/definitions');
|
|
304
439
|
const targetDefsDir = join(rootDir, 'content', 'definitions');
|
|
305
440
|
if (existsSync(sourceDefsDir)) cpSync(sourceDefsDir, targetDefsDir, { recursive: true });
|
|
306
441
|
|
|
307
442
|
let d1DatabaseId = '00000000-0000-0000-0000-000000000000';
|
|
308
|
-
let backendCacheId = '
|
|
309
|
-
let isAuthenticated = false;
|
|
310
|
-
|
|
443
|
+
let backendCacheId = '';
|
|
311
444
|
if (isCloudflare) {
|
|
312
|
-
s.message('
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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...');
|
|
322
|
-
}
|
|
323
|
-
|
|
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];
|
|
358
|
-
}
|
|
359
|
-
} catch (error) { }
|
|
360
|
-
}
|
|
361
|
-
}
|
|
445
|
+
s.message('Authenticating with Cloudflare...');
|
|
446
|
+
ensureCloudflareAuth(process.cwd());
|
|
447
|
+
s.message('Provisioning D1, KV, and R2 resources...');
|
|
448
|
+
backendCacheId = getOrCreateKV(`CACHE_KV_${workerName.toUpperCase().replace(/-/g, '_')}`, rootDir);
|
|
449
|
+
d1DatabaseId = getOrCreateD1(dbName, rootDir);
|
|
450
|
+
ensureR2Bucket(bucketName, rootDir);
|
|
362
451
|
}
|
|
363
452
|
|
|
364
453
|
s.message('Writing configuration files...');
|
|
365
454
|
|
|
455
|
+
const rootPkgPath = join(rootDir, 'package.json');
|
|
456
|
+
const existingRootPkg = existsSync(rootPkgPath) ? JSON.parse(readFileSync(rootPkgPath, 'utf-8')) : {};
|
|
457
|
+
const existingWorkspaces = Array.isArray(existingRootPkg.workspaces) ? existingRootPkg.workspaces : [];
|
|
366
458
|
const rootPkg = {
|
|
367
|
-
|
|
459
|
+
...existingRootPkg,
|
|
460
|
+
name: existingRootPkg.name || projName,
|
|
368
461
|
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: {}
|
|
462
|
+
type: existingRootPkg.type || 'module',
|
|
463
|
+
workspaces: [...new Set([...existingWorkspaces, 'kama-worker', 'sites/*'])],
|
|
464
|
+
scripts: { ...existingRootPkg.scripts, 'setup': 'npx kama bundle && npm run setup:local -w kama-worker' },
|
|
465
|
+
dependencies: { ...existingRootPkg.dependencies, '@brightsideoy/kama': currentKamaVersion },
|
|
466
|
+
devDependencies: { ...existingRootPkg.devDependencies }
|
|
374
467
|
};
|
|
375
468
|
|
|
376
469
|
if (isCloudflare) rootPkg.devDependencies['wrangler'] = WRANGLER_LATEST;
|
|
@@ -381,14 +474,14 @@ async function createFullWorkspace() {
|
|
|
381
474
|
rootPkg.scripts['predev'] = 'npx kama bundle';
|
|
382
475
|
rootPkg.scripts['dev'] = 'npx concurrently "npm run dev -w kama-worker" "npm run dev -w sites/' + siteFolderName + '"';
|
|
383
476
|
rootPkg.scripts['deploy:site'] = `npm run deploy -w sites/${siteFolderName}`;
|
|
384
|
-
rootPkg.scripts['deploy:
|
|
477
|
+
rootPkg.scripts['deploy:kama'] = `npm run deploy -w kama-worker`;
|
|
385
478
|
} else {
|
|
386
479
|
rootPkg.scripts['predev'] = 'npx kama bundle';
|
|
387
480
|
rootPkg.scripts['dev'] = 'npm run dev -w kama-worker';
|
|
388
|
-
rootPkg.scripts['deploy:
|
|
481
|
+
rootPkg.scripts['deploy:kama'] = `npm run deploy -w kama-worker`;
|
|
389
482
|
}
|
|
390
483
|
|
|
391
|
-
writeFileSync(
|
|
484
|
+
writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, 2));
|
|
392
485
|
|
|
393
486
|
const cmsDir = join(rootDir, 'kama-worker');
|
|
394
487
|
mkdirSync(cmsDir, { recursive: true });
|
|
@@ -407,17 +500,16 @@ async function createFullWorkspace() {
|
|
|
407
500
|
type: 'module',
|
|
408
501
|
version: '1.0.0',
|
|
409
502
|
scripts: {
|
|
410
|
-
// 🎯 Uses native node with the compiled dist/build-seed.js
|
|
411
503
|
"seed": "npm run seed:local",
|
|
412
|
-
"seed:local": "node ../node_modules/@brightsideoy/kama/dist/build-seed.js && wrangler d1 execute " + dbName + " --local --file
|
|
413
|
-
"seed:remote": "node ../node_modules/@brightsideoy/kama/dist/build-seed.js && wrangler d1 execute " + dbName + " --remote --file
|
|
414
|
-
"setup:local": `wrangler d1 execute ${dbName} --local --file=../node_modules/@brightsideoy/kama/schema.sql
|
|
504
|
+
"seed:local": "node ../node_modules/@brightsideoy/kama/dist/build-seed.js && for f in seed-*.sql; do wrangler d1 execute " + dbName + " --local --file=\"$f\"; done",
|
|
505
|
+
"seed:remote": "node ../node_modules/@brightsideoy/kama/dist/build-seed.js && for f in seed-*.sql; do wrangler d1 execute " + dbName + " --remote --file=\"$f\"; done",
|
|
506
|
+
"setup:local": `wrangler d1 execute ${dbName} --local --file=../node_modules/@brightsideoy/kama/schema.sql`,
|
|
415
507
|
"setup:remote": `wrangler d1 execute ${dbName} --remote --file=../node_modules/@brightsideoy/kama/schema.sql`,
|
|
416
508
|
"dev": "wrangler dev --port 8787 --inspector-port 9239",
|
|
417
|
-
"deploy": "npm run setup:remote && wrangler deploy"
|
|
509
|
+
"deploy": "npm run setup:remote && wrangler deploy --secrets-file .secrets"
|
|
418
510
|
},
|
|
419
511
|
dependencies: { '@brightsideoy/kama': currentKamaVersion, '@libsql/client': '^0.14.0' },
|
|
420
|
-
devDependencies: { "wrangler": WRANGLER_LATEST },
|
|
512
|
+
devDependencies: { "wrangler": WRANGLER_LATEST },
|
|
421
513
|
}, null, 2)
|
|
422
514
|
);
|
|
423
515
|
|
|
@@ -434,7 +526,7 @@ async function createFullWorkspace() {
|
|
|
434
526
|
vars: {
|
|
435
527
|
CDN_DOMAIN: cmsDomain || 'localhost:8787',
|
|
436
528
|
PUBLIC_FRONTEND_URL: (shouldCreateSite) ? (siteDomain && siteDomain !== 'localhost:4321' ? `https://${siteDomain}` : 'http://localhost:4321') : 'http://localhost:4321',
|
|
437
|
-
CLOUDFLARE_ZONE_ID:
|
|
529
|
+
CLOUDFLARE_ZONE_ID: cloudflareZoneId,
|
|
438
530
|
KAMA_RUNTIME: 'cloudflare'
|
|
439
531
|
},
|
|
440
532
|
d1_databases: [{ binding: 'DB', database_name: dbName, database_id: d1DatabaseId }],
|
|
@@ -444,9 +536,11 @@ async function createFullWorkspace() {
|
|
|
444
536
|
|
|
445
537
|
writeFileSync(join(cmsDir, 'wrangler.json'), JSON.stringify(cmsWranglerConfig, null, 2));
|
|
446
538
|
|
|
447
|
-
// 🎯 Inject API Secret directly into .dev.vars
|
|
448
539
|
const publicFrontendUrl = (shouldCreateSite) ? (siteDomain && siteDomain !== 'localhost:4321' ? `https://${siteDomain}` : 'http://localhost:4321') : 'http://localhost:4321';
|
|
449
|
-
|
|
540
|
+
const devVars = [`# Local Development Overrides`, `CDN_DOMAIN=`, `PUBLIC_FRONTEND_URL=${publicFrontendUrl}`, ``, `# Server-to-Server API Security`, `FRONTEND_API_SECRET="${frontendApiSecret}"`];
|
|
541
|
+
if (cloudflareApiToken) devVars.push(`CLOUDFLARE_API_TOKEN="${cloudflareApiToken}"`);
|
|
542
|
+
writeFileSync(join(cmsDir, '.dev.vars'), `${devVars.join('\n')}\n`, { mode: 0o600 });
|
|
543
|
+
writeWorkerSecrets(cmsDir, frontendApiSecret, cloudflareApiToken);
|
|
450
544
|
} else {
|
|
451
545
|
const templateServerPath = join(__dirname, '../templates/kama-worker/server.js');
|
|
452
546
|
if (existsSync(templateServerPath)) cpSync(templateServerPath, join(cmsDir, 'server.js'));
|
|
@@ -458,20 +552,19 @@ async function createFullWorkspace() {
|
|
|
458
552
|
type: 'module',
|
|
459
553
|
version: '1.0.0',
|
|
460
554
|
scripts: {
|
|
461
|
-
// 🎯 Uses native node with the compiled dist/build-seed.js
|
|
462
555
|
"seed": "npm run seed:local",
|
|
463
|
-
"seed:local": "node ../node_modules/@brightsideoy/kama/dist/build-seed.js &&
|
|
464
|
-
"setup:local": "
|
|
556
|
+
"seed:local": "node ../node_modules/@brightsideoy/kama/dist/build-seed.js && node setup-local-db.mjs seed",
|
|
557
|
+
"setup:local": "node setup-local-db.mjs schema",
|
|
465
558
|
"dev": "tsx server.js",
|
|
466
559
|
"start": "node server.js"
|
|
467
560
|
},
|
|
468
561
|
dependencies: { '@brightsideoy/kama': currentKamaVersion, '@hono/node-server': '^1.13.0', '@libsql/client': '^0.14.0', 'sharp': '^0.33.5', 'dotenv': '^16.4.5' },
|
|
469
|
-
devDependencies: { "tsx": "^4.19.0" },
|
|
562
|
+
devDependencies: { "tsx": "^4.19.0" },
|
|
470
563
|
}, null, 2)
|
|
471
564
|
);
|
|
472
565
|
|
|
473
|
-
// 🎯 Inject API Secret directly into .env
|
|
474
566
|
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`);
|
|
567
|
+
cpSync(join(__dirname, '../templates/kama-worker/setup-local-db.mjs'), join(cmsDir, 'setup-local-db.mjs'));
|
|
475
568
|
}
|
|
476
569
|
|
|
477
570
|
s.message('Bundling schemas...');
|
|
@@ -479,24 +572,26 @@ async function createFullWorkspace() {
|
|
|
479
572
|
|
|
480
573
|
if (shouldCreateSite) {
|
|
481
574
|
s.message(`Scaffolding Astro site (${siteFolderName})...`);
|
|
482
|
-
// 🎯 Pass secret to the frontend generator
|
|
483
575
|
await generateAstroSite(rootDir, siteFolderName, siteDomain, cmsFullUrl, workerName, isCloudflare, frontendApiSecret);
|
|
484
576
|
}
|
|
485
577
|
|
|
486
|
-
s.
|
|
578
|
+
s.message('Installing workspace dependencies...');
|
|
579
|
+
execSync('npm install', { cwd: rootDir, stdio: 'inherit' });
|
|
580
|
+
s.message('Bundling schemas and initializing the local database...');
|
|
581
|
+
execSync('npm run setup', { cwd: rootDir, stdio: 'inherit' });
|
|
582
|
+
|
|
583
|
+
s.stop('Workspace installed and initialized successfully.');
|
|
487
584
|
|
|
488
585
|
let nextSteps = '';
|
|
489
586
|
let stepCount = 1;
|
|
490
587
|
if (!isCurrentDir) nextSteps += `${stepCount++}. cd ${projName}\n`;
|
|
491
|
-
nextSteps += `${stepCount++}. npm install\n`;
|
|
492
|
-
nextSteps += `${stepCount++}. npm run setup ${pc.dim('<- Run this once to init the database schema!')}\n`;
|
|
493
588
|
nextSteps += `${stepCount++}. npm run dev\n`;
|
|
494
589
|
|
|
495
590
|
if (isCloudflare) {
|
|
496
591
|
nextSteps += `\n${pc.dim('Cloudflare Note:')}\n`;
|
|
497
|
-
nextSteps +=
|
|
498
|
-
|
|
499
|
-
|
|
592
|
+
nextSteps += cloudflareApiToken
|
|
593
|
+
? `${pc.dim('Zone ID was discovered automatically; the purge token is stored as an ignored Worker secret.')}`
|
|
594
|
+
: `${pc.dim('Zone-level cache purging is disabled. Run kama promote with a token to enable it.')}`;
|
|
500
595
|
}
|
|
501
596
|
|
|
502
597
|
note(nextSteps, 'Next steps');
|
|
@@ -528,7 +623,6 @@ async function createNewSiteOnly() {
|
|
|
528
623
|
} catch (e) { }
|
|
529
624
|
}
|
|
530
625
|
|
|
531
|
-
// 🎯 Ensure secret exists in worker before provisioning site
|
|
532
626
|
const frontendApiSecret = existsSync(workerDir) ? getOrGenerateWorkspaceSecret(workerDir) : crypto.randomBytes(32).toString('hex');
|
|
533
627
|
|
|
534
628
|
const isCloudflare = handleCancel(await select({
|
|
@@ -550,23 +644,29 @@ async function createNewSiteOnly() {
|
|
|
550
644
|
|
|
551
645
|
if (isCloudflare) {
|
|
552
646
|
siteDomain = handleCancel(await text({ message: 'Website Domain (e.g., brand.com):', defaultValue: '' }));
|
|
647
|
+
siteDomain = normalizeCloudflareDomain(siteDomain, 'Frontend Website Domain');
|
|
553
648
|
if (detectedCmsDomain) {
|
|
554
649
|
cmsFullUrl = detectedCmsDomain.startsWith('http') ? detectedCmsDomain : `https://${detectedCmsDomain.replace(/^https?:\/\//, '')}`;
|
|
555
650
|
} else {
|
|
556
651
|
const cmsDomainInput = handleCancel(await text({ message: 'CMS API Domain (e.g., admin.brand.com):', defaultValue: '' }));
|
|
557
|
-
cmsFullUrl = `https://${cmsDomainInput
|
|
652
|
+
cmsFullUrl = `https://${normalizeCloudflareDomain(cmsDomainInput, 'CMS API Domain')}`;
|
|
558
653
|
}
|
|
654
|
+
ensureCloudflareAuth(rootDir);
|
|
559
655
|
}
|
|
560
656
|
|
|
657
|
+
ignoreSecretFiles(rootDir);
|
|
658
|
+
|
|
561
659
|
const s = spinner();
|
|
562
660
|
s.start(`Generating Astro site...`);
|
|
563
661
|
|
|
564
|
-
// 🎯 Pass secret to the frontend generator
|
|
565
662
|
await generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, targetWorkerName, isCloudflare, frontendApiSecret);
|
|
663
|
+
ignoreSecretFiles(rootDir);
|
|
664
|
+
s.message('Installing workspace dependencies...');
|
|
665
|
+
execSync('npm install', { cwd: rootDir, stdio: 'inherit' });
|
|
566
666
|
|
|
567
667
|
s.stop(`Site created in sites/${siteFolder}!`);
|
|
568
668
|
|
|
569
|
-
note(`
|
|
669
|
+
note('Run `npm run dev` from the workspace root to start the new site.', 'Next steps');
|
|
570
670
|
outro('Site added successfully! 🚀');
|
|
571
671
|
}
|
|
572
672
|
|
|
@@ -582,10 +682,11 @@ async function bundleWorkspaceSchemas(targetRootDir, silent = false) {
|
|
|
582
682
|
try {
|
|
583
683
|
let compilerPath = join(__dirname, '../dist/schema-compiler.js');
|
|
584
684
|
if (!existsSync(compilerPath)) compilerPath = join(__dirname, '../scripts/schema-compiler.ts');
|
|
585
|
-
|
|
685
|
+
execSync(`${shellQuote(process.execPath)} ${shellQuote(compilerPath)}`, { cwd: rootDir, stdio: 'inherit' });
|
|
586
686
|
if (!silent) s.stop('Schemas bundled successfully.');
|
|
587
687
|
} catch (e) {
|
|
588
688
|
if (!silent) s.stop(`Schema compilation failed: ${e.message}`, true);
|
|
689
|
+
else throw e;
|
|
589
690
|
}
|
|
590
691
|
}
|
|
591
692
|
|
|
@@ -602,26 +703,20 @@ async function promoteLocalToCloudflare() {
|
|
|
602
703
|
process.exit(1);
|
|
603
704
|
}
|
|
604
705
|
|
|
605
|
-
// 🎯 Safely persist the secret during promotion
|
|
606
706
|
const frontendApiSecret = getOrGenerateWorkspaceSecret(workerDir);
|
|
607
707
|
|
|
608
708
|
const s = spinner();
|
|
609
709
|
s.start('Checking Cloudflare authentication...');
|
|
710
|
+
ensureCloudflareAuth(rootDir);
|
|
711
|
+
s.stop('Cloudflare authentication verified.');
|
|
610
712
|
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if (login === null) {
|
|
619
|
-
cancel('Cloudflare login failed.');
|
|
620
|
-
process.exit(1);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
const cmsDomain = handleCancel(await text({ message: 'CMS Admin Domain (e.g., admin.yourdomain.com):', defaultValue: '' }));
|
|
713
|
+
const cmsDomain = normalizeCloudflareDomain(
|
|
714
|
+
handleCancel(await text({ message: 'CMS Admin Domain (e.g., admin.yourdomain.com):', defaultValue: '' })),
|
|
715
|
+
'CMS Admin Domain'
|
|
716
|
+
);
|
|
717
|
+
const existingCloudflareToken = readEnvValue(join(workerDir, '.dev.vars'), 'CLOUDFLARE_API_TOKEN') || readEnvValue(join(workerDir, '.secrets'), 'CLOUDFLARE_API_TOKEN');
|
|
718
|
+
const cloudflareApiToken = await promptForCloudflareApiToken(existingCloudflareToken);
|
|
719
|
+
const cloudflareZoneId = await resolveCloudflareZoneId(cmsDomain, cloudflareApiToken);
|
|
625
720
|
|
|
626
721
|
const sitesFolderDir = join(rootDir, 'sites');
|
|
627
722
|
let siteDomain = '';
|
|
@@ -630,26 +725,24 @@ async function promoteLocalToCloudflare() {
|
|
|
630
725
|
const sites = readdirSync(sitesFolderDir);
|
|
631
726
|
if (sites.length > 0) {
|
|
632
727
|
siteDir = join(sitesFolderDir, sites[0]);
|
|
633
|
-
siteDomain =
|
|
728
|
+
siteDomain = normalizeCloudflareDomain(
|
|
729
|
+
handleCancel(await text({ message: `Frontend Website Domain for ${sites[0]}:`, defaultValue: '' })),
|
|
730
|
+
'Frontend Website Domain'
|
|
731
|
+
);
|
|
634
732
|
}
|
|
635
733
|
}
|
|
636
734
|
|
|
735
|
+
ignoreSecretFiles(rootDir);
|
|
736
|
+
|
|
637
737
|
s.start('Upgrading workspace to Cloudflare Edge...');
|
|
638
738
|
|
|
639
739
|
const projName = basename(rootDir);
|
|
640
|
-
let dbName = `${projName}-db`.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
641
|
-
let d1DatabaseId = '00000000-0000-0000-0000-000000000000';
|
|
642
|
-
|
|
643
|
-
const d1Output = runWrangler(`d1 create ${dbName}`);
|
|
644
|
-
if (d1Output) {
|
|
645
|
-
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}/);
|
|
646
|
-
if (match) d1DatabaseId = match[0];
|
|
647
|
-
}
|
|
648
|
-
|
|
649
740
|
const workerName = `${projName}-worker`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
650
741
|
const bucketName = `${projName}-media-prod`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
651
|
-
|
|
652
|
-
const
|
|
742
|
+
const dbName = `${projName}-db`.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
743
|
+
const d1DatabaseId = getOrCreateD1(dbName, rootDir);
|
|
744
|
+
const backendCacheId = getOrCreateKV(`CACHE_KV_${workerName.toUpperCase().replace(/-/g, '_')}`, rootDir);
|
|
745
|
+
ensureR2Bucket(bucketName, rootDir);
|
|
653
746
|
|
|
654
747
|
const rootPkg = JSON.parse(readFileSync(rootPkgPath, 'utf-8'));
|
|
655
748
|
rootPkg.devDependencies = rootPkg.devDependencies || {};
|
|
@@ -665,10 +758,21 @@ async function promoteLocalToCloudflare() {
|
|
|
665
758
|
compatibility_flags: ['nodejs_compat'],
|
|
666
759
|
assets: { directory: '../node_modules/@brightsideoy/kama/dist', binding: 'ASSETS' },
|
|
667
760
|
images: { binding: 'IMAGES' },
|
|
668
|
-
vars: { CLOUDFLARE_ZONE_ID:
|
|
761
|
+
vars: { CLOUDFLARE_ZONE_ID: '', KAMA_RUNTIME: 'cloudflare' }
|
|
669
762
|
};
|
|
670
763
|
|
|
764
|
+
const cloudflareEntryPath = join(workerDir, 'index.js');
|
|
765
|
+
if (!existsSync(cloudflareEntryPath)) {
|
|
766
|
+
cpSync(join(__dirname, '../templates/kama-worker/index.js'), cloudflareEntryPath);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
currentConfig.main = 'index.js';
|
|
671
770
|
currentConfig.dev = currentConfig.dev || { host: 'localhost', port: 8787 };
|
|
771
|
+
currentConfig.vars = currentConfig.vars || {};
|
|
772
|
+
if (String(currentConfig.vars.CLOUDFLARE_ZONE_ID || '').startsWith('REPLACE_WITH_')) {
|
|
773
|
+
currentConfig.vars.CLOUDFLARE_ZONE_ID = '';
|
|
774
|
+
}
|
|
775
|
+
currentConfig.vars.CLOUDFLARE_ZONE_ID = cloudflareZoneId || currentConfig.vars.CLOUDFLARE_ZONE_ID || '';
|
|
672
776
|
currentConfig.vars.CDN_DOMAIN = cmsDomain;
|
|
673
777
|
if (siteDomain) currentConfig.vars.PUBLIC_FRONTEND_URL = `https://${siteDomain}`;
|
|
674
778
|
currentConfig.routes = [{ pattern: cmsDomain, custom_domain: true }];
|
|
@@ -678,26 +782,29 @@ async function promoteLocalToCloudflare() {
|
|
|
678
782
|
|
|
679
783
|
writeFileSync(workerWranglerPath, JSON.stringify(currentConfig, null, 2));
|
|
680
784
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
785
|
+
updateEnvFile(join(workerDir, '.dev.vars'), {
|
|
786
|
+
CDN_DOMAIN: '',
|
|
787
|
+
PUBLIC_FRONTEND_URL: 'http://localhost:4321',
|
|
788
|
+
FRONTEND_API_SECRET: frontendApiSecret,
|
|
789
|
+
...(cloudflareApiToken ? { CLOUDFLARE_API_TOKEN: cloudflareApiToken } : {})
|
|
790
|
+
});
|
|
791
|
+
writeWorkerSecrets(workerDir, frontendApiSecret, cloudflareApiToken);
|
|
684
792
|
|
|
685
793
|
const workerPkgPath = join(workerDir, 'package.json');
|
|
686
794
|
if (existsSync(workerPkgPath)) {
|
|
687
795
|
const workerPkg = JSON.parse(readFileSync(workerPkgPath, 'utf-8'));
|
|
688
796
|
workerPkg.scripts.dev = "wrangler dev --port 8787 --inspector-port 9239";
|
|
689
|
-
workerPkg.scripts.deploy = "npm run setup:remote && wrangler deploy";
|
|
797
|
+
workerPkg.scripts.deploy = "npm run setup:remote && wrangler deploy --secrets-file .secrets";
|
|
690
798
|
workerPkg.scripts["setup:local"] = `wrangler d1 execute ${dbName} --local --file=../node_modules/@brightsideoy/kama/schema.sql`;
|
|
691
799
|
workerPkg.scripts["setup:remote"] = `wrangler d1 execute ${dbName} --remote --file=../node_modules/@brightsideoy/kama/schema.sql`;
|
|
692
800
|
|
|
693
|
-
// 🎯 Uses native node with the compiled dist/build-seed.js
|
|
694
801
|
workerPkg.scripts["seed"] = "npm run seed:local";
|
|
695
|
-
|
|
696
|
-
workerPkg.scripts["seed:
|
|
802
|
+
// 🎯 3. ARCHITECTURE FIX: Chunked seed loops for promoted worker
|
|
803
|
+
workerPkg.scripts["seed:local"] = `node ../node_modules/@brightsideoy/kama/dist/build-seed.js && for f in seed-*.sql; do wrangler d1 execute ${dbName} --local --file="$f"; done`;
|
|
804
|
+
workerPkg.scripts["seed:remote"] = `node ../node_modules/@brightsideoy/kama/dist/build-seed.js && for f in seed-*.sql; do wrangler d1 execute ${dbName} --remote --file="$f"; done`;
|
|
697
805
|
|
|
698
806
|
workerPkg.devDependencies = workerPkg.devDependencies || {};
|
|
699
807
|
workerPkg.devDependencies.wrangler = WRANGLER_LATEST;
|
|
700
|
-
// 🎯 explicit deletion of TSX addition on promotion
|
|
701
808
|
if (workerPkg.devDependencies.tsx) delete workerPkg.devDependencies.tsx;
|
|
702
809
|
|
|
703
810
|
writeFileSync(workerPkgPath, JSON.stringify(workerPkg, null, 2));
|
|
@@ -705,8 +812,8 @@ async function promoteLocalToCloudflare() {
|
|
|
705
812
|
|
|
706
813
|
if (siteDir && siteDomain) {
|
|
707
814
|
const prefix = basename(siteDir).replace(/[^a-zA-Z0-9]/g, '_').toUpperCase();
|
|
708
|
-
const sessionId = getOrCreateKV(`SESSION_${prefix}
|
|
709
|
-
const htmlCacheId = getOrCreateKV(`HTML_CACHE_${prefix}
|
|
815
|
+
const sessionId = getOrCreateKV(`SESSION_${prefix}`, rootDir);
|
|
816
|
+
const htmlCacheId = getOrCreateKV(`HTML_CACHE_${prefix}`, rootDir);
|
|
710
817
|
|
|
711
818
|
const siteWranglerPath = join(siteDir, 'wrangler.json');
|
|
712
819
|
const siteConfig = {
|
|
@@ -719,7 +826,8 @@ async function promoteLocalToCloudflare() {
|
|
|
719
826
|
assets: { directory: "./dist/client", binding: "ASSETS", run_worker_first: true },
|
|
720
827
|
kv_namespaces: [{ binding: "SESSION", id: sessionId }, { binding: "HTML_CACHE", id: htmlCacheId }],
|
|
721
828
|
services: [{ binding: "CMS_WORKER", service: currentConfig.name }],
|
|
722
|
-
|
|
829
|
+
// 🎯 2. ARCHITECTURE FIX: Removed strict zone_name requirement
|
|
830
|
+
routes: [{ pattern: siteDomain, custom_domain: true, enabled: true, previews_enabled: false }],
|
|
723
831
|
vars: { PUBLIC_CMS_URL: `https://${cmsDomain}`, PUBLIC_API_URL: `https://${cmsDomain}` }
|
|
724
832
|
};
|
|
725
833
|
writeFileSync(siteWranglerPath, JSON.stringify(siteConfig, null, 2));
|
|
@@ -733,14 +841,35 @@ async function promoteLocalToCloudflare() {
|
|
|
733
841
|
sitePkg.dependencies['@astrojs/cloudflare'] = ASTRO_CF_LATEST;
|
|
734
842
|
sitePkg.devDependencies = sitePkg.devDependencies || {};
|
|
735
843
|
sitePkg.devDependencies['wrangler'] = WRANGLER_LATEST;
|
|
844
|
+
sitePkg.scripts = sitePkg.scripts || {};
|
|
845
|
+
sitePkg.scripts.deploy = 'astro build && wrangler deploy --secrets-file .secrets';
|
|
736
846
|
writeFileSync(sitePkgPath, JSON.stringify(sitePkg, null, 2));
|
|
737
847
|
}
|
|
738
848
|
|
|
849
|
+
writeWorkerSecrets(siteDir, frontendApiSecret);
|
|
850
|
+
updateEnvFile(join(siteDir, '.env'), {
|
|
851
|
+
PUBLIC_CMS_URL: `https://${cmsDomain}`,
|
|
852
|
+
PUBLIC_API_URL: `https://${cmsDomain}`
|
|
853
|
+
}, ['FRONTEND_API_SECRET']);
|
|
854
|
+
updateEnvFile(join(siteDir, '.dev.vars'), { FRONTEND_API_SECRET: frontendApiSecret });
|
|
739
855
|
convertAstroConfigToCloudflare(siteDir);
|
|
740
856
|
}
|
|
741
857
|
|
|
742
|
-
s.
|
|
743
|
-
|
|
858
|
+
s.message('Installing Cloudflare dependencies...');
|
|
859
|
+
execSync('npm install', { cwd: rootDir, stdio: 'inherit' });
|
|
860
|
+
s.message('Bundling workspace schemas...');
|
|
861
|
+
await bundleWorkspaceSchemas(rootDir, true);
|
|
862
|
+
s.message('Deploying CMS and initializing the remote database...');
|
|
863
|
+
execSync('npm run deploy -w kama-worker', { cwd: rootDir, stdio: 'inherit' });
|
|
864
|
+
|
|
865
|
+
if (siteDir && siteDomain) {
|
|
866
|
+
const siteFolder = basename(siteDir);
|
|
867
|
+
s.message(`Building and deploying ${siteFolder}...`);
|
|
868
|
+
execSync(`npm run deploy -w sites/${shellQuote(siteFolder)}`, { cwd: rootDir, stdio: 'inherit' });
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
s.stop('Workspace provisioned and deployed to Cloudflare.');
|
|
872
|
+
outro('Promotion complete.');
|
|
744
873
|
}
|
|
745
874
|
|
|
746
875
|
/**
|
|
@@ -778,6 +907,7 @@ async function generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, wo
|
|
|
778
907
|
if (sitePkg.dependencies['wrangler']) delete sitePkg.dependencies['wrangler'];
|
|
779
908
|
sitePkg.devDependencies = sitePkg.devDependencies || {};
|
|
780
909
|
sitePkg.devDependencies['wrangler'] = WRANGLER_LATEST;
|
|
910
|
+
sitePkg.scripts.deploy = 'astro build && wrangler deploy --secrets-file .secrets';
|
|
781
911
|
convertAstroConfigToCloudflare(siteDir);
|
|
782
912
|
}
|
|
783
913
|
|
|
@@ -805,21 +935,29 @@ async function generateAstroSite(rootDir, siteFolder, siteDomain, cmsFullUrl, wo
|
|
|
805
935
|
assets: { directory: "./dist/client", binding: "ASSETS", run_worker_first: true },
|
|
806
936
|
kv_namespaces: [{ binding: "SESSION", id: sessionId }, { binding: "HTML_CACHE", id: htmlCacheId }],
|
|
807
937
|
services: [{ binding: "CMS_WORKER", service: workerName }],
|
|
938
|
+
routes: [{ pattern: siteDomain, custom_domain: true, enabled: true, previews_enabled: false }],
|
|
808
939
|
vars: { PUBLIC_CMS_URL: cmsFullUrl, PUBLIC_API_URL: cmsFullUrl, PUBLIC_CHANNEL_ID: channelKey, PUBLIC_TREE_ID: treeKey, PUBLIC_SITE_NAME: siteLabel }
|
|
809
940
|
};
|
|
810
941
|
|
|
811
942
|
if (siteDomain !== 'localhost:4321') siteConfig.routes = [{ pattern: siteDomain, custom_domain: true, enabled: true, previews_enabled: false }];
|
|
812
943
|
writeFileSync(join(siteDir, 'wrangler.json'), JSON.stringify(siteConfig, null, 2));
|
|
944
|
+
writeWorkerSecrets(siteDir, frontendApiSecret);
|
|
813
945
|
}
|
|
814
946
|
|
|
815
|
-
|
|
816
|
-
let envContent = `# Local Development Overrides\nPUBLIC_CMS_URL="
|
|
947
|
+
const localCmsUrl = isCloudflare ? cmsFullUrl : 'http://127.0.0.1:8787';
|
|
948
|
+
let envContent = `# Local Development Overrides\nPUBLIC_CMS_URL="${localCmsUrl}"\nPUBLIC_API_URL="${localCmsUrl}"\nPUBLIC_CHANNEL_ID="${channelKey}"\nPUBLIC_TREE_ID="${treeKey}"\nPUBLIC_SITE_NAME="${siteLabel.replace(/"/g, '\\"')}"\n`;
|
|
817
949
|
|
|
818
|
-
if (frontendApiSecret) {
|
|
950
|
+
if (frontendApiSecret && !isCloudflare) {
|
|
819
951
|
envContent += `\n# Server-to-Server API Security\nFRONTEND_API_SECRET="${frontendApiSecret}"\n`;
|
|
820
952
|
}
|
|
821
953
|
|
|
822
954
|
writeFileSync(join(siteDir, '.env'), envContent);
|
|
955
|
+
if (isCloudflare && frontendApiSecret) {
|
|
956
|
+
writeFileSync(join(siteDir, '.dev.vars'), `FRONTEND_API_SECRET="${frontendApiSecret}"\n`, { mode: 0o600 });
|
|
957
|
+
}
|
|
823
958
|
}
|
|
824
959
|
|
|
825
|
-
main().catch(
|
|
960
|
+
main().catch(error => {
|
|
961
|
+
console.error(error);
|
|
962
|
+
process.exitCode = 1;
|
|
963
|
+
});
|