metaowl 0.1.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,270 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * metaowl create — scaffold a new metaowl project.
4
+ *
5
+ * Usage:
6
+ * metaowl-create [project-name]
7
+ *
8
+ * If no name is given, it will be prompted interactively.
9
+ */
10
+ import { createInterface } from 'node:readline/promises'
11
+ import { mkdirSync, writeFileSync, existsSync } from 'node:fs'
12
+ import { resolve, join, dirname } from 'node:path'
13
+ import { banner, step, success, failure, version } from './utils.js'
14
+
15
+ banner('create')
16
+
17
+ // --- Project name ---
18
+ let name = process.argv[2]?.trim()
19
+
20
+ if (!name) {
21
+ const rl = createInterface({ input: process.stdin, output: process.stdout })
22
+ name = (await rl.question(' Project name: ')).trim()
23
+ rl.close()
24
+ console.log()
25
+ }
26
+
27
+ if (!name || !/^[a-zA-Z0-9_-]+$/.test(name)) {
28
+ failure('Invalid project name. Use only letters, numbers, hyphens, or underscores.')
29
+ process.exit(1)
30
+ }
31
+
32
+ const dest = resolve(process.cwd(), name)
33
+
34
+ if (existsSync(dest)) {
35
+ failure(`Directory "${name}" already exists.`)
36
+ process.exit(1)
37
+ }
38
+
39
+ step(`Scaffolding project "${name}"...`)
40
+ console.log()
41
+
42
+ // --- File writer helper ---
43
+ function write(filePath, content) {
44
+ const abs = join(dest, filePath)
45
+ mkdirSync(dirname(abs), { recursive: true })
46
+ writeFileSync(abs, content, 'utf-8')
47
+ console.log(` ${filePath}`)
48
+ }
49
+
50
+ // --- package.json ---
51
+ write('package.json', JSON.stringify({
52
+ name,
53
+ version: '0.1.0',
54
+ type: 'module',
55
+ scripts: {
56
+ dev: 'metaowl-dev',
57
+ build: 'metaowl-build',
58
+ generate: 'metaowl-generate',
59
+ lint: 'metaowl-lint'
60
+ },
61
+ dependencies: {
62
+ metaowl: `^${version}`
63
+ }
64
+ }, null, 2) + '\n')
65
+
66
+ // --- vite.config.js ---
67
+ write('vite.config.js',
68
+ `import { metaowlConfig } from 'metaowl/vite'
69
+
70
+ export default metaowlConfig({
71
+ componentsDir: 'src/components',
72
+ pagesDir: 'src/pages'
73
+ })
74
+ `)
75
+
76
+ // --- eslint.config.js ---
77
+ write('eslint.config.js',
78
+ `import { eslintConfig } from 'metaowl/eslint'
79
+
80
+ export default eslintConfig
81
+ `)
82
+
83
+ // --- postcss.config.cjs ---
84
+ write('postcss.config.cjs',
85
+ `const { createPostcssConfig } = require('metaowl/postcss')
86
+
87
+ module.exports = createPostcssConfig()
88
+ `)
89
+
90
+ // --- jsconfig.json ---
91
+ write('jsconfig.json', JSON.stringify({
92
+ extends: './node_modules/metaowl/config/jsconfig.base.json',
93
+ compilerOptions: {
94
+ baseUrl: 'src',
95
+ paths: {
96
+ '@pages/*': ['pages/*'],
97
+ '@components/*': ['components/*']
98
+ }
99
+ },
100
+ include: ['src']
101
+ }, null, 2) + '\n')
102
+
103
+ // --- .gitignore ---
104
+ write('.gitignore',
105
+ `node_modules/
106
+ dist/
107
+ .env
108
+ `)
109
+
110
+ // --- src/index.html ---
111
+ write('src/index.html',
112
+ `<!doctype html>
113
+ <html lang="en">
114
+ <head>
115
+ <meta charset="UTF-8" />
116
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
117
+ <title>${name}</title>
118
+ </head>
119
+ <body>
120
+ <div id="metaowl"></div>
121
+ <script type="module" src="/metaowl.js"></script>
122
+ </body>
123
+ </html>
124
+ `)
125
+
126
+ // --- src/metaowl.js ---
127
+ write('src/metaowl.js',
128
+ `import { boot, Fetch } from 'metaowl'
129
+
130
+ Fetch.configure({
131
+ baseUrl: import.meta.env.VITE_API_URL ?? ''
132
+ })
133
+
134
+ boot()
135
+ `)
136
+
137
+ // --- src/css.js ---
138
+ write('src/css.js',
139
+ `// Global styles — import shared CSS here.
140
+ // Component and page CSS files are auto-imported by the metaowl Vite plugin.
141
+ `)
142
+
143
+ // --- src/pages/index/Index.js ---
144
+ write('src/pages/index/Index.js',
145
+ `import { Component } from '@odoo/owl'
146
+ import { Meta } from 'metaowl'
147
+ import AppHeader from '@components/AppHeader/AppHeader'
148
+ import AppFooter from '@components/AppFooter/AppFooter'
149
+
150
+ export default class Index extends Component {
151
+ static template = 'Index'
152
+ static components = { AppHeader, AppFooter }
153
+
154
+ setup() {
155
+ Meta.title('Home — ${name}')
156
+ }
157
+ }
158
+ `)
159
+
160
+ // --- src/pages/index/Index.xml ---
161
+ write('src/pages/index/Index.xml',
162
+ `<?xml version="1.0" encoding="UTF-8"?>
163
+ <templates>
164
+ <t t-name="Index">
165
+ <div class="layout">
166
+ <AppHeader />
167
+ <main class="page page-index">
168
+ <h1>Welcome to ${name}</h1>
169
+ </main>
170
+ <AppFooter />
171
+ </div>
172
+ </t>
173
+ </templates>
174
+ `)
175
+
176
+ // --- src/pages/index/index.css ---
177
+ write('src/pages/index/index.css',
178
+ `.layout {
179
+ display: flex;
180
+ flex-direction: column;
181
+ min-height: 100vh;
182
+ }
183
+
184
+ .page-index {
185
+ flex: 1;
186
+ padding: 2rem;
187
+ }
188
+ `)
189
+
190
+ // --- src/components/AppHeader/AppHeader.js ---
191
+ write('src/components/AppHeader/AppHeader.js',
192
+ `import { Component } from '@odoo/owl'
193
+
194
+ export default class AppHeader extends Component {
195
+ static template = 'AppHeader'
196
+ }
197
+ `)
198
+
199
+ // --- src/components/AppHeader/AppHeader.xml ---
200
+ write('src/components/AppHeader/AppHeader.xml',
201
+ `<?xml version="1.0" encoding="UTF-8"?>
202
+ <templates>
203
+ <t t-name="AppHeader">
204
+ <header class="app-header">
205
+ <span class="app-header__logo">${name}</span>
206
+ </header>
207
+ </t>
208
+ </templates>
209
+ `)
210
+
211
+ // --- src/components/AppHeader/AppHeader.css ---
212
+ write('src/components/AppHeader/AppHeader.css',
213
+ `.app-header {
214
+ display: flex;
215
+ align-items: center;
216
+ padding: 0 1.5rem;
217
+ height: 56px;
218
+ border-bottom: 1px solid #e5e7eb;
219
+ }
220
+
221
+ .app-header__logo {
222
+ font-weight: 600;
223
+ font-size: 1.1rem;
224
+ }
225
+ `)
226
+
227
+ // --- src/components/AppFooter/AppFooter.js ---
228
+ write('src/components/AppFooter/AppFooter.js',
229
+ `import { Component } from '@odoo/owl'
230
+
231
+ export default class AppFooter extends Component {
232
+ static template = 'AppFooter'
233
+ }
234
+ `)
235
+
236
+ // --- src/components/AppFooter/AppFooter.xml ---
237
+ write('src/components/AppFooter/AppFooter.xml',
238
+ `<?xml version="1.0" encoding="UTF-8"?>
239
+ <templates>
240
+ <t t-name="AppFooter">
241
+ <footer class="app-footer">
242
+ <span>Built with metaowl</span>
243
+ </footer>
244
+ </t>
245
+ </templates>
246
+ `)
247
+
248
+ // --- src/components/AppFooter/AppFooter.css ---
249
+ write('src/components/AppFooter/AppFooter.css',
250
+ `.app-footer {
251
+ display: flex;
252
+ align-items: center;
253
+ justify-content: center;
254
+ padding: 1rem;
255
+ font-size: 0.85rem;
256
+ color: #6b7280;
257
+ border-top: 1px solid #e5e7eb;
258
+ }
259
+ `)
260
+
261
+ // --- Done ---
262
+ console.log()
263
+ success(`Project "${name}" ready`)
264
+ console.log()
265
+ console.log(' Next steps:')
266
+ console.log()
267
+ console.log(` cd ${name}`)
268
+ console.log(` npm install`)
269
+ console.log(` npm run dev`)
270
+ console.log()
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * metaowl dev — start the Vite development server.
4
+ */
5
+ import { execSync } from 'node:child_process'
6
+ import { banner, bin, cwd, step } from './utils.js'
7
+
8
+ banner('dev')
9
+ step('Starting development server...')
10
+ console.log()
11
+
12
+ execSync(`"${bin}/vite"`, { stdio: 'inherit', cwd })
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * metaowl generate — SSG production build.
4
+ *
5
+ * Runs lint + vite build, then generates a static HTML file for every page:
6
+ * - <title> and meta tags extracted statically from Meta.*() calls in the page JS
7
+ * - Static HTML content auto-extracted from the page's OWL XML template,
8
+ * with OWL-specific syntax stripped (best-effort, no JS evaluated)
9
+ *
10
+ * pagesDir / outDir can be overridden in package.json under "metaowl":
11
+ * { "metaowl": { "pagesDir": "src/pages", "outDir": "dist" } }
12
+ */
13
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
14
+ import { resolve } from 'node:path'
15
+ import { globSync } from 'glob'
16
+ import { banner, bin, cwd, metaowlRoot, run, step, success, failure } from './utils.js'
17
+
18
+ banner('generate')
19
+
20
+ function escapeAttr(str) {
21
+ return str.replace(/&/g, '&amp;').replace(/"/g, '&quot;')
22
+ }
23
+
24
+ /**
25
+ * Statically extract Meta.*() call arguments from JS source.
26
+ * Only works for string literals — dynamic values are skipped.
27
+ *
28
+ * Returns an object with the keys:
29
+ * title, description, keywords, author, canonical,
30
+ * ogTitle, ogDescription, ogImage, ogUrl, ogType, ogSiteName
31
+ */
32
+ function extractMetaFromJs(src) {
33
+ const meta = {}
34
+ const fns = [
35
+ 'title', 'description', 'keywords', 'author', 'canonical',
36
+ 'ogTitle', 'ogDescription', 'ogImage', 'ogUrl', 'ogType', 'ogSiteName',
37
+ ]
38
+ for (const fn of fns) {
39
+ const m = src.match(new RegExp(`Meta\\.${fn}\\s*\\(\\s*(['"\`])([^'"\`]+)\\1\\s*\\)`))
40
+ if (m) meta[fn] = m[2]
41
+ }
42
+ return meta
43
+ }
44
+
45
+ /**
46
+ * Inject extracted meta values into the HTML head.
47
+ * Returns the modified HTML string.
48
+ */
49
+ function injectMeta(html, meta) {
50
+ if (meta.title) {
51
+ html = html.replace(/<title>[^<]*<\/title>/, `<title>${escapeAttr(meta.title)}</title>`)
52
+ }
53
+ /** @param {string} selector @param {string} tag */
54
+ const injectTag = (selector, tag) => {
55
+ html = html.replace(new RegExp(`\\s*${selector}[^>]*>\\s*`, 'gi'), '')
56
+ html = html.replace('</head>', ` ${tag}\n </head>`)
57
+ }
58
+ if (meta.description)
59
+ injectTag('<meta\\s+name="description"', `<meta name="description" content="${escapeAttr(meta.description)}">`)
60
+ if (meta.keywords)
61
+ injectTag('<meta\\s+name="keywords"', `<meta name="keywords" content="${escapeAttr(meta.keywords)}">`)
62
+ if (meta.author)
63
+ injectTag('<meta\\s+name="author"', `<meta name="author" content="${escapeAttr(meta.author)}">`)
64
+ if (meta.canonical)
65
+ injectTag('<link\\s+rel="canonical"', `<link rel="canonical" href="${escapeAttr(meta.canonical)}">`)
66
+ if (meta.ogTitle)
67
+ injectTag('<meta\\s+property="og:title"', `<meta property="og:title" content="${escapeAttr(meta.ogTitle)}">`)
68
+ if (meta.ogDescription)
69
+ injectTag('<meta\\s+property="og:description"', `<meta property="og:description" content="${escapeAttr(meta.ogDescription)}">`)
70
+ if (meta.ogImage)
71
+ injectTag('<meta\\s+property="og:image"', `<meta property="og:image" content="${escapeAttr(meta.ogImage)}">`)
72
+ if (meta.ogUrl)
73
+ injectTag('<meta\\s+property="og:url"', `<meta property="og:url" content="${escapeAttr(meta.ogUrl)}">`)
74
+ if (meta.ogType)
75
+ injectTag('<meta\\s+property="og:type"', `<meta property="og:type" content="${escapeAttr(meta.ogType)}">`)
76
+ if (meta.ogSiteName)
77
+ injectTag('<meta\\s+property="og:site_name"', `<meta property="og:site_name" content="${escapeAttr(meta.ogSiteName)}">`)
78
+ return html
79
+ }
80
+
81
+ // Read project config
82
+ const pkg = JSON.parse(readFileSync(resolve(cwd, 'package.json'), 'utf-8'))
83
+ const metaowlConfig = pkg.metaowl ?? {}
84
+ const pagesDir = metaowlConfig.pagesDir ?? 'src/pages'
85
+ const outDir = metaowlConfig.outDir ?? 'dist'
86
+
87
+ // Derive URL route from a page file path
88
+ function deriveRoute(pageFile) {
89
+ const rel = pageFile.replace(new RegExp(`^${pagesDir}[\\/]`), '')
90
+ const parts = rel.split('/').slice(0, -1)
91
+ if (parts.length === 1 && parts[0] === 'index') return '/'
92
+ return '/' + parts.join('/')
93
+ }
94
+
95
+ /**
96
+ * Converts an OWL XML template to best-effort static HTML.
97
+ * - Strips t-name on the root element
98
+ * - Strips all t-* attributes
99
+ * - Unwraps bare <t> elements (replaces with their inner content)
100
+ * - Replaces PascalCase component tags with an HTML comment placeholder
101
+ */
102
+ function xmlToStaticHtml(xml) {
103
+ let html = xml
104
+ // Remove t-name attribute from root
105
+ html = html.replace(/\s+t-name="[^"]*"/g, '')
106
+ // Remove all t-* attributes (handles both t-if="..." and bare t-else/t-else)
107
+ html = html.replace(/\s+t-[\w-]+(="[^"]*")?/g, '')
108
+ // Unwrap bare <t> wrapper elements (self-closing)
109
+ html = html.replace(/<t\s*\/>/g, '')
110
+ // Unwrap <t ...> ... </t> blocks — replace opening/closing tags with content
111
+ html = html.replace(/<t(?:\s[^>]*)?>([\s\S]*?)<\/t>/g, (_, inner) => inner)
112
+ // Replace PascalCase component tags with a comment stub
113
+ // Matches <ComponentName ... /> and <ComponentName ...></ComponentName>
114
+ html = html.replace(/<([A-Z][A-Za-z0-9]*)\s*\/>/g, '<!-- $1 -->')
115
+ html = html.replace(/<([A-Z][A-Za-z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>/g, '<!-- $1 -->')
116
+ return html.trim()
117
+ }
118
+
119
+ // Build final HTML shell for a page
120
+ function buildShell(baseHtml, pageFile) {
121
+ let html = baseHtml
122
+
123
+ // Extract meta tags from JS source (Meta.title(...), Meta.description(...) etc.)
124
+ const jsSource = readFileSync(resolve(cwd, pageFile), 'utf-8')
125
+ const meta = extractMetaFromJs(jsSource)
126
+ html = injectMeta(html, meta)
127
+
128
+ // Inject static HTML from OWL XML template (auto-extracted, best-effort)
129
+ const xmlFile = resolve(cwd, pageFile.replace(/\.js$/, '.xml'))
130
+ if (existsSync(xmlFile)) {
131
+ const xmlContent = readFileSync(xmlFile, 'utf-8')
132
+ const staticHtml = xmlToStaticHtml(xmlContent)
133
+ if (staticHtml) {
134
+ html = html.replace(/(<div\s+id="metaowl"[^>]*>)(<\/div>)/, `$1${staticHtml}$2`)
135
+ }
136
+ }
137
+
138
+ return html
139
+ }
140
+
141
+ // 1. Lint
142
+ run('Linting', `node "${metaowlRoot}/bin/metaowl-lint.js"`)
143
+
144
+ // 2. Vite build
145
+ run('Building', `"${bin}/vite" build`)
146
+
147
+ // 3. SSG post-processing
148
+ step('Generating static pages...')
149
+ console.log()
150
+ const baseHtml = readFileSync(resolve(cwd, outDir, 'index.html'), 'utf-8')
151
+
152
+ const pageFiles = globSync(`${pagesDir}/**/*.js`, { cwd })
153
+
154
+ const seen = new Set()
155
+
156
+ for (const pageFile of pageFiles) {
157
+ const route = deriveRoute(pageFile)
158
+ if (seen.has(route)) continue
159
+ seen.add(route)
160
+
161
+ const shell = buildShell(baseHtml, pageFile)
162
+
163
+ if (route === '/') {
164
+ writeFileSync(resolve(cwd, outDir, 'index.html'), shell)
165
+ console.log(` /index.html`)
166
+ } else {
167
+ const destDir = resolve(cwd, outDir, route.slice(1))
168
+ mkdirSync(destDir, { recursive: true })
169
+ writeFileSync(resolve(destDir, 'index.html'), shell)
170
+ console.log(` ${route}/index.html`)
171
+ }
172
+ }
173
+
174
+ console.log()
175
+ success(`${seen.size} route(s) generated`)
176
+ console.log()
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * metaowl lint — format with Prettier then lint with ESLint.
4
+ *
5
+ * Lint targets can be configured in package.json under `metaowl.lint`:
6
+ *
7
+ * "metaowl": {
8
+ * "lint": ["src/app.js", "src/pages/**", "src/components/**"]
9
+ * }
10
+ */
11
+ import { execSync } from 'node:child_process'
12
+ import { existsSync, readFileSync } from 'node:fs'
13
+ import { resolve } from 'node:path'
14
+ import { globSync } from 'glob'
15
+ import { banner, bin, cwd, step, success, failure } from './utils.js'
16
+
17
+ banner('lint')
18
+
19
+ let lintTargets = null
20
+ try {
21
+ const pkg = JSON.parse(readFileSync(resolve(cwd, 'package.json'), 'utf8'))
22
+ lintTargets = pkg?.metaowl?.lint ?? null
23
+ } catch {
24
+ // no package.json or no metaowl config
25
+ }
26
+
27
+ const defaults = [
28
+ 'src/metaowl.js',
29
+ 'src/css.js',
30
+ 'src/owl/pages/**',
31
+ 'src/owl/components/**'
32
+ ]
33
+
34
+ const candidates = lintTargets ?? defaults
35
+
36
+ const existing = candidates.filter(pattern => {
37
+ if (existsSync(resolve(cwd, pattern))) return true
38
+ return globSync(pattern, { cwd }).length > 0
39
+ })
40
+
41
+ if (existing.length === 0) {
42
+ success('No lint targets found — skipping')
43
+ console.log()
44
+ process.exit(0)
45
+ }
46
+
47
+ const targets = existing.map(t => `"${t}"`).join(' ')
48
+
49
+ step('Formatting with Prettier...')
50
+ console.log()
51
+ try {
52
+ execSync(`"${bin}/prettier" src --single-quote --no-semi --write`, { stdio: 'inherit', cwd })
53
+ } catch {
54
+ failure('Prettier failed')
55
+ process.exit(1)
56
+ }
57
+ console.log()
58
+
59
+ step('Linting with ESLint...')
60
+ console.log()
61
+ try {
62
+ execSync(`"${bin}/eslint" ${targets} --fix`, { stdio: 'inherit', cwd })
63
+ } catch {
64
+ failure('ESLint failed')
65
+ process.exit(1)
66
+ }
67
+ console.log()
68
+
69
+ success('Lint complete')
70
+ console.log()
71
+
package/bin/utils.js ADDED
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Shared CLI utilities for metaowl bin scripts.
3
+ * Uses ANSI escape codes only when stdout is a TTY (no color when piped).
4
+ */
5
+ import { readFileSync } from 'node:fs'
6
+ import { resolve, dirname } from 'node:path'
7
+ import { fileURLToPath } from 'node:url'
8
+ import { execSync } from 'node:child_process'
9
+
10
+ export const metaowlRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
11
+ export const bin = resolve(metaowlRoot, 'node_modules/.bin')
12
+ export const cwd = process.cwd()
13
+
14
+ const { version } = JSON.parse(readFileSync(resolve(metaowlRoot, 'package.json'), 'utf-8'))
15
+ export { version }
16
+
17
+ const TTY = Boolean(process.stdout.isTTY)
18
+ const a = (str, code) => TTY ? `\x1b[${code}m${str}\x1b[0m` : str
19
+
20
+ /** Print a styled header for the current command. */
21
+ export function banner(command) {
22
+ console.log()
23
+ console.log(` ${a('metaowl', '1;36')} ${a(command, '1')} ${a(`v${version}`, '2')}`)
24
+ console.log()
25
+ }
26
+
27
+ /** Print a step indicator: " › message" */
28
+ export function step(msg) {
29
+ console.log(` ${a('›', '36')} ${msg}`)
30
+ }
31
+
32
+ /** Print a success line: " ✓ message" */
33
+ export function success(msg) {
34
+ console.log(` ${a('✓', '32')} ${a(msg, '2')}`)
35
+ }
36
+
37
+ /** Print an error line: " ✗ message" */
38
+ export function failure(msg) {
39
+ console.error(` ${a('✗', '31')} ${msg}`)
40
+ }
41
+
42
+ /**
43
+ * Run a shell command, printing a step label before and a blank line after.
44
+ * Exits the process with code 1 on failure.
45
+ *
46
+ * @param {string} label - Human-readable step description.
47
+ * @param {string} cmd - Shell command to execute.
48
+ * @param {object} [opts] - Additional options forwarded to execSync.
49
+ */
50
+ export function run(label, cmd, opts = {}) {
51
+ step(label)
52
+ console.log()
53
+ try {
54
+ execSync(cmd, { stdio: 'inherit', cwd, ...opts })
55
+ } catch {
56
+ console.log()
57
+ failure(`${label} failed`)
58
+ process.exit(1)
59
+ }
60
+ console.log()
61
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "compilerOptions": {}
3
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
6
+ "allowJs": false,
7
+ "skipLibCheck": true,
8
+ "esModuleInterop": false,
9
+ "allowSyntheticDefaultImports": true,
10
+ "strict": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "module": "ESNext",
13
+ "moduleResolution": "Node",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "noEmit": true
17
+ }
18
+ }
package/eslint.js ADDED
@@ -0,0 +1,49 @@
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+
4
+ /**
5
+ * Default metaowl ESLint configuration.
6
+ *
7
+ * Usage in your project's eslint.config.js:
8
+ *
9
+ * import { eslintConfig } from 'metaowl/eslint'
10
+ * export default eslintConfig
11
+ *
12
+ * To extend/override:
13
+ *
14
+ * import { eslintConfig } from 'metaowl/eslint'
15
+ * export default [
16
+ * ...eslintConfig,
17
+ * { rules: { 'no-console': 'warn' } }
18
+ * ]
19
+ */
20
+ export const eslintConfig = [
21
+ js.configs.recommended,
22
+ {
23
+ languageOptions: {
24
+ ecmaVersion: 2022,
25
+ sourceType: 'module',
26
+ globals: {
27
+ ...globals.browser,
28
+ ...globals.node,
29
+ COMPONENTS: 'readonly'
30
+ }
31
+ },
32
+ files: ['**/*.js', '**/*.jsx'],
33
+ rules: {
34
+ 'no-unused-vars': ['error', {
35
+ argsIgnorePattern: '^_',
36
+ varsIgnorePattern: '^_'
37
+ }],
38
+ 'semi': ['error', 'never'],
39
+ 'quotes': ['error', 'single'],
40
+ 'comma-dangle': ['error', 'never'],
41
+ 'no-undef': 'off'
42
+ },
43
+ ignores: [
44
+ 'node_modules/**',
45
+ 'dist/**',
46
+ 'build/**'
47
+ ]
48
+ }
49
+ ]
package/index.js ADDED
@@ -0,0 +1,32 @@
1
+ import { processRoutes } from './modules/router.js'
2
+ import { mountApp } from './modules/app-mounter.js'
3
+ import { buildRoutes } from './modules/file-router.js'
4
+
5
+ export { default as Fetch } from './modules/fetch.js'
6
+ export { default as Cache } from './modules/cache.js'
7
+ export { configureOwl } from './modules/app-mounter.js'
8
+ export * as Meta from './modules/meta.js'
9
+ export { buildRoutes }
10
+
11
+ /**
12
+ * Boots the metaowl application.
13
+ *
14
+ * When called without arguments inside a Vite project, the `metaowl:app`
15
+ * plugin transform automatically rewrites `boot()` to
16
+ * `boot(import.meta.glob('./pages/**\/*.js', { eager: true }))` at build time.
17
+ *
18
+ * Can also be called explicitly with:
19
+ * - An import.meta.glob result (file-based routing):
20
+ * boot(import.meta.glob('./pages/**\/*.js', { eager: true }))
21
+ * - A manual route array:
22
+ * boot([{ name: 'index', path: ['/'], component: IndexPage }])
23
+ *
24
+ * @param {Record<string, object>|object[]} [routesOrModules]
25
+ */
26
+ export async function boot(routesOrModules = {}) {
27
+ const routes = Array.isArray(routesOrModules)
28
+ ? routesOrModules
29
+ : buildRoutes(routesOrModules)
30
+ const route = await processRoutes(routes)
31
+ await mountApp(route)
32
+ }