@gravito/monolith 1.0.0-alpha.2

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/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@gravito/monolith",
3
+ "version": "1.0.0-alpha.2",
4
+ "description": "Content management and Markdown rendering orbit for Gravito",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "bun build ./src/index.ts --outdir ./dist --target node",
9
+ "test": "bun test"
10
+ },
11
+ "keywords": [
12
+ "gravito",
13
+ "orbit",
14
+ "content",
15
+ "markdown",
16
+ "cms"
17
+ ],
18
+ "author": "Carl Lee <carllee0520@gmail.com>",
19
+ "license": "MIT",
20
+ "peerDependencies": {
21
+ "gravito-core": "1.0.0-beta.2"
22
+ },
23
+ "dependencies": {
24
+ "marked": "^11.1.1",
25
+ "gray-matter": "^4.0.3"
26
+ },
27
+ "devDependencies": {
28
+ "bun-types": "latest",
29
+ "@types/marked": "^5.0.0"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "homepage": "https://github.com/gravito-framework/gravito#readme",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/gravito-framework/gravito.git",
38
+ "directory": "packages/monolith"
39
+ }
40
+ }
@@ -0,0 +1,116 @@
1
+ import { readdir, readFile, stat } from 'node:fs/promises'
2
+ import { join, parse } from 'node:path'
3
+ import matter from 'gray-matter'
4
+ import { marked } from 'marked'
5
+
6
+ export interface ContentItem {
7
+ slug: string
8
+ body: string // The HTML content
9
+ excerpt?: string
10
+ // biome-ignore lint/suspicious/noExplicitAny: Frontmatter can be anything
11
+ meta: Record<string, any> // Frontmatter data
12
+ raw: string // The raw markdown
13
+ }
14
+
15
+ export interface CollectionConfig {
16
+ path: string // e.g., 'resources/content/docs'
17
+ }
18
+
19
+ export class ContentManager {
20
+ private collections = new Map<string, CollectionConfig>()
21
+ // Simple memory cache: collection:locale:slug -> ContentItem
22
+ private cache = new Map<string, ContentItem>()
23
+
24
+ constructor(private rootDir: string) {}
25
+
26
+ /**
27
+ * Register a new content collection
28
+ */
29
+ defineCollection(name: string, config: CollectionConfig) {
30
+ this.collections.set(name, config)
31
+ }
32
+
33
+ /**
34
+ * Find a single content item
35
+ * @param collectionName The collection name (e.g. 'docs')
36
+ * @param slug The file slug (e.g. 'installation')
37
+ * @param locale The locale (e.g. 'en')
38
+ */
39
+ async find(collectionName: string, slug: string, locale = 'en'): Promise<ContentItem | null> {
40
+ const config = this.collections.get(collectionName)
41
+ if (!config) {
42
+ throw new Error(`Collection '${collectionName}' not defined`)
43
+ }
44
+
45
+ const cacheKey = `${collectionName}:${locale}:${slug}`
46
+ if (this.cache.has(cacheKey)) {
47
+ return this.cache.get(cacheKey)!
48
+ }
49
+
50
+ // Determine path strategy
51
+ // Strategy: {root}/{path}/{locale}/{slug}.md
52
+ const filePath = join(this.rootDir, config.path, locale, `${slug}.md`)
53
+
54
+ try {
55
+ const exists = await stat(filePath)
56
+ .then(() => true)
57
+ .catch(() => false)
58
+ if (!exists) {
59
+ return null
60
+ }
61
+
62
+ const fileContent = await readFile(filePath, 'utf-8')
63
+ const { data, content, exemption } = matter(fileContent)
64
+
65
+ const html = await marked.parse(content)
66
+
67
+ const item: ContentItem = {
68
+ slug,
69
+ body: html,
70
+ meta: data,
71
+ raw: content,
72
+ excerpt: exemption,
73
+ }
74
+
75
+ this.cache.set(cacheKey, item)
76
+ return item
77
+ } catch (e) {
78
+ console.error(`[Orbit-Content] Error reading file: ${filePath}`, e)
79
+ return null
80
+ }
81
+ }
82
+
83
+ /**
84
+ * List all items in a collection for a locale
85
+ * Useful for generating sitemaps or index pages
86
+ */
87
+ async list(collectionName: string, locale = 'en'): Promise<ContentItem[]> {
88
+ const config = this.collections.get(collectionName)
89
+ if (!config) {
90
+ throw new Error(`Collection '${collectionName}' not defined`)
91
+ }
92
+
93
+ const dirPath = join(this.rootDir, config.path, locale)
94
+
95
+ try {
96
+ const files = await readdir(dirPath)
97
+ const items: ContentItem[] = []
98
+
99
+ for (const file of files) {
100
+ if (!file.endsWith('.md')) {
101
+ continue
102
+ }
103
+ const slug = parse(file).name
104
+ const item = await this.find(collectionName, slug, locale)
105
+ if (item) {
106
+ items.push(item)
107
+ }
108
+ }
109
+
110
+ return items
111
+ } catch (_e) {
112
+ // Directory likely doesn't exist for this locale
113
+ return []
114
+ }
115
+ }
116
+ }
package/src/index.ts ADDED
@@ -0,0 +1,42 @@
1
+ import type { GravitoOrbit, PlanetCore } from 'gravito-core'
2
+ import { type CollectionConfig, ContentManager } from './ContentManager'
3
+
4
+ declare module 'gravito-core' {
5
+ interface Variables {
6
+ content: ContentManager
7
+ }
8
+ }
9
+
10
+ export interface ContentConfig {
11
+ root?: string // Defaults to process.cwd()
12
+ collections?: Record<string, CollectionConfig>
13
+ }
14
+
15
+ export class OrbitMonolith implements GravitoOrbit {
16
+ constructor(private config: ContentConfig = {}) {}
17
+
18
+ install(core: PlanetCore): void {
19
+ const root = this.config.root || process.cwd()
20
+ const manager = new ContentManager(root)
21
+
22
+ // Register Collections from Config
23
+ if (this.config.collections) {
24
+ for (const [name, config] of Object.entries(this.config.collections)) {
25
+ manager.defineCollection(name, config)
26
+ }
27
+ }
28
+
29
+ // Register Default 'docs' and 'blog' if folders exist?
30
+ // Let's stick to explicit configuration for now to avoid magic.
31
+
32
+ // Inject into request context
33
+ core.adapter.use('*', async (c, next) => {
34
+ c.set('content', manager)
35
+ await next()
36
+ })
37
+
38
+ core.logger.info('Orbit Monolith installed ⬛️')
39
+ }
40
+ }
41
+
42
+ export * from './ContentManager'
@@ -0,0 +1,41 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { join } from 'node:path'
3
+ import { ContentManager } from '../src/ContentManager'
4
+
5
+ describe('Orbit Content Manager', () => {
6
+ const rootDir = join(import.meta.dir, 'fixtures')
7
+ const manager = new ContentManager(rootDir)
8
+
9
+ manager.defineCollection('docs', {
10
+ path: 'docs',
11
+ })
12
+
13
+ test('it reads markdown content with frontmatter', async () => {
14
+ const item = await manager.find('docs', 'install', 'en')
15
+
16
+ expect(item).not.toBeNull()
17
+ expect(item?.slug).toBe('install')
18
+ expect(item?.meta.title).toBe('Installation Guide')
19
+ expect(item?.body).toContain('<h1>Installation</h1>')
20
+ expect(item?.body).toContain('<pre><code class="language-bash">bun add @gravito/core')
21
+ })
22
+
23
+ test('it respects locale', async () => {
24
+ const item = await manager.find('docs', 'install', 'zh')
25
+
26
+ expect(item).not.toBeNull()
27
+ expect(item?.meta.title).toBe('安裝指南')
28
+ expect(item?.body).toContain('<h1>安裝</h1>')
29
+ })
30
+
31
+ test('it returns null for missing files', async () => {
32
+ const item = await manager.find('docs', 'missing', 'en')
33
+ expect(item).toBeNull()
34
+ })
35
+
36
+ test('it lists items in collection', async () => {
37
+ const items = await manager.list('docs', 'en')
38
+ expect(items.length).toBeGreaterThan(0)
39
+ expect(items[0].slug).toBe('install')
40
+ })
41
+ })
@@ -0,0 +1,13 @@
1
+ ---
2
+ title: Installation Guide
3
+ description: How to install Gravito
4
+ date: 2024-01-01
5
+ ---
6
+
7
+ # Installation
8
+
9
+ To install Gravito, run:
10
+
11
+ ```bash
12
+ bun add @gravito/core
13
+ ```
@@ -0,0 +1,13 @@
1
+ ---
2
+ title: 安裝指南
3
+ description: 如何安裝 Gravito
4
+ date: 2024-01-01
5
+ ---
6
+
7
+ # 安裝
8
+
9
+ 執行以下指令安裝:
10
+
11
+ ```bash
12
+ bun add @gravito/core
13
+ ```
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "baseUrl": ".",
6
+ "paths": {
7
+ "gravito-core": ["../../packages/core/src/index.ts"],
8
+ "@gravito/*": ["../../packages/*/src/index.ts"]
9
+ }
10
+ },
11
+ "include": [
12
+ "src/**/*"
13
+ ],
14
+ "exclude": [
15
+ "node_modules",
16
+ "dist",
17
+ "**/*.test.ts"
18
+ ]
19
+ }