@zenithbuild/plugins 0.3.5 → 0.3.7

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.
@@ -129,7 +129,14 @@ jobs:
129
129
  # Match changed files to packages
130
130
  CHANGED_PKGS=""
131
131
  for pkg in $PACKAGES; do
132
- if echo "$CHANGED_FILES" | grep -q "^$pkg/"; then
132
+ MATCHED=false
133
+ if [ "$pkg" = "." ]; then
134
+ if [ -n "$CHANGED_FILES" ]; then MATCHED=true; fi
135
+ elif echo "$CHANGED_FILES" | grep -q "^$pkg/"; then
136
+ MATCHED=true
137
+ fi
138
+
139
+ if [ "$MATCHED" = "true" ]; then
133
140
  if [ -z "$CHANGED_PKGS" ]; then
134
141
  CHANGED_PKGS="\"$pkg\""
135
142
  else
@@ -172,90 +172,78 @@ export function createZenOrder(rawSections: any[]): ZenOrderReturn {
172
172
  // Process and sort sections
173
173
  const sections = processRawSections(rawSections);
174
174
 
175
- // Initialize state
176
- let state: ZenOrderState = {
177
- sections,
178
- selectedSection: sections[0] || null,
179
- selectedDoc: sections[0]?.items[0] || null
180
- };
181
-
182
- // Actions
183
- const selectSection = (section: Section): void => {
184
- state.selectedSection = section;
185
- state.selectedDoc = section.items[0] || null;
186
- };
187
-
188
- const selectDoc = (doc: DocItem): void => {
189
- state.selectedDoc = doc;
190
- // Also update selected section if doc is from a different section
191
- const docSection = sections.find(s => s.slug === doc.sectionSlug);
192
- if (docSection && state.selectedSection?.slug !== doc.sectionSlug) {
193
- state.selectedSection = docSection;
175
+ // React to selection changes by updating the returned object's properties
176
+ const actions: ZenOrderActions = {
177
+ selectSection: (section: Section): void => {
178
+ instance.selectedSection = section;
179
+ instance.selectedDoc = section.items[0] || null;
180
+ },
181
+
182
+ selectDoc: (doc: DocItem): void => {
183
+ instance.selectedDoc = doc;
184
+ const docSection = sections.find(s => s.slug === doc.sectionSlug);
185
+ if (docSection) {
186
+ instance.selectedSection = docSection;
187
+ }
188
+ },
189
+
190
+ getSectionBySlug: (sectionSlug: string): Section | null => {
191
+ return sections.find(s => s.slug === sectionSlug) || null;
192
+ },
193
+
194
+ getDocBySlug: (sectionSlug: string, docSlug: string): DocItem | null => {
195
+ const section = sections.find(s => s.slug === sectionSlug);
196
+ if (!section) return null;
197
+ return section.items.find(d => d.slug === docSlug) || null;
198
+ },
199
+
200
+ getNextDoc: (currentDoc: DocItem): DocItem | null => {
201
+ const currentSection = sections.find(s => s.slug === currentDoc.sectionSlug);
202
+ if (!currentSection) return null;
203
+
204
+ const currentIndex = currentSection.items.findIndex(d => d.slug === currentDoc.slug);
205
+
206
+ if (currentIndex < currentSection.items.length - 1) {
207
+ return currentSection.items[currentIndex + 1];
208
+ }
209
+
210
+ const sectionIndex = sections.findIndex(s => s.slug === currentSection.slug);
211
+ if (sectionIndex < sections.length - 1) {
212
+ const nextSection = sections[sectionIndex + 1];
213
+ return nextSection.items[0] || null;
214
+ }
215
+
216
+ return null;
217
+ },
218
+
219
+ getPrevDoc: (currentDoc: DocItem): DocItem | null => {
220
+ const currentSection = sections.find(s => s.slug === currentDoc.sectionSlug);
221
+ if (!currentSection) return null;
222
+
223
+ const currentIndex = currentSection.items.findIndex(d => d.slug === currentDoc.slug);
224
+
225
+ if (currentIndex > 0) {
226
+ return currentSection.items[currentIndex - 1];
227
+ }
228
+
229
+ const sectionIndex = sections.findIndex(s => s.slug === currentSection.slug);
230
+ if (sectionIndex > 0) {
231
+ const prevSection = sections[sectionIndex - 1];
232
+ return prevSection.items[prevSection.items.length - 1] || null;
233
+ }
234
+
235
+ return null;
194
236
  }
195
237
  };
196
238
 
197
- const getSectionBySlug = (sectionSlug: string): Section | null => {
198
- return sections.find(s => s.slug === sectionSlug) || null;
199
- };
200
-
201
- const getDocBySlug = (sectionSlug: string, docSlug: string): DocItem | null => {
202
- const section = getSectionBySlug(sectionSlug);
203
- if (!section) return null;
204
- return section.items.find(d => d.slug === docSlug) || null;
205
- };
206
-
207
- const getNextDoc = (currentDoc: DocItem): DocItem | null => {
208
- const currentSection = sections.find(s => s.slug === currentDoc.sectionSlug);
209
- if (!currentSection) return null;
210
-
211
- const currentIndex = currentSection.items.findIndex(d => d.slug === currentDoc.slug);
212
-
213
- // Try next doc in same section
214
- if (currentIndex < currentSection.items.length - 1) {
215
- return currentSection.items[currentIndex + 1];
216
- }
217
-
218
- // Try first doc of next section
219
- const sectionIndex = sections.findIndex(s => s.slug === currentSection.slug);
220
- if (sectionIndex < sections.length - 1) {
221
- const nextSection = sections[sectionIndex + 1];
222
- return nextSection.items[0] || null;
223
- }
224
-
225
- return null;
226
- };
227
-
228
- const getPrevDoc = (currentDoc: DocItem): DocItem | null => {
229
- const currentSection = sections.find(s => s.slug === currentDoc.sectionSlug);
230
- if (!currentSection) return null;
231
-
232
- const currentIndex = currentSection.items.findIndex(d => d.slug === currentDoc.slug);
233
-
234
- // Try previous doc in same section
235
- if (currentIndex > 0) {
236
- return currentSection.items[currentIndex - 1];
237
- }
238
-
239
- // Try last doc of previous section
240
- const sectionIndex = sections.findIndex(s => s.slug === currentSection.slug);
241
- if (sectionIndex > 0) {
242
- const prevSection = sections[sectionIndex - 1];
243
- return prevSection.items[prevSection.items.length - 1] || null;
244
- }
245
-
246
- return null;
247
- };
248
-
249
- return {
250
- ...state,
239
+ const instance: ZenOrderReturn = {
251
240
  sections,
252
- selectSection,
253
- selectDoc,
254
- getNextDoc,
255
- getPrevDoc,
256
- getDocBySlug,
257
- getSectionBySlug
241
+ selectedSection: sections[0] || null,
242
+ selectedDoc: sections[0]?.items[0] || null,
243
+ ...actions
258
244
  };
245
+
246
+ return instance;
259
247
  }
260
248
 
261
249
  /**
package/content/index.ts CHANGED
@@ -107,11 +107,14 @@ export default function content(options: ContentPluginOptions = {}): ZenithPlugi
107
107
  name: 'zenith-content',
108
108
  config: options,
109
109
  setup(ctx: PluginContext) {
110
+ console.log('[zenith:content] setup() starting...');
110
111
  let collections: Record<string, ContentItem[]> = {};
111
112
 
112
113
  if (options.sources && Object.keys(options.sources).length > 0) {
113
114
  // Use new sources configuration
115
+ console.log('[zenith:content] Loading from sources...');
114
116
  collections = loadFromSources(options.sources, ctx.projectRoot);
117
+ console.log('[zenith:content] loadFromSources completed');
115
118
  } else if (options.contentDir) {
116
119
  // Legacy: single content directory
117
120
  const contentPath = path.resolve(ctx.projectRoot, options.contentDir);
@@ -131,10 +134,12 @@ export default function content(options: ContentPluginOptions = {}): ZenithPlugi
131
134
 
132
135
  // Pass to runtime using generic namespaced data store
133
136
  const allItems = Object.values(collections).flat();
137
+ console.log('[zenith:content] Setting plugin data, items:', allItems.length);
134
138
  ctx.setPluginData('content', allItems);
135
139
 
136
140
  // Update legacy storage
137
141
  allContent = allItems;
142
+ console.log('[zenith:content] setup() completed');
138
143
  },
139
144
 
140
145
  /**
package/content/loader.ts CHANGED
@@ -4,15 +4,19 @@ import type { ContentItem, ContentSourceConfig } from './types';
4
4
  import { compileMarkdown, vnodesToHtml } from './markdown';
5
5
 
6
6
  export function loadContent(contentDir: string): ContentItem[] {
7
+ console.log(`[zenith:content:loadContent] Starting for: ${contentDir}`);
7
8
  if (!fs.existsSync(contentDir)) {
8
9
  console.warn(`Content directory ${contentDir} does not exist.`);
9
10
  return [];
10
11
  }
11
12
 
12
13
  const items: ContentItem[] = [];
14
+ console.log('[zenith:content:loadContent] Getting all files...');
13
15
  const files = getAllFiles(contentDir);
16
+ console.log(`[zenith:content:loadContent] Found ${files.length} files`);
14
17
 
15
18
  for (const filePath of files) {
19
+ console.log(`[zenith:content:loadContent] Processing file: ${path.basename(filePath)}`);
16
20
  const ext = path.extname(filePath).toLowerCase();
17
21
  const relativePath = path.relative(contentDir, filePath);
18
22
  const collection = relativePath.split(path.sep)[0];
@@ -40,10 +44,14 @@ export function loadContent(contentDir: string): ContentItem[] {
40
44
  console.error(`Error parsing JSON file ${filePath}:`, e);
41
45
  }
42
46
  } else if (ext === '.md' || ext === '.mdx') {
47
+ console.log(`[zenith:content:loadContent] Parsing markdown: ${path.basename(filePath)}`);
43
48
  const { metadata, content: markdownBody } = parseMarkdown(rawContent);
49
+ console.log(`[zenith:content:loadContent] Compiling markdown...`);
44
50
  // Compile markdown to VNodes then to HTML for rendering
45
51
  const vnodes = compileMarkdown(markdownBody);
52
+ console.log(`[zenith:content:loadContent] Converting vnodes to HTML...`);
46
53
  const compiledContent = vnodesToHtml(vnodes);
54
+ console.log(`[zenith:content:loadContent] Done with: ${path.basename(filePath)}`);
47
55
  items.push({
48
56
  id,
49
57
  slug,
@@ -53,7 +61,7 @@ export function loadContent(contentDir: string): ContentItem[] {
53
61
  });
54
62
  }
55
63
  }
56
-
64
+ console.log(`[zenith:content:loadContent] Finished, returning ${items.length} items`);
57
65
  return items;
58
66
  }
59
67
 
@@ -65,27 +73,34 @@ export function loadFromSources(
65
73
  sources: Record<string, ContentSourceConfig>,
66
74
  projectRoot: string
67
75
  ): Record<string, ContentItem[]> {
76
+ console.log('[zenith:content:loader] loadFromSources called, sources:', Object.keys(sources));
68
77
  const collections: Record<string, ContentItem[]> = {};
69
78
 
70
79
  for (const [collectionName, config] of Object.entries(sources)) {
80
+ console.log(`[zenith:content:loader] Processing collection: ${collectionName}`);
71
81
  const rootPath = path.resolve(projectRoot, config.root);
82
+ console.log(`[zenith:content:loader] Root path: ${rootPath}`);
72
83
 
73
84
  if (!fs.existsSync(rootPath)) {
74
85
  console.warn(`[zenith:content] Source root "${rootPath}" does not exist for collection "${collectionName}"`);
75
86
  continue;
76
87
  }
88
+ console.log('[zenith:content:loader] Root path exists, scanning folders...');
77
89
 
78
90
  // Get folders to scan
79
91
  let foldersToScan: string[] = [];
80
92
 
81
93
  if (config.include && config.include.length > 0) {
82
94
  // Explicit include list
95
+ console.log('[zenith:content:loader] Using explicit include list:', config.include);
83
96
  foldersToScan = config.include;
84
97
  } else {
85
98
  // Scan all subdirectories if no include specified
86
99
  try {
100
+ console.log('[zenith:content:loader] Reading subdirectories...');
87
101
  foldersToScan = fs.readdirSync(rootPath)
88
102
  .filter(f => fs.statSync(path.join(rootPath, f)).isDirectory());
103
+ console.log('[zenith:content:loader] Found subdirectories:', foldersToScan);
89
104
  } catch {
90
105
  // If root is itself the content folder
91
106
  foldersToScan = ['.'];
@@ -95,10 +110,12 @@ export function loadFromSources(
95
110
  // Apply excludes
96
111
  const exclude = config.exclude || [];
97
112
  foldersToScan = foldersToScan.filter(f => !exclude.includes(f));
113
+ console.log('[zenith:content:loader] Folders after exclude filter:', foldersToScan);
98
114
 
99
115
  // Load content from each folder
100
116
  const items: ContentItem[] = [];
101
117
  for (const folder of foldersToScan) {
118
+ console.log(`[zenith:content:loader] Processing folder: ${folder}`);
102
119
  const folderPath = folder === '.' ? rootPath : path.join(rootPath, folder);
103
120
 
104
121
  if (!fs.existsSync(folderPath)) {
@@ -106,7 +123,9 @@ export function loadFromSources(
106
123
  continue;
107
124
  }
108
125
 
126
+ console.log(`[zenith:content:loader] Calling loadContent for: ${folderPath}`);
109
127
  const folderItems = loadContent(folderPath);
128
+ console.log(`[zenith:content:loader] loadContent returned ${folderItems.length} items`);
110
129
 
111
130
  // Override collection name to match the configured name
112
131
  items.push(...folderItems.map(item => ({
@@ -109,8 +109,16 @@ export function compileMarkdown(markdown: string): VNode[] {
109
109
  const line = lines[i];
110
110
  const trimmed = line.trim();
111
111
 
112
- // Skip empty lines
113
- if (trimmed === '') {
112
+ // Raw HTML: if a line starts with < and ends with > (simplified detection)
113
+ if (trimmed.startsWith('<') && trimmed.endsWith('>')) {
114
+ // We'll treat this as a raw HTML block.
115
+ // VNode doesn't have a 'raw' type, so we'll use a special tag 'div'
116
+ // but we need a way to signal it shouldn't be escaped.
117
+ // Since our system uses innerHTML for the final injection,
118
+ // we can just put the raw HTML in the children for now?
119
+ // Wait, vnodesToHtml will escape children if they are strings.
120
+ // Let's use a special tag 'zen-raw-html' that vnodesToHtml handles.
121
+ nodes.push(h('zen-raw-html', { html: trimmed }, []));
114
122
  i++;
115
123
  continue;
116
124
  }
@@ -185,6 +193,12 @@ export function compileMarkdown(markdown: string): VNode[] {
185
193
  continue;
186
194
  }
187
195
 
196
+ // Skip empty lines explicitly to prevent infinite loop
197
+ if (trimmed === '') {
198
+ i++;
199
+ continue;
200
+ }
201
+
188
202
  // Paragraph: collect consecutive non-empty lines
189
203
  const paraLines: string[] = [];
190
204
  while (i < lines.length && lines[i].trim() !== '' &&
@@ -219,6 +233,10 @@ export function vnodesToHtml(nodes: VNode[]): string {
219
233
 
220
234
  const { tag, props, children } = node;
221
235
 
236
+ if (tag === 'zen-raw-html') {
237
+ return props.html || '';
238
+ }
239
+
222
240
  // Self-closing tags
223
241
  if (tag === 'hr' || tag === 'br') {
224
242
  const attrs = Object.entries(props)
package/content/query.ts CHANGED
@@ -97,4 +97,70 @@ export class ZenCollection {
97
97
  const results = await this.get();
98
98
  return results.length;
99
99
  }
100
+
101
+ /**
102
+ * Group items by folder (first segment of slug).
103
+ * Returns an object with a groupByFolder method that can be chained.
104
+ */
105
+ groupByFolder(): { get: () => Array<{ id: string; title: string; items: ContentItem[] }> } {
106
+ const self = this;
107
+ return {
108
+ get(): Array<{ id: string; title: string; items: ContentItem[] }> {
109
+ let results = [...self.collectionItems];
110
+
111
+ // 1. Filter
112
+ for (const filter of self.filters) {
113
+ results = results.filter(filter);
114
+ }
115
+
116
+ // 2. Sort
117
+ if (self.sortField) {
118
+ results.sort((a, b) => {
119
+ const valA = a[self.sortField!];
120
+ const valB = b[self.sortField!];
121
+
122
+ if (valA < valB) return self.sortOrder === 'asc' ? -1 : 1;
123
+ if (valA > valB) return self.sortOrder === 'asc' ? 1 : -1;
124
+ return 0;
125
+ });
126
+ }
127
+
128
+ // 3. Limit
129
+ if (self.limitCount !== null) {
130
+ results = results.slice(0, self.limitCount);
131
+ }
132
+
133
+ // Group by folder (first segment of slug)
134
+ const groups = new Map<string, ContentItem[]>();
135
+ for (const item of results) {
136
+ const slug = String(item.slug || item.id || '');
137
+ const parts = slug.split('/');
138
+ const folder = parts.length > 1 ? parts[0] : 'default';
139
+
140
+ if (!groups.has(folder)) {
141
+ groups.set(folder, []);
142
+ }
143
+ groups.get(folder)!.push(item);
144
+ }
145
+
146
+ // Convert to array of sections
147
+ const sections: Array<{ id: string; title: string; items: ContentItem[] }> = [];
148
+ for (const [folder, items] of groups) {
149
+ // Generate title from folder name
150
+ const title = folder
151
+ .split('-')
152
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
153
+ .join(' ');
154
+
155
+ sections.push({
156
+ id: folder,
157
+ title,
158
+ items
159
+ });
160
+ }
161
+
162
+ return sections;
163
+ }
164
+ };
165
+ }
100
166
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenithbuild/plugins",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Plugin system for Zenith framework",
5
5
  "type": "module",
6
6
  "main": "content/index.ts",