@hashrock/ono 0.1.2 → 0.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/src/content.js DELETED
@@ -1,272 +0,0 @@
1
- import { readdir, readFile } from "node:fs/promises";
2
- import { join, relative, sep } from "node:path";
3
- import { marked } from "marked";
4
-
5
- /**
6
- * Parse frontmatter from markdown content
7
- * @param {string} content - Raw markdown content
8
- * @returns {{ data: Object, content: string }} Parsed frontmatter and content
9
- */
10
- function parseFrontmatter(content) {
11
- const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
12
- const match = content.match(frontmatterRegex);
13
-
14
- if (!match) {
15
- return { data: {}, content };
16
- }
17
-
18
- const [, frontmatterStr, markdownContent] = match;
19
- const data = {};
20
-
21
- // Simple YAML-like parser (supports basic key: value pairs)
22
- const lines = frontmatterStr.split("\n");
23
- let currentKey = null;
24
- let arrayItems = [];
25
-
26
- for (const line of lines) {
27
- const trimmed = line.trim();
28
- if (!trimmed) continue;
29
-
30
- // Array item
31
- if (trimmed.startsWith("- ")) {
32
- if (currentKey) {
33
- arrayItems.push(trimmed.slice(2).trim());
34
- }
35
- continue;
36
- }
37
-
38
- // If we were collecting array items, save them
39
- if (currentKey && arrayItems.length > 0) {
40
- data[currentKey] = arrayItems;
41
- arrayItems = [];
42
- currentKey = null;
43
- }
44
-
45
- // Key-value pair
46
- const colonIndex = trimmed.indexOf(":");
47
- if (colonIndex > 0) {
48
- const key = trimmed.slice(0, colonIndex).trim();
49
- let value = trimmed.slice(colonIndex + 1).trim();
50
-
51
- // Parse value types
52
- if (value === "") {
53
- // Empty value might indicate an array follows
54
- currentKey = key;
55
- continue;
56
- } else if (value === "true") {
57
- value = true;
58
- } else if (value === "false") {
59
- value = false;
60
- } else if (value.startsWith("[") && value.endsWith("]")) {
61
- // JSON array - parse with proper quoting for unquoted strings
62
- try {
63
- // First try parsing as-is (for properly quoted JSON)
64
- value = JSON.parse(value);
65
- } catch {
66
- // If that fails, try parsing as YAML-style array [item1, item2]
67
- try {
68
- const items = value
69
- .slice(1, -1) // Remove [ ]
70
- .split(",")
71
- .map((item) => item.trim())
72
- .filter((item) => item.length > 0);
73
- value = items;
74
- } catch {
75
- // Keep as string if all parsing fails
76
- }
77
- }
78
- } else if (value.startsWith("{") && value.endsWith("}")) {
79
- // JSON object
80
- try {
81
- value = JSON.parse(value);
82
- } catch {
83
- // Keep as string if parsing fails
84
- }
85
- } else if (/^\d{4}-\d{2}-\d{2}/.test(value)) {
86
- // Date format
87
- value = new Date(value);
88
- } else if (/^\d+$/.test(value)) {
89
- value = parseInt(value, 10);
90
- } else if (/^\d+\.\d+$/.test(value)) {
91
- value = parseFloat(value);
92
- }
93
-
94
- data[key] = value;
95
- currentKey = null;
96
- }
97
- }
98
-
99
- // Handle final array if exists
100
- if (currentKey && arrayItems.length > 0) {
101
- data[currentKey] = arrayItems;
102
- }
103
-
104
- return { data, content: markdownContent };
105
- }
106
-
107
- /**
108
- * Validate data against schema
109
- * @param {Object} data - Data to validate
110
- * @param {Object} schema - Schema definition
111
- * @returns {{ valid: boolean, errors: string[] }}
112
- */
113
- function validateSchema(data, schema) {
114
- const errors = [];
115
-
116
- for (const [key, definition] of Object.entries(schema)) {
117
- const value = data[key];
118
-
119
- // Check required fields
120
- if (definition.required && (value === undefined || value === null)) {
121
- errors.push(`Missing required field: ${key}`);
122
- continue;
123
- }
124
-
125
- // Skip validation if field is not present and not required
126
- if (value === undefined || value === null) {
127
- // Apply default if specified
128
- if (definition.default !== undefined) {
129
- data[key] = definition.default;
130
- }
131
- continue;
132
- }
133
-
134
- // Type validation
135
- const actualType = Array.isArray(value) ? "array" : typeof value === "object" && value instanceof Date ? "date" : typeof value;
136
-
137
- if (definition.type !== actualType) {
138
- errors.push(
139
- `Invalid type for ${key}: expected ${definition.type}, got ${actualType}`,
140
- );
141
- continue;
142
- }
143
-
144
- // Array item type validation
145
- if (definition.type === "array" && definition.items) {
146
- for (let i = 0; i < value.length; i++) {
147
- const itemType = typeof value[i];
148
- if (itemType !== definition.items) {
149
- errors.push(
150
- `Invalid array item type for ${key}[${i}]: expected ${definition.items}, got ${itemType}`,
151
- );
152
- }
153
- }
154
- }
155
- }
156
-
157
- return { valid: errors.length === 0, errors };
158
- }
159
-
160
- /**
161
- * Generate slug from file path
162
- * @param {string} filePath - File path relative to content directory
163
- * @returns {string} Generated slug
164
- */
165
- function generateSlug(filePath) {
166
- return filePath
167
- .replace(/\.md$/, "")
168
- .split(sep)
169
- .join("/");
170
- }
171
-
172
- /**
173
- * Read all markdown files from a directory recursively
174
- * @param {string} dir - Directory path
175
- * @returns {Promise<string[]>} Array of file paths
176
- */
177
- async function readMarkdownFiles(dir) {
178
- const files = [];
179
- const entries = await readdir(dir, { withFileTypes: true });
180
-
181
- for (const entry of entries) {
182
- const fullPath = join(dir, entry.name);
183
- if (entry.isDirectory()) {
184
- files.push(...(await readMarkdownFiles(fullPath)));
185
- } else if (entry.name.endsWith(".md")) {
186
- files.push(fullPath);
187
- }
188
- }
189
-
190
- return files;
191
- }
192
-
193
- /**
194
- * Load content collection configuration
195
- * @param {string} configPath - Path to content.config.js
196
- * @returns {Promise<Object>} Configuration object
197
- */
198
- async function loadConfig(configPath) {
199
- const configFile = configPath || join(process.cwd(), "content.config.js");
200
- try {
201
- // Convert to file URL for proper import
202
- const fileUrl = new URL(`file://${configFile}`);
203
- const config = await import(fileUrl.href);
204
- return config.collections || {};
205
- } catch {
206
- return {};
207
- }
208
- }
209
-
210
- /**
211
- * Get all entries from a collection
212
- * @param {string} collection - Collection name
213
- * @param {Function} [filter] - Optional filter function
214
- * @returns {Promise<Array>} Array of collection entries
215
- */
216
- export async function getCollection(collection, filter) {
217
- const contentDir = join(process.cwd(), "content", collection);
218
- const config = await loadConfig();
219
- const schema = config[collection]?.schema;
220
-
221
- try {
222
- const files = await readMarkdownFiles(contentDir);
223
- const entries = [];
224
-
225
- for (const file of files) {
226
- const content = await readFile(file, "utf-8");
227
- const { data, content: markdown } = parseFrontmatter(content);
228
-
229
- // Validate against schema if provided
230
- if (schema) {
231
- const validation = validateSchema(data, schema);
232
- if (!validation.valid) {
233
- console.warn(`Validation errors in ${file}:`, validation.errors);
234
- }
235
- }
236
-
237
- const html = marked.parse(markdown);
238
- const relativePath = relative(contentDir, file);
239
- const slug = generateSlug(relativePath);
240
-
241
- entries.push({
242
- slug,
243
- data,
244
- html,
245
- file,
246
- });
247
- }
248
-
249
- // Apply filter if provided
250
- if (filter) {
251
- return entries.filter(filter);
252
- }
253
-
254
- return entries;
255
- } catch (error) {
256
- if (error.code === "ENOENT") {
257
- return [];
258
- }
259
- throw error;
260
- }
261
- }
262
-
263
- /**
264
- * Get a single entry from a collection
265
- * @param {string} collection - Collection name
266
- * @param {string} slug - Entry slug
267
- * @returns {Promise<Object|null>} Collection entry or null if not found
268
- */
269
- export async function getEntry(collection, slug) {
270
- const entries = await getCollection(collection);
271
- return entries.find((entry) => entry.slug === slug) || null;
272
- }
package/src/resolver.js DELETED
@@ -1,137 +0,0 @@
1
- /**
2
- * Module Resolver - Parse imports and resolve dependencies
3
- */
4
-
5
- import fs from "node:fs/promises";
6
- import path from "node:path";
7
-
8
- /**
9
- * Parse import statements from source code
10
- * @param {string} code - Source code
11
- * @returns {Array} Array of import objects with specifier
12
- */
13
- export function parseImports(code) {
14
- const imports = [];
15
-
16
- // Match various import patterns:
17
- // import foo from "bar"
18
- // import { foo } from "bar"
19
- // import * as foo from "bar"
20
- // import "bar"
21
- const importRegex = /import\s+(?:[\w{},\s*]+\s+from\s+)?['"]([^'"]+)['"]/g;
22
-
23
- let match;
24
- while ((match = importRegex.exec(code)) !== null) {
25
- imports.push({
26
- specifier: match[1]
27
- });
28
- }
29
-
30
- return imports;
31
- }
32
-
33
- /**
34
- * Resolve import path relative to the importing file
35
- * @param {string} importPath - Import specifier (e.g., "./Button.jsx")
36
- * @param {string} fromFile - Absolute path of the file doing the import
37
- * @returns {string} Absolute path to the imported file
38
- */
39
- export function resolveImportPath(importPath, fromFile) {
40
- // If it's already an absolute path, return as-is
41
- if (path.isAbsolute(importPath)) {
42
- return importPath;
43
- }
44
-
45
- // For relative imports, resolve relative to the importing file
46
- const dir = path.dirname(fromFile);
47
- return path.resolve(dir, importPath);
48
- }
49
-
50
- /**
51
- * Topological sort for dependency graph
52
- * @param {Map} graph - Dependency graph (file -> [dependencies])
53
- * @returns {Array} Sorted array of file paths
54
- */
55
- function topologicalSort(graph) {
56
- const sorted = [];
57
- const visited = new Set();
58
- const visiting = new Set();
59
-
60
- function visit(node) {
61
- if (visited.has(node)) return;
62
- if (visiting.has(node)) {
63
- throw new Error(`Circular dependency detected: ${node}`);
64
- }
65
-
66
- visiting.add(node);
67
-
68
- const deps = graph.get(node) || [];
69
- for (const dep of deps) {
70
- visit(dep);
71
- }
72
-
73
- visiting.delete(node);
74
- visited.add(node);
75
- sorted.push(node);
76
- }
77
-
78
- // Visit all nodes
79
- for (const node of graph.keys()) {
80
- visit(node);
81
- }
82
-
83
- return sorted;
84
- }
85
-
86
- /**
87
- * Collect all dependencies recursively
88
- * @param {string} entryFile - Absolute path to entry file
89
- * @returns {Object} Object with modules (Set), graph (Map), and order (Array)
90
- */
91
- export async function collectDependencies(entryFile) {
92
- const modules = new Set();
93
- const graph = new Map();
94
- const queue = [entryFile];
95
-
96
- // BFS to collect all dependencies
97
- while (queue.length > 0) {
98
- const currentFile = queue.shift();
99
-
100
- // Skip if already processed
101
- if (modules.has(currentFile)) continue;
102
-
103
- // Read the file
104
- let source;
105
- try {
106
- source = await fs.readFile(currentFile, "utf-8");
107
- } catch (error) {
108
- throw new Error(`Cannot read file: ${currentFile}\n${error.message}`);
109
- }
110
-
111
- // Parse imports
112
- const imports = parseImports(source);
113
- const dependencies = [];
114
-
115
- for (const imp of imports) {
116
- // Only process relative imports (skip node_modules, etc.)
117
- if (imp.specifier.startsWith(".")) {
118
- const resolvedPath = resolveImportPath(imp.specifier, currentFile);
119
- dependencies.push(resolvedPath);
120
- queue.push(resolvedPath);
121
- }
122
- }
123
-
124
- // Add to modules and graph
125
- modules.add(currentFile);
126
- graph.set(currentFile, dependencies);
127
- }
128
-
129
- // Sort dependencies topologically
130
- const order = topologicalSort(graph);
131
-
132
- return {
133
- modules,
134
- graph,
135
- order
136
- };
137
- }