@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/README.md +35 -320
- package/package.json +14 -13
- package/src/barrels.js +230 -0
- package/src/browser/compiler.js +71 -193
- package/src/browser/unocss.js +5 -1
- package/src/builder.js +110 -232
- package/src/bundler.js +215 -64
- package/src/cli.js +22 -191
- package/src/commands/build.js +141 -0
- package/src/commands/dev.js +54 -0
- package/src/constants.js +74 -0
- package/src/jsx-runtime.js +15 -5
- package/src/parser.js +555 -0
- package/src/renderer.js +29 -48
- package/src/server.js +88 -74
- package/src/transformer.js +1 -18
- package/src/unocss.js +20 -24
- package/src/utils.js +55 -0
- package/src/watcher.js +98 -125
- package/src/content.js +0 -272
- package/src/resolver.js +0 -137
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
|
-
}
|