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.
- package/README.md +852 -3
- package/bin/metaowl-create.js +425 -0
- package/index.js +155 -1
- package/modules/app-mounter.js +7 -0
- package/modules/auto-import.js +225 -0
- package/modules/cache.js +2 -0
- package/modules/composables.js +600 -0
- package/modules/error-boundary.js +228 -0
- package/modules/fetch.js +7 -0
- package/modules/file-router.js +425 -19
- package/modules/forms.js +353 -0
- package/modules/i18n.js +333 -0
- package/modules/layouts.js +433 -0
- package/modules/odoo-rpc.js +511 -0
- package/modules/pwa.js +515 -0
- package/modules/router.js +593 -29
- package/modules/seo.js +501 -0
- package/modules/store.js +409 -0
- package/modules/templates-manager.js +5 -0
- package/modules/test-utils.js +532 -0
- package/package.json +1 -1
- package/test/auto-import.test.js +110 -0
- package/test/composables.test.js +103 -0
- package/test/dynamic-routes.test.js +520 -0
- package/test/error-boundary.test.js +126 -0
- package/test/forms.test.js +203 -0
- package/test/i18n.test.js +188 -0
- package/test/layouts.test.js +395 -0
- package/test/odoo-rpc.test.js +547 -0
- package/test/pwa.test.js +154 -0
- package/test/router-guards.test.js +617 -0
- package/test/seo.test.js +353 -0
- package/test/store.test.js +476 -0
- package/test/test-utils.test.js +314 -0
- package/vite/plugin.js +43 -5
|
@@ -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
|
+
}
|