@vibecms/core 0.1.0

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.
Files changed (43) hide show
  1. package/dist/engine/index.d.mts +4 -0
  2. package/dist/engine/index.d.ts +4 -0
  3. package/dist/engine/index.js +1226 -0
  4. package/dist/engine/index.js.map +1 -0
  5. package/dist/engine/index.mjs +1184 -0
  6. package/dist/engine/index.mjs.map +1 -0
  7. package/dist/hooks/index.d.mts +27 -0
  8. package/dist/hooks/index.d.ts +27 -0
  9. package/dist/hooks/index.js +75 -0
  10. package/dist/hooks/index.js.map +1 -0
  11. package/dist/hooks/index.mjs +47 -0
  12. package/dist/hooks/index.mjs.map +1 -0
  13. package/dist/index-C3P1J_of.d.ts +339 -0
  14. package/dist/index-CO4uwTdH.d.mts +339 -0
  15. package/dist/index.css +2326 -0
  16. package/dist/index.css.map +1 -0
  17. package/dist/index.d.mts +412 -0
  18. package/dist/index.d.ts +412 -0
  19. package/dist/index.js +3715 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/index.mjs +3587 -0
  22. package/dist/index.mjs.map +1 -0
  23. package/dist/storage/index.d.mts +32 -0
  24. package/dist/storage/index.d.ts +32 -0
  25. package/dist/storage/index.js +103 -0
  26. package/dist/storage/index.js.map +1 -0
  27. package/dist/storage/index.mjs +75 -0
  28. package/dist/storage/index.mjs.map +1 -0
  29. package/dist/types-BkDsDmQJ.d.mts +83 -0
  30. package/dist/types-BkDsDmQJ.d.ts +83 -0
  31. package/dist/ui/index.d.mts +127 -0
  32. package/dist/ui/index.d.ts +127 -0
  33. package/dist/ui/index.js +1119 -0
  34. package/dist/ui/index.js.map +1 -0
  35. package/dist/ui/index.mjs +1030 -0
  36. package/dist/ui/index.mjs.map +1 -0
  37. package/dist/vite-plugin.d.mts +8 -0
  38. package/dist/vite-plugin.d.ts +8 -0
  39. package/dist/vite-plugin.js +193 -0
  40. package/dist/vite-plugin.js.map +1 -0
  41. package/dist/vite-plugin.mjs +165 -0
  42. package/dist/vite-plugin.mjs.map +1 -0
  43. package/package.json +89 -0
@@ -0,0 +1,1226 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/engine/index.ts
31
+ var engine_exports = {};
32
+ __export(engine_exports, {
33
+ BrowserEngine: () => BrowserEngine,
34
+ DevEngine: () => DevEngine,
35
+ NodeEngine: () => NodeEngine,
36
+ VibeEngine: () => VibeEngine,
37
+ assertValidCollection: () => assertValidCollection,
38
+ assertValidCommitOid: () => assertValidCommitOid,
39
+ assertValidSlug: () => assertValidSlug,
40
+ initVibeEngine: () => initVibeEngine,
41
+ populate: () => populate,
42
+ sanitizeRedirectPath: () => sanitizeRedirectPath,
43
+ validateCollection: () => validateCollection,
44
+ validateCommitOid: () => validateCommitOid,
45
+ validateSlug: () => validateSlug
46
+ });
47
+ module.exports = __toCommonJS(engine_exports);
48
+
49
+ // src/config.ts
50
+ var schemaRegistry = /* @__PURE__ */ new Map();
51
+ function registerCollectionSchemas(collections, singletons) {
52
+ schemaRegistry.clear();
53
+ if (collections) {
54
+ for (const [key, col] of Object.entries(collections)) {
55
+ schemaRegistry.set(key, col.schema);
56
+ }
57
+ }
58
+ if (singletons) {
59
+ for (const [key, col] of Object.entries(singletons)) {
60
+ schemaRegistry.set(key, col.schema);
61
+ }
62
+ }
63
+ }
64
+ function validateCollectionData(collection, data) {
65
+ const schema = schemaRegistry.get(collection);
66
+ if (!schema) {
67
+ return { success: true, data };
68
+ }
69
+ const result = schema.safeParse(data);
70
+ if (result.success) {
71
+ return { success: true, data: result.data };
72
+ }
73
+ const errors = result.error.issues.map(
74
+ (issue) => `${issue.path.join(".")}: ${issue.message}`
75
+ );
76
+ return { success: false, errors };
77
+ }
78
+
79
+ // src/engine/validation.ts
80
+ var SLUG_REGEX = /^[a-z0-9][a-z0-9_/-]{0,100}$/i;
81
+ var COMMIT_OID_REGEX = /^[0-9a-f]{5,40}$/;
82
+ var COLLECTION_REGEX = /^[a-z0-9][a-z0-9_-]{0,50}$/i;
83
+ function validateSlug(slug) {
84
+ return typeof slug === "string" && SLUG_REGEX.test(slug);
85
+ }
86
+ function validateCommitOid(oid) {
87
+ return typeof oid === "string" && COMMIT_OID_REGEX.test(oid);
88
+ }
89
+ function validateCollection(collection) {
90
+ return typeof collection === "string" && COLLECTION_REGEX.test(collection);
91
+ }
92
+ function assertValidSlug(slug) {
93
+ if (!validateSlug(slug)) {
94
+ throw new Error(`Invalid slug: "${slug}". Slugs must match ${SLUG_REGEX}`);
95
+ }
96
+ }
97
+ function assertValidCommitOid(oid) {
98
+ if (!validateCommitOid(oid)) {
99
+ throw new Error(`Invalid commit OID: "${oid}". Must be a hex SHA hash.`);
100
+ }
101
+ }
102
+ function assertValidCollection(collection) {
103
+ if (!validateCollection(collection)) {
104
+ throw new Error(`Invalid collection name: "${collection}"`);
105
+ }
106
+ }
107
+ function sanitizeRedirectPath(path) {
108
+ if (!path) return "/";
109
+ if (!path.startsWith("/")) return "/";
110
+ if (path.startsWith("//")) return "/";
111
+ if (path.includes("://")) return "/";
112
+ if (path.includes("\\")) return "/";
113
+ return path;
114
+ }
115
+
116
+ // src/engine/engine-node.ts
117
+ function hasReferenceToSlug(data, slug) {
118
+ if (data === slug) return true;
119
+ if (Array.isArray(data)) {
120
+ return data.some((item) => hasReferenceToSlug(item, slug));
121
+ }
122
+ if (data && typeof data === "object") {
123
+ for (const [key, value] of Object.entries(data)) {
124
+ if (key.startsWith("_")) continue;
125
+ if ([
126
+ "title",
127
+ "subtitle",
128
+ "description",
129
+ "content",
130
+ "body",
131
+ "text",
132
+ "html",
133
+ "seoTitle",
134
+ "seoDescription",
135
+ "label",
136
+ "name",
137
+ "placeholder",
138
+ "helpText",
139
+ "successMessage",
140
+ "quote",
141
+ "answer",
142
+ "question",
143
+ "caption",
144
+ "alt",
145
+ "icon",
146
+ "url",
147
+ "image",
148
+ "ogImage",
149
+ "backgroundColor",
150
+ "textColor",
151
+ "keywords"
152
+ ].includes(key)) {
153
+ continue;
154
+ }
155
+ if (hasReferenceToSlug(value, slug)) return true;
156
+ }
157
+ }
158
+ return false;
159
+ }
160
+ var ContentCache = class {
161
+ cache = /* @__PURE__ */ new Map();
162
+ maxEntries;
163
+ ttlMs;
164
+ constructor(maxEntries = 200, ttlMs = 3e4) {
165
+ this.maxEntries = maxEntries;
166
+ this.ttlMs = ttlMs;
167
+ }
168
+ get(key) {
169
+ const entry = this.cache.get(key);
170
+ if (!entry) return void 0;
171
+ if (Date.now() > entry.expiresAt) {
172
+ this.cache.delete(key);
173
+ return void 0;
174
+ }
175
+ this.cache.delete(key);
176
+ this.cache.set(key, entry);
177
+ return entry.data;
178
+ }
179
+ set(key, data) {
180
+ if (this.cache.size >= this.maxEntries) {
181
+ const firstKey = this.cache.keys().next().value;
182
+ if (firstKey !== void 0) this.cache.delete(firstKey);
183
+ }
184
+ this.cache.set(key, { data, expiresAt: Date.now() + this.ttlMs });
185
+ }
186
+ invalidate(key) {
187
+ this.cache.delete(key);
188
+ }
189
+ invalidateCollection(collection) {
190
+ for (const key of this.cache.keys()) {
191
+ if (key.startsWith(`${collection}/`)) {
192
+ this.cache.delete(key);
193
+ }
194
+ }
195
+ }
196
+ };
197
+ var NodeEngine = class {
198
+ contentDir = "src/content";
199
+ publicDir = "public";
200
+ config;
201
+ contentCache = new ContentCache();
202
+ async init(config) {
203
+ this.config = config;
204
+ if (config?.contentDir) this.contentDir = config.contentDir;
205
+ if (config?.publicDir) this.publicDir = config.publicDir;
206
+ }
207
+ resolveFilename(slug, locale) {
208
+ const base = slug.endsWith(".json") ? slug.replace(/\.json$/, "") : slug;
209
+ return locale ? `${base}.${locale}.json` : `${base}.json`;
210
+ }
211
+ async read(collection, slug, options) {
212
+ if (typeof window !== "undefined") return null;
213
+ assertValidCollection(collection);
214
+ assertValidSlug(slug);
215
+ const filename = this.resolveFilename(slug, options?.locale);
216
+ const cacheKey = `${collection}/${filename}`;
217
+ const cached = this.contentCache.get(cacheKey);
218
+ if (cached !== void 0) return cached;
219
+ const fs = await import("fs/promises");
220
+ const path = await import("path");
221
+ const filePath = path.join(process.cwd(), this.contentDir, collection, filename);
222
+ try {
223
+ const [content, stat] = await Promise.all([
224
+ fs.readFile(filePath, "utf-8"),
225
+ fs.stat(filePath)
226
+ ]);
227
+ const data = JSON.parse(content);
228
+ data._vibeVersion = stat.mtimeMs;
229
+ this.contentCache.set(cacheKey, data);
230
+ return data;
231
+ } catch (e) {
232
+ return null;
233
+ }
234
+ }
235
+ async write(collection, slug, data, options) {
236
+ if (typeof window !== "undefined") return;
237
+ assertValidCollection(collection);
238
+ assertValidSlug(slug);
239
+ const validation = validateCollectionData(collection, data);
240
+ if (!validation.success) {
241
+ throw new Error(
242
+ `Schema validation failed for ${collection}/${slug}: ${validation.errors.join("; ")}`
243
+ );
244
+ }
245
+ const fs = await import("fs/promises");
246
+ const path = await import("path");
247
+ const filename = this.resolveFilename(slug, options?.locale);
248
+ const dir = path.join(process.cwd(), this.contentDir, collection);
249
+ await fs.mkdir(dir, { recursive: true });
250
+ const filePath = path.join(dir, filename);
251
+ const dataToWrite = { ...validation.data };
252
+ const clientVersion = data._vibeVersion;
253
+ delete dataToWrite._vibeVersion;
254
+ if (clientVersion) {
255
+ try {
256
+ const stat = await fs.stat(filePath);
257
+ if (Math.abs(stat.mtimeMs - clientVersion) > 100) {
258
+ throw new Error(
259
+ `Conflict: "${collection}/${slug}" was modified by another editor. Reload to see the latest version.`
260
+ );
261
+ }
262
+ } catch (e) {
263
+ if (e.message.startsWith("Conflict:")) throw e;
264
+ }
265
+ }
266
+ await fs.writeFile(filePath, JSON.stringify(dataToWrite, null, 2), "utf-8");
267
+ this.contentCache.invalidate(`${collection}/${filename}`);
268
+ }
269
+ async delete(collection, slug, options) {
270
+ if (typeof window !== "undefined") return;
271
+ assertValidCollection(collection);
272
+ assertValidSlug(slug);
273
+ const fs = await import("fs/promises");
274
+ const path = await import("path");
275
+ const filename = this.resolveFilename(slug, options?.locale);
276
+ const baseDir = path.join(process.cwd(), this.contentDir);
277
+ const cols = await fs.readdir(baseDir);
278
+ for (const col of cols) {
279
+ if (col === collection) continue;
280
+ const colDir = path.join(baseDir, col);
281
+ const stat = await fs.stat(colDir);
282
+ if (!stat.isDirectory()) continue;
283
+ const files = await fs.readdir(colDir);
284
+ for (const file of files) {
285
+ if (!file.endsWith(".json")) continue;
286
+ const raw = await fs.readFile(path.join(colDir, file), "utf8");
287
+ try {
288
+ const data = JSON.parse(raw);
289
+ if (hasReferenceToSlug(data, slug)) {
290
+ throw new Error(
291
+ `Referential Integrity Error: Cannot delete "${slug}" because it is referenced by ${col}/${file}`
292
+ );
293
+ }
294
+ } catch (e) {
295
+ if (e.message.startsWith("Referential Integrity")) throw e;
296
+ }
297
+ }
298
+ }
299
+ const filePath = path.join(baseDir, collection, filename);
300
+ await fs.unlink(filePath);
301
+ this.contentCache.invalidate(`${collection}/${filename}`);
302
+ }
303
+ async isDirty() {
304
+ return false;
305
+ }
306
+ async revert() {
307
+ }
308
+ async commit(_message, _author) {
309
+ }
310
+ async push() {
311
+ }
312
+ async pull() {
313
+ }
314
+ async writeMedia(file, author) {
315
+ if (this.config?.media?.driver) {
316
+ return this.config.media.driver.upload(file, { engine: this, author });
317
+ }
318
+ if (typeof window !== "undefined") return "";
319
+ const fs = await import("fs/promises");
320
+ const path = await import("path");
321
+ const buffer = Buffer.from(await file.arrayBuffer());
322
+ const dir = path.join(process.cwd(), this.publicDir, "assets", "uploads");
323
+ await fs.mkdir(dir, { recursive: true });
324
+ const safeName = file.name.replace(/[^a-z0-9.-]/gi, "_").toLowerCase();
325
+ const filePath = path.join(dir, safeName);
326
+ await fs.writeFile(filePath, buffer);
327
+ const url = `/assets/uploads/${safeName}`;
328
+ try {
329
+ const sizeOf = (await import("image-size")).default;
330
+ const dimensions = sizeOf(buffer);
331
+ if (dimensions && dimensions.width && dimensions.height) {
332
+ return {
333
+ url,
334
+ width: dimensions.width,
335
+ height: dimensions.height,
336
+ alt: file.name
337
+ };
338
+ }
339
+ } catch (e) {
340
+ }
341
+ return `/assets/uploads/${safeName}`;
342
+ }
343
+ async listMedia() {
344
+ if (this.config?.media?.driver?.list) {
345
+ return this.config.media.driver.list({ engine: this });
346
+ }
347
+ if (typeof window !== "undefined") return [];
348
+ try {
349
+ const fs = await import("fs/promises");
350
+ const path = await import("path");
351
+ const uploadsDir = path.join(process.cwd(), this.publicDir, "assets", "uploads");
352
+ const files = await fs.readdir(uploadsDir);
353
+ return files.filter((f) => !f.startsWith(".")).map((f) => `/assets/uploads/${f}`);
354
+ } catch {
355
+ return [];
356
+ }
357
+ }
358
+ async deleteMedia(filename) {
359
+ if (this.config?.media?.driver?.delete) {
360
+ return this.config.media.driver.delete(filename, { engine: this });
361
+ }
362
+ if (typeof window !== "undefined") return;
363
+ const fs = await import("fs/promises");
364
+ const path = await import("path");
365
+ const safeName = filename.split("/").pop() || filename;
366
+ const filePath = path.join(process.cwd(), this.publicDir, "assets", "uploads", safeName);
367
+ try {
368
+ await fs.unlink(filePath);
369
+ } catch (e) {
370
+ }
371
+ }
372
+ async renameMedia(oldFilename, newFilename) {
373
+ if (this.config?.media?.driver?.rename) {
374
+ return this.config.media.driver.rename(oldFilename, newFilename, { engine: this });
375
+ }
376
+ if (typeof window !== "undefined") return;
377
+ const fs = await import("fs/promises");
378
+ const path = await import("path");
379
+ const safeOldName = oldFilename.split("/").pop() || oldFilename;
380
+ const safeNewName = newFilename.split("/").pop() || newFilename;
381
+ const oldPath = path.join(process.cwd(), this.publicDir, "assets", "uploads", safeOldName);
382
+ const newPath = path.join(process.cwd(), this.publicDir, "assets", "uploads", safeNewName);
383
+ try {
384
+ await fs.rename(oldPath, newPath);
385
+ } catch (e) {
386
+ throw new Error(`Failed to rename local media: ${e}`);
387
+ }
388
+ }
389
+ async pruneMedia() {
390
+ if (typeof window !== "undefined") return { deletedCount: 0, bytesFreed: 0 };
391
+ const fs = await import("fs/promises");
392
+ const path = await import("path");
393
+ let usedMedia = /* @__PURE__ */ new Set();
394
+ const contentDir = path.join(process.cwd(), "src", "content");
395
+ const uploadsDir = path.join(process.cwd(), this.publicDir, "assets", "uploads");
396
+ async function scanDir(dir) {
397
+ if (!require("fs").existsSync(dir)) return;
398
+ const entries = await fs.readdir(dir, { withFileTypes: true });
399
+ for (const entry of entries) {
400
+ const fullPath = path.join(dir, entry.name);
401
+ if (entry.isDirectory()) await scanDir(fullPath);
402
+ else if (entry.name.endsWith(".json")) {
403
+ const content = await fs.readFile(fullPath, "utf-8");
404
+ const regex = /\/assets\/uploads\/([^"'\s\\]+)/g;
405
+ let match;
406
+ while ((match = regex.exec(content)) !== null) {
407
+ usedMedia.add(match[1]);
408
+ }
409
+ }
410
+ }
411
+ }
412
+ await scanDir(contentDir);
413
+ let deletedCount = 0;
414
+ let bytesFreed = 0;
415
+ if (require("fs").existsSync(uploadsDir)) {
416
+ const uFiles = await fs.readdir(uploadsDir);
417
+ for (const f of uFiles) {
418
+ if (!usedMedia.has(f) && !f.startsWith(".")) {
419
+ const stat = await fs.stat(path.join(uploadsDir, f));
420
+ bytesFreed += stat.size;
421
+ await fs.unlink(path.join(uploadsDir, f));
422
+ deletedCount++;
423
+ }
424
+ }
425
+ }
426
+ return { deletedCount, bytesFreed };
427
+ }
428
+ async getMediaUrl(relativePath) {
429
+ return relativePath;
430
+ }
431
+ async list(collection, options) {
432
+ if (typeof window !== "undefined") return [];
433
+ try {
434
+ const fs = await import("fs/promises");
435
+ const path = await import("path");
436
+ const dir = path.join(process.cwd(), this.contentDir, collection);
437
+ const files = await fs.readdir(dir);
438
+ const slugs = files.filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
439
+ if (options?.withMeta) {
440
+ const metaList = await Promise.all(slugs.map(async (slug) => {
441
+ try {
442
+ const content = await fs.readFile(path.join(dir, `${slug}.json`), "utf-8");
443
+ const data = JSON.parse(content);
444
+ return { slug, title: data.title || data.name || slug, ...data };
445
+ } catch (e) {
446
+ return { slug, title: slug };
447
+ }
448
+ }));
449
+ return metaList;
450
+ }
451
+ return slugs;
452
+ } catch {
453
+ return [];
454
+ }
455
+ }
456
+ async getHistory(collection, slug) {
457
+ if (typeof window !== "undefined") return [];
458
+ assertValidCollection(collection);
459
+ assertValidSlug(slug);
460
+ try {
461
+ const { execFileSync } = require("child_process");
462
+ const filePath = `${this.contentDir}/${collection}/${slug}.json`;
463
+ const raw = execFileSync(
464
+ "git",
465
+ ["log", "--follow", "--format=%H%x00%s%x00%an%x00%ae%x00%at", "--", filePath],
466
+ { cwd: process.cwd(), encoding: "utf-8", timeout: 5e3 }
467
+ ).trim();
468
+ if (!raw) return [];
469
+ return raw.split("\n").map((line) => {
470
+ const [oid, message, author, email, ts] = line.split("\0");
471
+ return { oid, message, author, email, timestamp: parseInt(ts, 10) * 1e3 };
472
+ });
473
+ } catch {
474
+ return [];
475
+ }
476
+ }
477
+ async getVersionContent(commitOid, collection, slug) {
478
+ if (typeof window !== "undefined") return null;
479
+ assertValidCollection(collection);
480
+ assertValidSlug(slug);
481
+ assertValidCommitOid(commitOid);
482
+ try {
483
+ const { execFileSync } = require("child_process");
484
+ const filePath = `${this.contentDir}/${collection}/${slug}.json`;
485
+ const content = execFileSync(
486
+ "git",
487
+ ["show", `${commitOid}:${filePath}`],
488
+ { cwd: process.cwd(), encoding: "utf-8", timeout: 5e3 }
489
+ );
490
+ return JSON.parse(content);
491
+ } catch {
492
+ return null;
493
+ }
494
+ }
495
+ };
496
+
497
+ // src/engine/engine-browser.ts
498
+ var DEFAULT_AUTHOR = { name: "VibeCMS Editor", email: "editor@vibecms.local" };
499
+ function hasReferenceToSlug2(data, slug) {
500
+ if (data === slug) return true;
501
+ if (Array.isArray(data)) {
502
+ return data.some((item) => hasReferenceToSlug2(item, slug));
503
+ }
504
+ if (data && typeof data === "object") {
505
+ for (const [key, value] of Object.entries(data)) {
506
+ if (key.startsWith("_")) continue;
507
+ if ([
508
+ "title",
509
+ "subtitle",
510
+ "description",
511
+ "content",
512
+ "body",
513
+ "text",
514
+ "html",
515
+ "seoTitle",
516
+ "seoDescription",
517
+ "label",
518
+ "name",
519
+ "placeholder",
520
+ "helpText",
521
+ "successMessage",
522
+ "quote",
523
+ "answer",
524
+ "question",
525
+ "caption",
526
+ "alt",
527
+ "icon",
528
+ "url",
529
+ "image",
530
+ "ogImage",
531
+ "backgroundColor",
532
+ "textColor",
533
+ "keywords"
534
+ ].includes(key)) {
535
+ continue;
536
+ }
537
+ if (hasReferenceToSlug2(value, slug)) return true;
538
+ }
539
+ }
540
+ return false;
541
+ }
542
+ var BrowserEngine = class {
543
+ fs;
544
+ pfs;
545
+ dir = "/vibecms";
546
+ contentDir = "src/content";
547
+ publicDir = "public";
548
+ gitProxyUrl = "/api/git-proxy/";
549
+ isInitializing = false;
550
+ initialized = false;
551
+ config;
552
+ constructor() {
553
+ if (typeof window !== "undefined") {
554
+ try {
555
+ const LightningFS = require("@isomorphic-git/lightning-fs");
556
+ const FS = LightningFS.default || LightningFS;
557
+ this.fs = new FS("vibecms-fs");
558
+ this.pfs = this.fs.promises;
559
+ } catch (e) {
560
+ console.error("Failed to init lightning-fs", e);
561
+ }
562
+ }
563
+ }
564
+ async init(config) {
565
+ if (typeof window === "undefined") return;
566
+ this.config = config;
567
+ if (config?.contentDir) this.contentDir = config.contentDir;
568
+ if (config?.publicDir) this.publicDir = config.publicDir;
569
+ if (this.initialized || this.isInitializing) return;
570
+ this.isInitializing = true;
571
+ const git = await import("isomorphic-git");
572
+ const http = (await import("isomorphic-git/http/web")).default;
573
+ try {
574
+ await this.pfs.readdir(this.dir);
575
+ this.initialized = true;
576
+ this.isInitializing = false;
577
+ } catch (e) {
578
+ try {
579
+ await git.clone({
580
+ fs: this.fs,
581
+ http,
582
+ dir: this.dir,
583
+ corsProxy: config?.corsProxy || this.gitProxyUrl,
584
+ url: config?.repoUrl || "",
585
+ singleBranch: true,
586
+ depth: 1
587
+ });
588
+ this.initialized = true;
589
+ } catch (cloneErr) {
590
+ console.error("Failed to clone repository:", cloneErr);
591
+ } finally {
592
+ this.isInitializing = false;
593
+ }
594
+ }
595
+ }
596
+ resolveFilename(slug, locale) {
597
+ const base = slug.endsWith(".json") ? slug.replace(/\.json$/, "") : slug;
598
+ return locale ? `${base}.${locale}.json` : `${base}.json`;
599
+ }
600
+ async read(collection, slug, options) {
601
+ if (typeof window === "undefined" || !this.pfs) return null;
602
+ try {
603
+ const filename = this.resolveFilename(slug, options?.locale);
604
+ const filePath = `${this.dir}/${this.contentDir}/${collection}/${filename}`;
605
+ const content = await this.pfs.readFile(filePath, { encoding: "utf8" });
606
+ return JSON.parse(content);
607
+ } catch (e) {
608
+ return null;
609
+ }
610
+ }
611
+ async write(collection, slug, data, options) {
612
+ if (typeof window === "undefined" || !this.pfs) return;
613
+ const validation = validateCollectionData(collection, data);
614
+ if (!validation.success) {
615
+ throw new Error(
616
+ `Schema validation failed for ${collection}/${slug}: ${validation.errors.join("; ")}`
617
+ );
618
+ }
619
+ const filename = this.resolveFilename(slug, options?.locale);
620
+ const dirPath = `${this.dir}/${this.contentDir}/${collection}`;
621
+ const filePath = `${dirPath}/${filename}`;
622
+ const parts = dirPath.split("/").filter(Boolean);
623
+ let currentPath = "";
624
+ for (const part of parts) {
625
+ currentPath += `/${part}`;
626
+ try {
627
+ await this.pfs.mkdir(currentPath);
628
+ } catch (e) {
629
+ }
630
+ }
631
+ await this.pfs.writeFile(filePath, JSON.stringify(validation.data, null, 2), "utf8");
632
+ }
633
+ async delete(collection, slug, options) {
634
+ if (typeof window === "undefined" || !this.pfs) return;
635
+ const baseDir = `${this.dir}/${this.contentDir}`;
636
+ const cols = await this.pfs.readdir(baseDir);
637
+ for (const col of cols) {
638
+ if (col === collection) continue;
639
+ const colDir = `${baseDir}/${col}`;
640
+ try {
641
+ const files = await this.pfs.readdir(colDir);
642
+ for (const file of files) {
643
+ if (!file.endsWith(".json")) continue;
644
+ const raw = await this.pfs.readFile(`${colDir}/${file}`, { encoding: "utf8" });
645
+ try {
646
+ const data = JSON.parse(raw);
647
+ if (hasReferenceToSlug2(data, slug)) {
648
+ throw new Error(
649
+ `Referential Integrity Error: Cannot delete "${slug}" because it is referenced by ${col}/${file}`
650
+ );
651
+ }
652
+ } catch (e) {
653
+ if (e.message.startsWith("Referential Integrity")) throw e;
654
+ }
655
+ }
656
+ } catch (e) {
657
+ if (e.message?.startsWith("Referential Integrity")) throw e;
658
+ }
659
+ }
660
+ const filename = this.resolveFilename(slug, options?.locale);
661
+ await this.pfs.unlink(`${baseDir}/${collection}/${filename}`);
662
+ }
663
+ async isDirty(collection, slug) {
664
+ if (typeof window === "undefined" || !this.pfs) return false;
665
+ try {
666
+ const git = await import("isomorphic-git");
667
+ const relativeFilePath = `${this.contentDir}/${collection}/${slug}.json`;
668
+ const status = await git.status({ fs: this.fs, dir: this.dir, filepath: relativeFilePath });
669
+ return status !== "unmodified" && status !== "ignored";
670
+ } catch (e) {
671
+ return false;
672
+ }
673
+ }
674
+ async revert(collection, slug) {
675
+ if (typeof window === "undefined" || !this.pfs) return;
676
+ try {
677
+ const git = await import("isomorphic-git");
678
+ const relativeFilePath = `${this.contentDir}/${collection}/${slug}.json`;
679
+ await git.checkout({
680
+ fs: this.fs,
681
+ dir: this.dir,
682
+ filepaths: [relativeFilePath],
683
+ force: true
684
+ });
685
+ } catch (e) {
686
+ console.error("Failed to revert", e);
687
+ }
688
+ }
689
+ async commit(message, author) {
690
+ if (typeof window === "undefined" || !this.pfs) return;
691
+ const git = await import("isomorphic-git");
692
+ const commitAuthor = author || DEFAULT_AUTHOR;
693
+ const listFiles = async (dirPath) => {
694
+ const entries = await this.pfs.readdir(dirPath);
695
+ let files = [];
696
+ for (const entry of entries) {
697
+ if (entry === ".git") continue;
698
+ const internalPath = `${dirPath}/${entry}`;
699
+ const stat = await this.pfs.stat(internalPath);
700
+ if (stat.isDirectory()) {
701
+ files = files.concat(await listFiles(internalPath));
702
+ } else if (entry.endsWith(".json")) {
703
+ files.push(internalPath.replace(`${this.dir}/`, ""));
704
+ }
705
+ }
706
+ return files;
707
+ };
708
+ try {
709
+ const formFiles = await listFiles(`${this.dir}/${this.contentDir}`);
710
+ for (const filepath of formFiles) {
711
+ await git.add({ fs: this.fs, dir: this.dir, filepath });
712
+ }
713
+ await git.commit({
714
+ fs: this.fs,
715
+ dir: this.dir,
716
+ author: { name: commitAuthor.name, email: commitAuthor.email },
717
+ message: message || "CMS Content Update"
718
+ });
719
+ } catch (e) {
720
+ console.error("Commit failed", e);
721
+ }
722
+ }
723
+ async push(oauthToken) {
724
+ if (typeof window === "undefined" || !this.pfs) return;
725
+ const git = await import("isomorphic-git");
726
+ const http = (await import("isomorphic-git/http/web")).default;
727
+ await git.push({
728
+ fs: this.fs,
729
+ http,
730
+ dir: this.dir,
731
+ remote: "origin",
732
+ ref: "main",
733
+ onAuth: () => ({ oauth2format: "github", password: oauthToken })
734
+ });
735
+ }
736
+ async pull() {
737
+ if (typeof window === "undefined" || !this.pfs) return;
738
+ const git = await import("isomorphic-git");
739
+ const http = (await import("isomorphic-git/http/web")).default;
740
+ await git.pull({
741
+ fs: this.fs,
742
+ http,
743
+ dir: this.dir,
744
+ ref: "main",
745
+ singleBranch: true,
746
+ corsProxy: this.gitProxyUrl,
747
+ author: { name: DEFAULT_AUTHOR.name, email: DEFAULT_AUTHOR.email }
748
+ });
749
+ }
750
+ async writeMedia(file, author) {
751
+ if (this.config?.media?.driver) {
752
+ return this.config.media.driver.upload(file, { engine: this, author });
753
+ }
754
+ if (typeof window === "undefined" || !this.pfs) return "";
755
+ const git = await import("isomorphic-git");
756
+ const dirPath = `${this.dir}/${this.publicDir}/assets/uploads`;
757
+ try {
758
+ const parts = `${this.publicDir}/assets/uploads`.split("/").filter(Boolean);
759
+ let currentPath = this.dir;
760
+ for (const part of parts) {
761
+ currentPath += `/${part}`;
762
+ try {
763
+ await this.pfs.mkdir(currentPath);
764
+ } catch (e) {
765
+ }
766
+ }
767
+ } catch (e) {
768
+ }
769
+ const safeName = file.name.replace(/[^a-z0-9.-]/gi, "_").toLowerCase();
770
+ const filePath = `${dirPath}/${safeName}`;
771
+ const arrayBuffer = await file.arrayBuffer();
772
+ const buffer = new Uint8Array(arrayBuffer);
773
+ await this.pfs.writeFile(filePath, buffer);
774
+ await git.add({ fs: this.fs, dir: this.dir, filepath: `${this.publicDir}/assets/uploads/${safeName}` });
775
+ const commitAuthor = author || DEFAULT_AUTHOR;
776
+ await git.commit({
777
+ fs: this.fs,
778
+ dir: this.dir,
779
+ author: { name: commitAuthor.name, email: commitAuthor.email },
780
+ message: `Upload media ${safeName}`
781
+ });
782
+ try {
783
+ const blob = new Blob([buffer], { type: file.type });
784
+ const src = URL.createObjectURL(blob);
785
+ const img = new Image();
786
+ img.src = src;
787
+ await new Promise((resolve) => {
788
+ img.onload = resolve;
789
+ img.onerror = resolve;
790
+ });
791
+ return {
792
+ url: `/assets/uploads/${safeName}`,
793
+ width: img.naturalWidth,
794
+ height: img.naturalHeight,
795
+ alt: file.name
796
+ };
797
+ } catch {
798
+ return `/assets/uploads/${safeName}`;
799
+ }
800
+ }
801
+ async pruneMedia() {
802
+ return { deletedCount: 0, bytesFreed: 0 };
803
+ }
804
+ async listMedia() {
805
+ if (this.config?.media?.driver?.list) {
806
+ return this.config.media.driver.list({ engine: this });
807
+ }
808
+ if (typeof window === "undefined" || !this.pfs) return [];
809
+ try {
810
+ const dirPath = `${this.dir}/${this.publicDir}/assets/uploads`;
811
+ const files = await this.pfs.readdir(dirPath);
812
+ return files.filter((f) => !f.startsWith(".")).map((f) => `/assets/uploads/${f}`);
813
+ } catch {
814
+ return [];
815
+ }
816
+ }
817
+ async deleteMedia(filename) {
818
+ if (this.config?.media?.driver?.delete) {
819
+ return this.config.media.driver.delete(filename, { engine: this });
820
+ }
821
+ if (typeof window === "undefined" || !this.pfs) return;
822
+ const safeName = filename.split("/").pop() || filename;
823
+ const filePath = `${this.dir}/${this.publicDir}/assets/uploads/${safeName}`;
824
+ try {
825
+ await this.pfs.unlink(filePath);
826
+ const git = await import("isomorphic-git");
827
+ await git.remove({ fs: this.fs, dir: this.dir, filepath: `${this.publicDir}/assets/uploads/${safeName}` });
828
+ } catch {
829
+ }
830
+ }
831
+ async renameMedia(oldFilename, newFilename) {
832
+ if (this.config?.media?.driver?.rename) {
833
+ return this.config.media.driver.rename(oldFilename, newFilename, { engine: this });
834
+ }
835
+ if (typeof window === "undefined" || !this.pfs) return;
836
+ const safeOldName = oldFilename.split("/").pop() || oldFilename;
837
+ const safeNewName = newFilename.split("/").pop() || newFilename;
838
+ const oldPath = `${this.dir}/${this.publicDir}/assets/uploads/${safeOldName}`;
839
+ const newPath = `${this.dir}/${this.publicDir}/assets/uploads/${safeNewName}`;
840
+ try {
841
+ const buffer = await this.pfs.readFile(oldPath);
842
+ await this.pfs.writeFile(newPath, buffer);
843
+ await this.pfs.unlink(oldPath);
844
+ const git = await import("isomorphic-git");
845
+ await git.remove({ fs: this.fs, dir: this.dir, filepath: `${this.publicDir}/assets/uploads/${safeOldName}` });
846
+ await git.add({ fs: this.fs, dir: this.dir, filepath: `${this.publicDir}/assets/uploads/${safeNewName}` });
847
+ } catch {
848
+ }
849
+ }
850
+ async getMediaUrl(relativePath) {
851
+ if (typeof window === "undefined" || !this.pfs) return relativePath;
852
+ try {
853
+ const filePath = `${this.dir}/${this.publicDir}${relativePath}`;
854
+ const uint8Array = await this.pfs.readFile(filePath);
855
+ const ext = relativePath.split(".").pop()?.toLowerCase();
856
+ let mime = "image/png";
857
+ if (ext === "jpg" || ext === "jpeg") mime = "image/jpeg";
858
+ else if (ext === "svg") mime = "image/svg+xml";
859
+ else if (ext === "gif") mime = "image/gif";
860
+ else if (ext === "webp") mime = "image/webp";
861
+ const blob = new Blob([uint8Array], { type: mime });
862
+ return URL.createObjectURL(blob);
863
+ } catch (e) {
864
+ return relativePath;
865
+ }
866
+ }
867
+ async list(collection, options) {
868
+ if (typeof window === "undefined" || !this.pfs) return [];
869
+ try {
870
+ const files = await this.pfs.readdir(`${this.dir}/${this.contentDir}/${collection}`);
871
+ const slugs = files.filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
872
+ if (options?.withMeta) {
873
+ const metaList = await Promise.all(slugs.map(async (slug) => {
874
+ try {
875
+ const content = await this.pfs.readFile(`${this.dir}/${this.contentDir}/${collection}/${slug}.json`, { encoding: "utf8" });
876
+ const data = JSON.parse(content);
877
+ return { slug, title: data.title || data.name || slug, ...data };
878
+ } catch (e) {
879
+ return { slug, title: slug };
880
+ }
881
+ }));
882
+ return metaList;
883
+ }
884
+ return slugs;
885
+ } catch {
886
+ return [];
887
+ }
888
+ }
889
+ async getHistory(collection, slug) {
890
+ if (typeof window === "undefined" || !this.pfs) return [];
891
+ try {
892
+ const git = await import("isomorphic-git");
893
+ const relativeFilePath = `${this.contentDir}/${collection}/${slug}.json`;
894
+ const commits = await git.log({
895
+ fs: this.fs,
896
+ dir: this.dir,
897
+ filepath: relativeFilePath
898
+ });
899
+ return commits.map((c) => ({
900
+ oid: c.oid,
901
+ message: c.commit.message,
902
+ author: c.commit.author.name,
903
+ email: c.commit.author.email,
904
+ timestamp: c.commit.author.timestamp * 1e3
905
+ }));
906
+ } catch (e) {
907
+ return [];
908
+ }
909
+ }
910
+ async getVersionContent(commitOid, collection, slug) {
911
+ if (typeof window === "undefined" || !this.pfs) return null;
912
+ try {
913
+ const git = await import("isomorphic-git");
914
+ const relativeFilePath = `${this.contentDir}/${collection}/${slug}.json`;
915
+ const { blob } = await git.readBlob({
916
+ fs: this.fs,
917
+ dir: this.dir,
918
+ oid: commitOid,
919
+ filepath: relativeFilePath
920
+ });
921
+ const content = new TextDecoder("utf-8").decode(blob);
922
+ return JSON.parse(content);
923
+ } catch (e) {
924
+ return null;
925
+ }
926
+ }
927
+ };
928
+
929
+ // src/engine/engine-dev.ts
930
+ var DevEngine = class {
931
+ apiBase = "/__vibecms/api";
932
+ config;
933
+ async init(config) {
934
+ this.config = config;
935
+ if (config?.devMode === false) {
936
+ console.warn("DevEngine initialized but devMode is false");
937
+ }
938
+ }
939
+ async fetchApi(method, body) {
940
+ const res = await fetch(this.apiBase, {
941
+ method: "POST",
942
+ headers: { "Content-Type": "application/json" },
943
+ body: JSON.stringify({ method, ...body })
944
+ });
945
+ if (!res.ok) {
946
+ const err = await res.json();
947
+ throw new Error(err.message || "API Error");
948
+ }
949
+ return res.json();
950
+ }
951
+ async read(collection, slug, options) {
952
+ return this.fetchApi("read", { collection, slug, options }).catch(() => null);
953
+ }
954
+ async write(collection, slug, data, options) {
955
+ await this.fetchApi("write", { collection, slug, data, options });
956
+ }
957
+ async delete(collection, slug, options) {
958
+ await this.fetchApi("delete", { collection, slug, options });
959
+ }
960
+ async isDirty() {
961
+ return false;
962
+ }
963
+ async revert() {
964
+ }
965
+ async commit(_message, _author) {
966
+ }
967
+ async push() {
968
+ }
969
+ async pull() {
970
+ }
971
+ async writeMedia(file, author) {
972
+ if (this.config?.media?.driver) {
973
+ return this.config.media.driver.upload(file, { engine: this, author });
974
+ }
975
+ const formData = new FormData();
976
+ formData.append("file", file);
977
+ formData.append("method", "writeMedia");
978
+ const res = await fetch(this.apiBase, {
979
+ method: "POST",
980
+ body: formData
981
+ });
982
+ if (!res.ok) throw new Error("Failed to upload media");
983
+ const data = await res.json();
984
+ return data.url && data.width ? data : data.url;
985
+ }
986
+ async listMedia() {
987
+ if (this.config?.media?.driver?.list) {
988
+ return this.config.media.driver.list({ engine: this });
989
+ }
990
+ const res = await this.fetchApi("listMedia");
991
+ return res.files || [];
992
+ }
993
+ async deleteMedia(filename) {
994
+ if (this.config?.media?.driver?.delete) {
995
+ return this.config.media.driver.delete(filename, { engine: this });
996
+ }
997
+ await this.fetchApi("deleteMedia", { file: filename });
998
+ }
999
+ async renameMedia(oldFilename, newFilename) {
1000
+ if (this.config?.media?.driver?.rename) {
1001
+ return this.config.media.driver.rename(oldFilename, newFilename, { engine: this });
1002
+ }
1003
+ await this.fetchApi("renameMedia", { oldFile: oldFilename, newFile: newFilename });
1004
+ }
1005
+ async pruneMedia() {
1006
+ return this.fetchApi("pruneMedia");
1007
+ }
1008
+ async getMediaUrl(relativePath) {
1009
+ return relativePath;
1010
+ }
1011
+ async list(collection, options) {
1012
+ return this.fetchApi("list", { collection, options });
1013
+ }
1014
+ async getHistory(_collection, _slug) {
1015
+ return this.fetchApi("getHistory", { collection: _collection, slug: _slug }).catch(() => []);
1016
+ }
1017
+ async getVersionContent(commitOid, collection, slug) {
1018
+ return this.fetchApi("getVersionContent", { commitOid, collection, slug }).catch(() => null);
1019
+ }
1020
+ };
1021
+
1022
+ // src/license.ts
1023
+ var VIBE_PUBLIC_KEY_B64 = "MCowBQYDK2VwAyEAhoV+ydoo3GG0wM0tjBmtBWKMaHo7gVg25J5lsSfoBI0=";
1024
+ var VALIDATION_API = "https://api.vibecms.com/v1/license/validate";
1025
+ var _remoteCache = null;
1026
+ var REMOTE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
1027
+ var _revokedAt = null;
1028
+ var GRACE_PERIOD_MS = 72 * 60 * 60 * 1e3;
1029
+ function base64urlDecode(str) {
1030
+ let b64 = str.replace(/-/g, "+").replace(/_/g, "/");
1031
+ while (b64.length % 4 !== 0) b64 += "=";
1032
+ if (typeof Buffer !== "undefined") {
1033
+ return new Uint8Array(Buffer.from(b64, "base64"));
1034
+ }
1035
+ const binary = atob(b64);
1036
+ const bytes = new Uint8Array(binary.length);
1037
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
1038
+ return bytes;
1039
+ }
1040
+ function validateLicenseLocal(key) {
1041
+ if (!key || !key.startsWith("vibe_")) {
1042
+ throw new Error(
1043
+ '[VibeCMS] Invalid license key format. Keys start with "vibe_".\nGet your key at https://vibecms.com/pricing'
1044
+ );
1045
+ }
1046
+ const body = key.slice(5);
1047
+ const dotIndex = body.lastIndexOf(".");
1048
+ if (dotIndex === -1) {
1049
+ throw new Error(
1050
+ "[VibeCMS] Malformed license key. Expected format: vibe_<payload>.<signature>\nGet a new key at https://vibecms.com/pricing"
1051
+ );
1052
+ }
1053
+ const payloadB64 = body.slice(0, dotIndex);
1054
+ const signatureB64 = body.slice(dotIndex + 1);
1055
+ let payload;
1056
+ try {
1057
+ const payloadBytes = base64urlDecode(payloadB64);
1058
+ const payloadJson = new TextDecoder().decode(payloadBytes);
1059
+ payload = JSON.parse(payloadJson);
1060
+ } catch {
1061
+ throw new Error(
1062
+ "[VibeCMS] Invalid license key. Could not decode payload.\nCheck your key or get a new one at https://vibecms.com/pricing"
1063
+ );
1064
+ }
1065
+ if (!payload.sub || !payload.plan || !payload.iat || !payload.exp || !payload.kid) {
1066
+ throw new Error(
1067
+ "[VibeCMS] Invalid license key. Missing required fields.\nGet a new key at https://vibecms.com/pricing"
1068
+ );
1069
+ }
1070
+ try {
1071
+ const crypto = require("crypto");
1072
+ const pubKeyDer = Buffer.from(VIBE_PUBLIC_KEY_B64, "base64");
1073
+ const publicKey = crypto.createPublicKey({
1074
+ key: pubKeyDer,
1075
+ format: "der",
1076
+ type: "spki"
1077
+ });
1078
+ const payloadBytes = base64urlDecode(payloadB64);
1079
+ const signatureBytes = base64urlDecode(signatureB64);
1080
+ const isValid = crypto.verify(null, payloadBytes, publicKey, signatureBytes);
1081
+ if (!isValid) {
1082
+ throw new Error("signature mismatch");
1083
+ }
1084
+ } catch (e) {
1085
+ if (e.message?.includes("[VibeCMS]")) throw e;
1086
+ throw new Error(
1087
+ "[VibeCMS] Invalid license key. Signature verification failed.\nCheck your key or get a new one at https://vibecms.com/pricing"
1088
+ );
1089
+ }
1090
+ const nowSec = Math.floor(Date.now() / 1e3);
1091
+ if (payload.exp < nowSec) {
1092
+ const expDate = new Date(payload.exp * 1e3).toLocaleDateString("en-US", {
1093
+ year: "numeric",
1094
+ month: "long",
1095
+ day: "numeric"
1096
+ });
1097
+ throw new Error(
1098
+ `[VibeCMS] License expired on ${expDate}.
1099
+ Renew at https://vibecms.com/account`
1100
+ );
1101
+ }
1102
+ return payload;
1103
+ }
1104
+ async function validateLicenseRemote(key, version) {
1105
+ if (_remoteCache && Date.now() - _remoteCache.checkedAt < REMOTE_CACHE_TTL_MS) {
1106
+ if (!_remoteCache.valid) {
1107
+ handleRevocation();
1108
+ }
1109
+ return;
1110
+ }
1111
+ try {
1112
+ const res = await fetch(VALIDATION_API, {
1113
+ method: "POST",
1114
+ headers: { "Content-Type": "application/json" },
1115
+ body: JSON.stringify({ key, version }),
1116
+ signal: AbortSignal.timeout(5e3)
1117
+ // 5s timeout
1118
+ });
1119
+ if (res.ok) {
1120
+ const data = await res.json();
1121
+ _remoteCache = {
1122
+ valid: data.valid !== false,
1123
+ reason: data.reason,
1124
+ checkedAt: Date.now()
1125
+ };
1126
+ if (!_remoteCache.valid) {
1127
+ handleRevocation();
1128
+ } else {
1129
+ _revokedAt = null;
1130
+ }
1131
+ }
1132
+ } catch {
1133
+ }
1134
+ }
1135
+ function handleRevocation() {
1136
+ if (!_revokedAt) {
1137
+ _revokedAt = Date.now();
1138
+ console.warn(
1139
+ "[VibeCMS] \u26A0 Warning: Your license key has been revoked.\nVibeCMS will stop working in 72 hours.\nContact support@vibecms.com or visit https://vibecms.com/account"
1140
+ );
1141
+ }
1142
+ const elapsed = Date.now() - _revokedAt;
1143
+ if (elapsed > GRACE_PERIOD_MS) {
1144
+ throw new Error(
1145
+ "[VibeCMS] License has been revoked and the grace period has expired.\nContact support@vibecms.com or get a new key at https://vibecms.com/pricing"
1146
+ );
1147
+ }
1148
+ }
1149
+ function resolveLicenseKey(configKey) {
1150
+ const key = (typeof process !== "undefined" ? process.env?.VIBE_LICENSE_KEY : void 0) ?? configKey;
1151
+ if (!key) {
1152
+ throw new Error(
1153
+ "[VibeCMS] License key required.\nSet the VIBE_LICENSE_KEY environment variable or pass licenseKey in defineConfig().\nGet your free key at https://vibecms.com/pricing"
1154
+ );
1155
+ }
1156
+ return key;
1157
+ }
1158
+
1159
+ // src/engine/index.ts
1160
+ var _licensed = false;
1161
+ var getEngine = () => {
1162
+ if (typeof window === "undefined") return new NodeEngine();
1163
+ const isLocal = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
1164
+ return isLocal ? new DevEngine() : new BrowserEngine();
1165
+ };
1166
+ var _raw = getEngine();
1167
+ var VibeEngine = new Proxy(_raw, {
1168
+ get(target, prop, receiver) {
1169
+ const value = Reflect.get(target, prop, receiver);
1170
+ if (!_licensed && typeof value === "function" && prop !== "init") {
1171
+ return (...args) => {
1172
+ throw new Error(
1173
+ "[VibeCMS] License key required. Call initVibeEngine() with a valid key.\nGet your key at https://vibecms.com/pricing"
1174
+ );
1175
+ };
1176
+ }
1177
+ return value;
1178
+ }
1179
+ });
1180
+ function initVibeEngine(config, collections, singletons) {
1181
+ const key = resolveLicenseKey(config.licenseKey);
1182
+ validateLicenseLocal(key);
1183
+ _licensed = true;
1184
+ validateLicenseRemote(key, "0.1.0").catch(() => {
1185
+ });
1186
+ if (collections || singletons) {
1187
+ registerCollectionSchemas(collections, singletons);
1188
+ }
1189
+ return VibeEngine.init(config);
1190
+ }
1191
+ async function populate(document, refs) {
1192
+ if (!document) return document;
1193
+ const doc = { ...document };
1194
+ for (const ref of refs) {
1195
+ if (typeof doc[ref.field] === "string") {
1196
+ const data = await VibeEngine.read(ref.collection, doc[ref.field]);
1197
+ if (data) doc[ref.field] = data;
1198
+ } else if (Array.isArray(doc[ref.field])) {
1199
+ doc[ref.field] = await Promise.all(doc[ref.field].map(async (item) => {
1200
+ if (typeof item === "string") {
1201
+ const data = await VibeEngine.read(ref.collection, item);
1202
+ return data || item;
1203
+ }
1204
+ return item;
1205
+ }));
1206
+ }
1207
+ }
1208
+ return doc;
1209
+ }
1210
+ // Annotate the CommonJS export names for ESM import in node:
1211
+ 0 && (module.exports = {
1212
+ BrowserEngine,
1213
+ DevEngine,
1214
+ NodeEngine,
1215
+ VibeEngine,
1216
+ assertValidCollection,
1217
+ assertValidCommitOid,
1218
+ assertValidSlug,
1219
+ initVibeEngine,
1220
+ populate,
1221
+ sanitizeRedirectPath,
1222
+ validateCollection,
1223
+ validateCommitOid,
1224
+ validateSlug
1225
+ });
1226
+ //# sourceMappingURL=index.js.map