@gravito/monolith 3.0.0 → 3.2.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.
- package/CHANGELOG.md +15 -0
- package/dist/index.cjs +298 -13
- package/dist/index.d.cts +113 -4
- package/dist/index.d.ts +113 -4
- package/dist/index.js +296 -13
- package/ion/src/index.js +2775 -2559
- package/package.json +11 -7
- package/scripts/check-coverage.ts +64 -0
- package/src/ContentManager.ts +115 -13
- package/src/ContentWatcher.ts +123 -0
- package/src/Controller.ts +17 -0
- package/src/FormRequest.ts +12 -1
- package/src/Router.ts +4 -0
- package/src/driver/ContentDriver.ts +5 -0
- package/src/driver/GitHubDriver.ts +80 -0
- package/src/driver/LocalDriver.ts +30 -0
- package/src/index.ts +38 -1
- package/tests/content-cache.test.ts +36 -0
- package/tests/content-search.test.ts +79 -0
- package/tests/content-watcher.test.ts +56 -0
- package/tests/content.test.ts +2 -1
- package/tests/extra.test.ts +2 -1
- package/tests/hot-reload.test.ts +74 -0
- package/tsconfig.json +13 -19
- package/dist/src/index.js +0 -5624
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,16 @@ import { TSchema } from '@gravito/mass';
|
|
|
4
4
|
export { Schema } from '@gravito/mass';
|
|
5
5
|
import { Hono } from 'hono';
|
|
6
6
|
|
|
7
|
+
interface ContentDriver {
|
|
8
|
+
read(path: string): Promise<string>;
|
|
9
|
+
exists(path: string): Promise<boolean>;
|
|
10
|
+
list(dir: string): Promise<string[]>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Represents a single content item (file).
|
|
15
|
+
* @public
|
|
16
|
+
*/
|
|
7
17
|
interface ContentItem {
|
|
8
18
|
slug: string;
|
|
9
19
|
body: string;
|
|
@@ -11,20 +21,42 @@ interface ContentItem {
|
|
|
11
21
|
meta: Record<string, any>;
|
|
12
22
|
raw: string;
|
|
13
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Configuration for a content collection.
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
14
28
|
interface CollectionConfig {
|
|
15
29
|
path: string;
|
|
16
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Manages fetching, parsing, and caching of filesystem-based content.
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
17
35
|
declare class ContentManager {
|
|
18
|
-
private
|
|
36
|
+
private readonly driver;
|
|
19
37
|
private collections;
|
|
20
38
|
private cache;
|
|
39
|
+
private searchIndex;
|
|
21
40
|
private renderer;
|
|
41
|
+
/**
|
|
42
|
+
* Clear all cached content.
|
|
43
|
+
* Useful for hot reload during development.
|
|
44
|
+
*/
|
|
45
|
+
clearCache(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Invalidate a specific content item.
|
|
48
|
+
* @param collection - The collection name.
|
|
49
|
+
* @param slug - The file slug.
|
|
50
|
+
* @param locale - The locale. Defaults to 'en'.
|
|
51
|
+
*/
|
|
52
|
+
invalidate(collection: string, slug: string, locale?: string): void;
|
|
53
|
+
getCollectionConfig(name: string): CollectionConfig | undefined;
|
|
22
54
|
/**
|
|
23
55
|
* Create a new ContentManager instance.
|
|
24
56
|
*
|
|
25
|
-
* @param
|
|
57
|
+
* @param driver - The content driver to use.
|
|
26
58
|
*/
|
|
27
|
-
constructor(
|
|
59
|
+
constructor(driver: ContentDriver);
|
|
28
60
|
/**
|
|
29
61
|
* Register a new content collection.
|
|
30
62
|
*
|
|
@@ -52,6 +84,18 @@ declare class ContentManager {
|
|
|
52
84
|
* @throws {Error} If the collection is not defined.
|
|
53
85
|
*/
|
|
54
86
|
list(collectionName: string, locale?: string): Promise<ContentItem[]>;
|
|
87
|
+
/**
|
|
88
|
+
* Search for content items across collections and locales.
|
|
89
|
+
*
|
|
90
|
+
* @param query - The search query.
|
|
91
|
+
* @param options - Optional filters for collection and locale.
|
|
92
|
+
* @returns An array of matching ContentItems.
|
|
93
|
+
*/
|
|
94
|
+
search(query: string, options?: {
|
|
95
|
+
collection?: string;
|
|
96
|
+
locale?: string;
|
|
97
|
+
}): ContentItem[];
|
|
98
|
+
private buildSearchIndex;
|
|
55
99
|
private sanitizeSegment;
|
|
56
100
|
private escapeHtml;
|
|
57
101
|
private isSafeUrl;
|
|
@@ -67,10 +111,27 @@ declare class Sanitizer {
|
|
|
67
111
|
static clean(data: any): any;
|
|
68
112
|
}
|
|
69
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Base class for all Monolith Controllers.
|
|
116
|
+
*
|
|
117
|
+
* Provides basic functionality for calling actions and sanitizing data.
|
|
118
|
+
*
|
|
119
|
+
* @public
|
|
120
|
+
* @since 3.0.0
|
|
121
|
+
*/
|
|
70
122
|
declare abstract class BaseController {
|
|
71
123
|
protected sanitizer: Sanitizer;
|
|
72
124
|
call(ctx: GravitoContext, method: string): Promise<Response>;
|
|
73
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Controller class with request context awareness and helper methods.
|
|
128
|
+
*
|
|
129
|
+
* This class provides a more feature-rich base for controllers that
|
|
130
|
+
* need direct access to the request context and common response helpers.
|
|
131
|
+
*
|
|
132
|
+
* @public
|
|
133
|
+
* @since 3.0.0
|
|
134
|
+
*/
|
|
74
135
|
declare abstract class Controller {
|
|
75
136
|
protected context: GravitoContext;
|
|
76
137
|
/**
|
|
@@ -107,6 +168,40 @@ declare abstract class Controller {
|
|
|
107
168
|
static call(method: string): any;
|
|
108
169
|
}
|
|
109
170
|
|
|
171
|
+
interface GitHubDriverOptions {
|
|
172
|
+
owner: string;
|
|
173
|
+
repo: string;
|
|
174
|
+
ref?: string;
|
|
175
|
+
auth?: string;
|
|
176
|
+
}
|
|
177
|
+
declare class GitHubDriver implements ContentDriver {
|
|
178
|
+
private octokit;
|
|
179
|
+
private owner;
|
|
180
|
+
private repo;
|
|
181
|
+
private ref?;
|
|
182
|
+
constructor(options: GitHubDriverOptions);
|
|
183
|
+
read(path: string): Promise<string>;
|
|
184
|
+
exists(path: string): Promise<boolean>;
|
|
185
|
+
list(dir: string): Promise<string[]>;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
declare class LocalDriver implements ContentDriver {
|
|
189
|
+
private rootDir;
|
|
190
|
+
constructor(rootDir: string);
|
|
191
|
+
read(path: string): Promise<string>;
|
|
192
|
+
exists(path: string): Promise<boolean>;
|
|
193
|
+
list(dir: string): Promise<string[]>;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Base class for Monolith Form Requests.
|
|
198
|
+
*
|
|
199
|
+
* Provides a structured way to handle request validation and authorization
|
|
200
|
+
* for the Monolith architecture.
|
|
201
|
+
*
|
|
202
|
+
* @public
|
|
203
|
+
* @since 3.0.0
|
|
204
|
+
*/
|
|
110
205
|
declare abstract class FormRequest {
|
|
111
206
|
protected context: GravitoContext;
|
|
112
207
|
/**
|
|
@@ -135,6 +230,10 @@ declare abstract class FormRequest {
|
|
|
135
230
|
static middleware(): any;
|
|
136
231
|
}
|
|
137
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Utility for registering resourceful routes.
|
|
235
|
+
* @public
|
|
236
|
+
*/
|
|
138
237
|
declare class RouterHelper {
|
|
139
238
|
/**
|
|
140
239
|
* Register standard resource routes for a controller.
|
|
@@ -155,14 +254,24 @@ declare module '@gravito/core' {
|
|
|
155
254
|
content: ContentManager;
|
|
156
255
|
}
|
|
157
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* Configuration for Orbit Monolith (Content Engine).
|
|
259
|
+
* @public
|
|
260
|
+
*/
|
|
158
261
|
interface ContentConfig {
|
|
159
262
|
root?: string;
|
|
263
|
+
driver?: ContentDriver;
|
|
160
264
|
collections?: Record<string, CollectionConfig>;
|
|
161
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* Orbit Monolith Service.
|
|
268
|
+
* Provides flat-file CMS capabilities to Gravito applications.
|
|
269
|
+
* @public
|
|
270
|
+
*/
|
|
162
271
|
declare class OrbitMonolith implements GravitoOrbit {
|
|
163
272
|
private config;
|
|
164
273
|
constructor(config?: ContentConfig);
|
|
165
274
|
install(core: PlanetCore): void;
|
|
166
275
|
}
|
|
167
276
|
|
|
168
|
-
export { BaseController, type CollectionConfig, type ContentConfig, type ContentItem, ContentManager, Controller, FormRequest, OrbitMonolith, RouterHelper as Route };
|
|
277
|
+
export { BaseController, type CollectionConfig, type ContentConfig, type ContentDriver, type ContentItem, ContentManager, Controller, FormRequest, GitHubDriver, type GitHubDriverOptions, LocalDriver, OrbitMonolith, RouterHelper as Route };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// src/ContentManager.ts
|
|
2
|
-
import { readdir, readFile, stat } from "fs/promises";
|
|
3
2
|
import { join, parse } from "path";
|
|
4
3
|
import matter from "gray-matter";
|
|
5
4
|
import { marked } from "marked";
|
|
@@ -7,14 +6,16 @@ var ContentManager = class {
|
|
|
7
6
|
/**
|
|
8
7
|
* Create a new ContentManager instance.
|
|
9
8
|
*
|
|
10
|
-
* @param
|
|
9
|
+
* @param driver - The content driver to use.
|
|
11
10
|
*/
|
|
12
|
-
constructor(
|
|
13
|
-
this.
|
|
11
|
+
constructor(driver) {
|
|
12
|
+
this.driver = driver;
|
|
14
13
|
}
|
|
15
14
|
collections = /* @__PURE__ */ new Map();
|
|
16
15
|
// Simple memory cache: collection:locale:slug -> ContentItem
|
|
17
16
|
cache = /* @__PURE__ */ new Map();
|
|
17
|
+
// In-memory search index: term -> Set<cacheKey>
|
|
18
|
+
searchIndex = /* @__PURE__ */ new Map();
|
|
18
19
|
renderer = (() => {
|
|
19
20
|
const renderer = new marked.Renderer();
|
|
20
21
|
renderer.html = (html) => this.escapeHtml(html);
|
|
@@ -28,6 +29,31 @@ var ContentManager = class {
|
|
|
28
29
|
};
|
|
29
30
|
return renderer;
|
|
30
31
|
})();
|
|
32
|
+
/**
|
|
33
|
+
* Clear all cached content.
|
|
34
|
+
* Useful for hot reload during development.
|
|
35
|
+
*/
|
|
36
|
+
clearCache() {
|
|
37
|
+
this.cache.clear();
|
|
38
|
+
this.searchIndex.clear();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Invalidate a specific content item.
|
|
42
|
+
* @param collection - The collection name.
|
|
43
|
+
* @param slug - The file slug.
|
|
44
|
+
* @param locale - The locale. Defaults to 'en'.
|
|
45
|
+
*/
|
|
46
|
+
invalidate(collection, slug, locale = "en") {
|
|
47
|
+
const safeSlug = this.sanitizeSegment(slug);
|
|
48
|
+
const safeLocale = this.sanitizeSegment(locale);
|
|
49
|
+
if (safeSlug && safeLocale) {
|
|
50
|
+
const cacheKey = `${collection}:${safeLocale}:${safeSlug}`;
|
|
51
|
+
this.cache.delete(cacheKey);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
getCollectionConfig(name) {
|
|
55
|
+
return this.collections.get(name);
|
|
56
|
+
}
|
|
31
57
|
/**
|
|
32
58
|
* Register a new content collection.
|
|
33
59
|
*
|
|
@@ -57,16 +83,17 @@ var ContentManager = class {
|
|
|
57
83
|
return null;
|
|
58
84
|
}
|
|
59
85
|
const cacheKey = `${collectionName}:${locale}:${slug}`;
|
|
60
|
-
|
|
61
|
-
|
|
86
|
+
const cachedItem = this.cache.get(cacheKey);
|
|
87
|
+
if (cachedItem) {
|
|
88
|
+
return cachedItem;
|
|
62
89
|
}
|
|
63
|
-
const filePath = join(
|
|
90
|
+
const filePath = join(config.path, safeLocale, `${safeSlug}.md`);
|
|
64
91
|
try {
|
|
65
|
-
const exists = await
|
|
92
|
+
const exists = await this.driver.exists(filePath);
|
|
66
93
|
if (!exists) {
|
|
67
94
|
return null;
|
|
68
95
|
}
|
|
69
|
-
const fileContent = await
|
|
96
|
+
const fileContent = await this.driver.read(filePath);
|
|
70
97
|
const { data, content, excerpt } = matter(fileContent);
|
|
71
98
|
const html = await marked.parse(content, { renderer: this.renderer });
|
|
72
99
|
const item = {
|
|
@@ -77,6 +104,7 @@ var ContentManager = class {
|
|
|
77
104
|
excerpt
|
|
78
105
|
};
|
|
79
106
|
this.cache.set(cacheKey, item);
|
|
107
|
+
this.buildSearchIndex(cacheKey, item);
|
|
80
108
|
return item;
|
|
81
109
|
} catch (e) {
|
|
82
110
|
console.error(`[Orbit-Content] Error reading file: ${filePath}`, e);
|
|
@@ -101,9 +129,9 @@ var ContentManager = class {
|
|
|
101
129
|
if (!safeLocale) {
|
|
102
130
|
return [];
|
|
103
131
|
}
|
|
104
|
-
const dirPath = join(
|
|
132
|
+
const dirPath = join(config.path, safeLocale);
|
|
105
133
|
try {
|
|
106
|
-
const files = await
|
|
134
|
+
const files = await this.driver.list(dirPath);
|
|
107
135
|
const items = [];
|
|
108
136
|
for (const file of files) {
|
|
109
137
|
if (!file.endsWith(".md")) {
|
|
@@ -120,6 +148,54 @@ var ContentManager = class {
|
|
|
120
148
|
return [];
|
|
121
149
|
}
|
|
122
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Search for content items across collections and locales.
|
|
153
|
+
*
|
|
154
|
+
* @param query - The search query.
|
|
155
|
+
* @param options - Optional filters for collection and locale.
|
|
156
|
+
* @returns An array of matching ContentItems.
|
|
157
|
+
*/
|
|
158
|
+
search(query, options = {}) {
|
|
159
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
160
|
+
if (terms.length === 0) {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
const matches = /* @__PURE__ */ new Set();
|
|
164
|
+
for (const term of terms) {
|
|
165
|
+
const keys = this.searchIndex.get(term);
|
|
166
|
+
if (keys) {
|
|
167
|
+
for (const key of keys) {
|
|
168
|
+
matches.add(key);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const results = [];
|
|
173
|
+
for (const key of matches) {
|
|
174
|
+
const item = this.cache.get(key);
|
|
175
|
+
if (!item) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const [collection, locale] = key.split(":");
|
|
179
|
+
if (options.collection && options.collection !== collection) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (options.locale && options.locale !== locale) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
results.push(item);
|
|
186
|
+
}
|
|
187
|
+
return results;
|
|
188
|
+
}
|
|
189
|
+
buildSearchIndex(cacheKey, item) {
|
|
190
|
+
const text = `${item.slug} ${item.meta.title || ""} ${item.raw} ${item.excerpt || ""}`.toLowerCase();
|
|
191
|
+
const terms = text.split(/[^\w\d]+/).filter((t) => t.length > 2);
|
|
192
|
+
for (const term of terms) {
|
|
193
|
+
if (!this.searchIndex.has(term)) {
|
|
194
|
+
this.searchIndex.set(term, /* @__PURE__ */ new Set());
|
|
195
|
+
}
|
|
196
|
+
this.searchIndex.get(term)?.add(cacheKey);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
123
199
|
sanitizeSegment(value) {
|
|
124
200
|
if (!value) {
|
|
125
201
|
return null;
|
|
@@ -156,6 +232,128 @@ var ContentManager = class {
|
|
|
156
232
|
}
|
|
157
233
|
};
|
|
158
234
|
|
|
235
|
+
// src/ContentWatcher.ts
|
|
236
|
+
import { readdirSync, watch } from "fs";
|
|
237
|
+
import { join as join2 } from "path";
|
|
238
|
+
var ContentWatcher = class {
|
|
239
|
+
constructor(contentManager, rootDir, options = {}) {
|
|
240
|
+
this.contentManager = contentManager;
|
|
241
|
+
this.rootDir = rootDir;
|
|
242
|
+
this.options = options;
|
|
243
|
+
}
|
|
244
|
+
watchers = [];
|
|
245
|
+
debounceTimers = /* @__PURE__ */ new Map();
|
|
246
|
+
watch(collectionName) {
|
|
247
|
+
const config = this.contentManager.getCollectionConfig(collectionName);
|
|
248
|
+
if (!config) {
|
|
249
|
+
console.warn(`[ContentWatcher] Collection '${collectionName}' not found`);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const watchPath = join2(this.rootDir, config.path);
|
|
253
|
+
this.addWatcher(watchPath, collectionName);
|
|
254
|
+
try {
|
|
255
|
+
const entries = readdirSync(watchPath, { withFileTypes: true });
|
|
256
|
+
for (const entry of entries) {
|
|
257
|
+
if (entry.isDirectory()) {
|
|
258
|
+
this.addWatcher(join2(watchPath, entry.name), collectionName, entry.name);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
} catch (_e) {
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
addWatcher(watchPath, collection, localePrefix) {
|
|
265
|
+
try {
|
|
266
|
+
const watcher = watch(watchPath, { recursive: true }, (_eventType, filename) => {
|
|
267
|
+
if (!filename) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const key = `${collection}:${localePrefix || ""}:${filename}`;
|
|
271
|
+
if (this.debounceTimers.has(key)) {
|
|
272
|
+
clearTimeout(this.debounceTimers.get(key));
|
|
273
|
+
}
|
|
274
|
+
this.debounceTimers.set(
|
|
275
|
+
key,
|
|
276
|
+
setTimeout(() => {
|
|
277
|
+
this.handleFileChange(collection, filename.toString(), localePrefix);
|
|
278
|
+
this.debounceTimers.delete(key);
|
|
279
|
+
}, this.options.debounceMs ?? 100)
|
|
280
|
+
);
|
|
281
|
+
});
|
|
282
|
+
this.watchers.push(watcher);
|
|
283
|
+
} catch (_e) {
|
|
284
|
+
try {
|
|
285
|
+
const watcher = watch(watchPath, { recursive: false }, (_eventType, filename) => {
|
|
286
|
+
if (!filename) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
this.handleFileChange(collection, filename.toString(), localePrefix);
|
|
290
|
+
});
|
|
291
|
+
this.watchers.push(watcher);
|
|
292
|
+
} catch (err) {
|
|
293
|
+
console.error(`[ContentWatcher] Failed to watch ${watchPath}:`, err);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
handleFileChange(collection, filename, localePrefix) {
|
|
298
|
+
const parts = filename.split(/[/\\]/);
|
|
299
|
+
let locale;
|
|
300
|
+
let file;
|
|
301
|
+
if (parts.length >= 2) {
|
|
302
|
+
locale = parts[parts.length - 2];
|
|
303
|
+
file = parts[parts.length - 1];
|
|
304
|
+
} else if (localePrefix) {
|
|
305
|
+
locale = localePrefix;
|
|
306
|
+
file = parts[0];
|
|
307
|
+
} else {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (!file.endsWith(".md")) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const slug = file.replace(/\.md$/, "");
|
|
314
|
+
this.contentManager.invalidate(collection, slug, locale);
|
|
315
|
+
}
|
|
316
|
+
close() {
|
|
317
|
+
for (const watcher of this.watchers) {
|
|
318
|
+
watcher.close();
|
|
319
|
+
}
|
|
320
|
+
this.watchers = [];
|
|
321
|
+
for (const timer of this.debounceTimers.values()) {
|
|
322
|
+
clearTimeout(timer);
|
|
323
|
+
}
|
|
324
|
+
this.debounceTimers.clear();
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
// src/driver/LocalDriver.ts
|
|
329
|
+
import { readdir, readFile, stat } from "fs/promises";
|
|
330
|
+
import { join as join3 } from "path";
|
|
331
|
+
var LocalDriver = class {
|
|
332
|
+
constructor(rootDir) {
|
|
333
|
+
this.rootDir = rootDir;
|
|
334
|
+
}
|
|
335
|
+
async read(path) {
|
|
336
|
+
const fullPath = join3(this.rootDir, path);
|
|
337
|
+
return readFile(fullPath, "utf-8");
|
|
338
|
+
}
|
|
339
|
+
async exists(path) {
|
|
340
|
+
try {
|
|
341
|
+
await stat(join3(this.rootDir, path));
|
|
342
|
+
return true;
|
|
343
|
+
} catch {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async list(dir) {
|
|
348
|
+
try {
|
|
349
|
+
const fullPath = join3(this.rootDir, dir);
|
|
350
|
+
return await readdir(fullPath);
|
|
351
|
+
} catch {
|
|
352
|
+
return [];
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
159
357
|
// src/index.ts
|
|
160
358
|
import { Schema } from "@gravito/mass";
|
|
161
359
|
|
|
@@ -255,6 +453,69 @@ var Controller = class {
|
|
|
255
453
|
}
|
|
256
454
|
};
|
|
257
455
|
|
|
456
|
+
// src/driver/GitHubDriver.ts
|
|
457
|
+
import { Octokit } from "@octokit/rest";
|
|
458
|
+
var GitHubDriver = class {
|
|
459
|
+
octokit;
|
|
460
|
+
owner;
|
|
461
|
+
repo;
|
|
462
|
+
ref;
|
|
463
|
+
constructor(options) {
|
|
464
|
+
this.octokit = new Octokit({ auth: options.auth });
|
|
465
|
+
this.owner = options.owner;
|
|
466
|
+
this.repo = options.repo;
|
|
467
|
+
this.ref = options.ref;
|
|
468
|
+
}
|
|
469
|
+
async read(path) {
|
|
470
|
+
try {
|
|
471
|
+
const { data } = await this.octokit.rest.repos.getContent({
|
|
472
|
+
owner: this.owner,
|
|
473
|
+
repo: this.repo,
|
|
474
|
+
path,
|
|
475
|
+
ref: this.ref
|
|
476
|
+
});
|
|
477
|
+
if (Array.isArray(data) || !("content" in data)) {
|
|
478
|
+
throw new Error(`Path is not a file: ${path}`);
|
|
479
|
+
}
|
|
480
|
+
return Buffer.from(data.content, "base64").toString("utf-8");
|
|
481
|
+
} catch (e) {
|
|
482
|
+
if (e.status === 404) {
|
|
483
|
+
throw new Error(`File not found: ${path}`);
|
|
484
|
+
}
|
|
485
|
+
throw e;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
async exists(path) {
|
|
489
|
+
try {
|
|
490
|
+
await this.octokit.rest.repos.getContent({
|
|
491
|
+
owner: this.owner,
|
|
492
|
+
repo: this.repo,
|
|
493
|
+
path,
|
|
494
|
+
ref: this.ref
|
|
495
|
+
});
|
|
496
|
+
return true;
|
|
497
|
+
} catch {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
async list(dir) {
|
|
502
|
+
try {
|
|
503
|
+
const { data } = await this.octokit.rest.repos.getContent({
|
|
504
|
+
owner: this.owner,
|
|
505
|
+
repo: this.repo,
|
|
506
|
+
path: dir,
|
|
507
|
+
ref: this.ref
|
|
508
|
+
});
|
|
509
|
+
if (!Array.isArray(data)) {
|
|
510
|
+
return [];
|
|
511
|
+
}
|
|
512
|
+
return data.map((item) => item.name);
|
|
513
|
+
} catch {
|
|
514
|
+
return [];
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
|
|
258
519
|
// src/FormRequest.ts
|
|
259
520
|
import { validate } from "@gravito/mass";
|
|
260
521
|
var FormRequest = class {
|
|
@@ -303,7 +564,9 @@ var FormRequest = class {
|
|
|
303
564
|
for (const issue of issues) {
|
|
304
565
|
const path = Array.isArray(issue.path) ? issue.path.join(".") : issue.path || "root";
|
|
305
566
|
const key = path.replace(/^\//, "").replace(/\//g, ".");
|
|
306
|
-
if (!errors[key])
|
|
567
|
+
if (!errors[key]) {
|
|
568
|
+
errors[key] = [];
|
|
569
|
+
}
|
|
307
570
|
errors[key].push(issue.message || "Validation failed");
|
|
308
571
|
}
|
|
309
572
|
return ctx.json(
|
|
@@ -360,7 +623,16 @@ var OrbitMonolith = class {
|
|
|
360
623
|
}
|
|
361
624
|
install(core) {
|
|
362
625
|
const root = this.config.root || process.cwd();
|
|
363
|
-
|
|
626
|
+
let driver;
|
|
627
|
+
let isLocal = false;
|
|
628
|
+
if (this.config.driver) {
|
|
629
|
+
driver = this.config.driver;
|
|
630
|
+
isLocal = driver instanceof LocalDriver;
|
|
631
|
+
} else {
|
|
632
|
+
driver = new LocalDriver(root);
|
|
633
|
+
isLocal = true;
|
|
634
|
+
}
|
|
635
|
+
const manager = new ContentManager(driver);
|
|
364
636
|
if (this.config.collections) {
|
|
365
637
|
for (const [name, config] of Object.entries(this.config.collections)) {
|
|
366
638
|
manager.defineCollection(name, config);
|
|
@@ -370,6 +642,15 @@ var OrbitMonolith = class {
|
|
|
370
642
|
c.set("content", manager);
|
|
371
643
|
return await next();
|
|
372
644
|
});
|
|
645
|
+
if (process.env.NODE_ENV === "development" && isLocal) {
|
|
646
|
+
const watcher = new ContentWatcher(manager, root);
|
|
647
|
+
if (this.config.collections) {
|
|
648
|
+
for (const name of Object.keys(this.config.collections)) {
|
|
649
|
+
watcher.watch(name);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
core.logger.info("Orbit Monolith Hot Reload Active \u{1F525}");
|
|
653
|
+
}
|
|
373
654
|
core.logger.info("Orbit Monolith installed \u2B1B\uFE0F");
|
|
374
655
|
}
|
|
375
656
|
};
|
|
@@ -378,6 +659,8 @@ export {
|
|
|
378
659
|
ContentManager,
|
|
379
660
|
Controller,
|
|
380
661
|
FormRequest,
|
|
662
|
+
GitHubDriver,
|
|
663
|
+
LocalDriver,
|
|
381
664
|
OrbitMonolith,
|
|
382
665
|
RouterHelper as Route,
|
|
383
666
|
Schema
|