metaowl 0.1.3 → 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.
@@ -0,0 +1,225 @@
1
+ /**
2
+ * @module AutoImport
3
+ *
4
+ * Automatic component importing for MetaOwl applications.
5
+ *
6
+ * Features:
7
+ * - Auto-discovers components from src/components/
8
+ * - Generates import statements at build time
9
+ * - Optional - disabled by default
10
+ *
11
+ * Configuration in vite.config.js:
12
+ * metaowlConfig({
13
+ * autoImport: {
14
+ * enabled: true,
15
+ * componentsDir: 'src/components',
16
+ * pattern: '*.js'
17
+ * }
18
+ * })
19
+ *
20
+ * Usage:
21
+ * - Components are automatically available in templates
22
+ * - No manual import needed
23
+ */
24
+
25
+ import { globSync } from 'glob'
26
+ import { resolve, relative, basename, extname, dirname } from 'path'
27
+
28
+ /**
29
+ * Registry of auto-discovered components.
30
+ */
31
+ let _importMap = null
32
+
33
+ /**
34
+ * Generate component import map from directory.
35
+ *
36
+ * @param {string} componentsDir - e.g. 'src/components'
37
+ * @returns {Map<string, string>} Map of PascalCase names to import paths
38
+ */
39
+ export function generateComponentMap(componentsDir, pattern = '*.js') {
40
+ const map = new Map()
41
+ const globPattern = pattern.includes('/') ? pattern : `${componentsDir}/${pattern}`
42
+ const files = globSync(globPattern)
43
+
44
+ for (const file of files) {
45
+ if (file.includes('.test.') || file.includes('.spec.')) continue
46
+
47
+ const name = getComponentName(file)
48
+ if (!name) continue
49
+
50
+ const importPath = `/@components/${relative(componentsDir, file).replace(/\\/g, '/')}`
51
+ map.set(name, importPath)
52
+ }
53
+
54
+ return map
55
+ }
56
+
57
+ function getComponentName(filePath) {
58
+ const ext = extname(filePath)
59
+ const base = basename(filePath, ext)
60
+
61
+ if (basename(filePath) === base + ext) {
62
+ return base
63
+ }
64
+
65
+ const dir = relative(process.cwd(), filePath).split('/')
66
+ if (base === 'index' && dir.length > 1) {
67
+ return toPascalCase(dir[dir.length - 2])
68
+ }
69
+
70
+ return toPascalCase(base)
71
+ }
72
+
73
+ function toPascalCase(str) {
74
+ return str
75
+ .replace(/[-_](.)/g, (_, char) => char.toUpperCase())
76
+ .replace(/^[a-z]/, char => char.toUpperCase())
77
+ }
78
+
79
+ /**
80
+ * Scan components directory for component files.
81
+ *
82
+ * @param {string} componentsDir - Directory to scan
83
+ * @param {object} options - Options
84
+ * @param {string} options.pattern - Glob pattern
85
+ * @returns {Promise<string[]>} Array of component names
86
+ */
87
+ export async function scanComponents(componentsDir, options = {}) {
88
+ const { pattern = '*.js' } = options
89
+ const absoluteDir = resolve(componentsDir)
90
+ const fs = await import('fs/promises')
91
+ const { join } = await import('path')
92
+
93
+ const components = []
94
+
95
+ async function scanDir(dir) {
96
+ try {
97
+ const entries = await fs.readdir(dir, { withFileTypes: true })
98
+
99
+ for (const entry of entries) {
100
+ const fullPath = join(dir, entry.name)
101
+
102
+ if (entry.isDirectory()) {
103
+ // Recursively scan subdirectories
104
+ await scanDir(fullPath)
105
+ } else if (entry.isFile() && entry.name.endsWith('.js')) {
106
+ // Skip test files
107
+ if (entry.name.includes('.test.') || entry.name.includes('.spec.')) continue
108
+
109
+ const name = getComponentName(fullPath)
110
+ if (name && !components.includes(name)) {
111
+ components.push(name)
112
+ }
113
+ }
114
+ }
115
+ } catch {
116
+ // Directory doesn't exist or can't be read
117
+ }
118
+ }
119
+
120
+ await scanDir(absoluteDir)
121
+ return components
122
+ }
123
+
124
+ /**
125
+ * Generate TypeScript declarations file for components.
126
+ *
127
+ * @param {string[]} components - Array of component names
128
+ * @param {string} outputPath - Path to write .d.ts file
129
+ * @returns {Promise<void>}
130
+ */
131
+ export async function generateComponentDts(components, outputPath) {
132
+ const { writeFileSync, mkdirSync } = await import('fs')
133
+ const { join } = await import('path')
134
+
135
+ // Ensure directory exists
136
+ const dir = dirname(outputPath)
137
+ mkdirSync(dir, { recursive: true })
138
+
139
+ const declarations = components.map(name =>
140
+ ` ${name}: typeof import('./components/${name}/${name}.js').default`
141
+ ).join('\n')
142
+
143
+ const content = `// Auto-generated by metaowl - do not edit\ndeclare module '@metaowl/components' {\n${declarations}\n}\n`
144
+
145
+ writeFileSync(outputPath, content, 'utf-8')
146
+ }
147
+
148
+ export function generateImports(componentMap) {
149
+ const lines = []
150
+ for (const [name, path] of componentMap) {
151
+ lines.push(`import ${name} from '${path}'`)
152
+ }
153
+ return lines.join('\n')
154
+ }
155
+
156
+ export function generateComponentsObject(componentMap) {
157
+ const entries = Array.from(componentMap.keys())
158
+ .map(name => ` ${name}`)
159
+ .join(',\n')
160
+ return `{\n${entries}\n}`
161
+ }
162
+
163
+ export function createAutoImportPlugin(options = {}) {
164
+ const {
165
+ enabled = false,
166
+ componentsDir = 'src/components',
167
+ pattern = '**/*.js'
168
+ } = options
169
+
170
+ if (!enabled) {
171
+ return null
172
+ }
173
+
174
+ _importMap = generateComponentMap(componentsDir, `${componentsDir}/${pattern}`)
175
+
176
+ return {
177
+ name: 'metaowl:auto-import',
178
+ enforce: 'pre',
179
+
180
+ config(config) {
181
+ config.resolve ||= {}
182
+ config.resolve.alias ||= {}
183
+ config.resolve.alias['/@components'] = resolve(process.cwd(), componentsDir)
184
+ },
185
+
186
+ transform(code, id) {
187
+ if (!id.includes('/pages/') || !/\.[jt]s$/.test(id)) {
188
+ return null
189
+ }
190
+
191
+ if (!code.includes('/* auto-import */') && !code.includes('// auto-import')) {
192
+ return null
193
+ }
194
+
195
+ const imports = generateImports(_importMap)
196
+ const componentsObj = generateComponentsObject(_importMap)
197
+
198
+ let transformed = imports + '\n\n' + code
199
+
200
+ if (transformed.includes('extends Component')) {
201
+ transformed = transformed.replace(
202
+ /(class\s+\w+\s+extends\s+Component\s*\{)/,
203
+ `$1\n static components = ${componentsObj}\n`
204
+ )
205
+ }
206
+
207
+ return { code: transformed, map: null }
208
+ }
209
+ }
210
+ }
211
+
212
+ export function registerAutoImport(name, path) {
213
+ if (!_importMap) {
214
+ _importMap = new Map()
215
+ }
216
+ _importMap.set(name, path)
217
+ }
218
+
219
+ export function getAutoImportMap() {
220
+ return _importMap ? new Map(_importMap) : null
221
+ }
222
+
223
+ export function clearAutoImports() {
224
+ _importMap = null
225
+ }
package/modules/cache.js CHANGED
@@ -1,4 +1,6 @@
1
1
  /**
2
+ * @module Cache
3
+ *
2
4
  * Async-style localStorage wrapper.
3
5
  *
4
6
  * Values are automatically JSON-serialised on write and deserialised on read.