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.
package/README.md ADDED
@@ -0,0 +1,543 @@
1
+ # metaowl
2
+
3
+ > A lightweight meta-framework for [Odoo OWL](https://github.com/odoo/owl), built on top of [Vite](https://vitejs.dev).
4
+
5
+ [![npm version](https://img.shields.io/npm/v/metaowl.svg)](https://www.npmjs.com/package/metaowl)
6
+ [![License: LGPL v3](https://img.shields.io/badge/License-LGPL_v3-blue.svg)](LICENSE)
7
+ [![Node.js >=18](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org)
8
+ [![GitHub Issues](https://img.shields.io/github/issues/dennisschott/metaowl.svg)](https://github.com/dennisschott/metaowl/issues)
9
+
10
+ metaowl gives you everything you need to ship production-ready OWL applications — file-based routing, app mounting, a fetch helper, `localStorage` cache, meta tag management, an SSG generator, and a batteries-included Vite plugin — so you can focus on building components instead of wiring infrastructure.
11
+
12
+ ---
13
+
14
+ ## Table of Contents
15
+
16
+ - [Features](#features)
17
+ - [Requirements](#requirements)
18
+ - [Installation](#installation)
19
+ - [Create a New Project](#create-a-new-project)
20
+ - [Manual Setup](#manual-setup)
21
+ - [File-based Routing](#file-based-routing)
22
+ - [CLI Reference](#cli-reference)
23
+ - [API Reference](#api-reference)
24
+ - [boot](#bootroutes)
25
+ - [Fetch](#fetch)
26
+ - [Cache](#cache)
27
+ - [Meta](#meta)
28
+ - [configureOwl](#configureowlconfig)
29
+ - [buildRoutes](#buildroutesmodules)
30
+ - [Vite Plugin](#vite-plugin)
31
+ - [metaowlPlugin](#metaowlpluginoptions)
32
+ - [metaowlConfig](#metaowlconfigoptions)
33
+ - [ESLint Config](#eslint-config)
34
+ - [PostCSS Config](#postcss-config)
35
+ - [TypeScript / jsconfig](#typescript--jsconfig)
36
+ - [Contributing](#contributing)
37
+ - [License](#license)
38
+
39
+ ---
40
+
41
+ ## Features
42
+
43
+ - **File-based routing** — mirrors Nuxt/Next.js conventions out of the box
44
+ - **App mounting** — zero-config OWL component mounting with template merging
45
+ - **Fetch helper** — thin wrapper around the Fetch API with a configurable base URL and error handler
46
+ - **Cache** — async-style `localStorage` wrapper (`get`, `set`, `remove`, `clear`, `keys`)
47
+ - **Meta tags** — programmatic control over `<title>`, Open Graph, Twitter Card, canonical, and more
48
+ - **SSG generator** — statically pre-renders HTML pages with correct meta tags at build time
49
+ - **Vite plugin** — handles `COMPONENTS` injection, XML template copying, CSS auto-import, chunk splitting, and env filtering
50
+ - **ESLint & PostCSS** — shareable configs included; no extra dev-dependencies needed in your project
51
+
52
+ ---
53
+
54
+ ## Requirements
55
+
56
+ | Dependency | Version |
57
+ |---|---|
58
+ | Node.js | `>=18` |
59
+ | `@odoo/owl` | bundled |
60
+
61
+ ---
62
+
63
+ ## Installation
64
+
65
+ ```bash
66
+ npm install metaowl
67
+ ```
68
+
69
+ `@odoo/owl` is bundled with metaowl and resolved automatically — no separate installation required.
70
+
71
+ ---
72
+
73
+ ## Create a New Project
74
+
75
+ The fastest way to get started is the `metaowl-create` scaffolder:
76
+
77
+ ```bash
78
+ npx metaowl-create my-app
79
+ ```
80
+
81
+ Or install metaowl globally and run it interactively:
82
+
83
+ ```bash
84
+ npm install -g metaowl
85
+ metaowl-create
86
+ ```
87
+
88
+ This generates a ready-to-run project:
89
+
90
+ ```
91
+ my-app/
92
+ ├── package.json
93
+ ├── vite.config.js
94
+ ├── eslint.config.js
95
+ ├── postcss.config.cjs
96
+ ├── jsconfig.json
97
+ ├── .gitignore
98
+ └── src/
99
+ ├── index.html
100
+ ├── metaowl.js
101
+ ├── css.js
102
+ ├── components/
103
+ │ ├── AppHeader/
104
+ │ │ ├── AppHeader.js
105
+ │ │ ├── AppHeader.xml
106
+ │ │ └── AppHeader.css
107
+ │ └── AppFooter/
108
+ │ ├── AppFooter.js
109
+ │ ├── AppFooter.xml
110
+ │ └── AppFooter.css
111
+ └── pages/
112
+ └── index/
113
+ ├── Index.js
114
+ ├── Index.xml
115
+ └── index.css
116
+ ```
117
+
118
+ Then:
119
+
120
+ ```bash
121
+ cd my-app
122
+ npm install
123
+ npm run dev
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Manual Setup
129
+
130
+ ### 1. `vite.config.js`
131
+
132
+ Use the convenience wrapper for a sensible default configuration:
133
+
134
+ ```js
135
+ import { metaowlConfig } from 'metaowl/vite'
136
+
137
+ export default metaowlConfig({
138
+ componentsDir: 'src/owl/components',
139
+ pagesDir: 'src/owl/pages',
140
+ server: { port: 3000 },
141
+ preview: { port: 4173 }
142
+ })
143
+ ```
144
+
145
+ Or compose the plugin into your own config:
146
+
147
+ ```js
148
+ import { defineConfig } from 'vite'
149
+ import { metaowlPlugin } from 'metaowl/vite'
150
+
151
+ export default defineConfig({
152
+ plugins: [
153
+ metaowlPlugin({
154
+ componentsDir: 'src/owl/components',
155
+ pagesDir: 'src/owl/pages'
156
+ })
157
+ ]
158
+ })
159
+ ```
160
+
161
+ ### 2. `src/metaowl.js`
162
+
163
+ ```js
164
+ import { boot, Fetch } from 'metaowl'
165
+
166
+ Fetch.configure({
167
+ baseUrl: import.meta.env.VITE_API_URL ?? ''
168
+ })
169
+
170
+ // File-based routing — boot() with no args is the recommended convention.
171
+ // The metaowl Vite plugin expands it to import.meta.glob at build time.
172
+ boot()
173
+
174
+ // — or — manual route table
175
+ // import routes from './routes.js'
176
+ // boot(routes)
177
+ ```
178
+
179
+ ### 3. `src/index.html`
180
+
181
+ ```html
182
+ <!doctype html>
183
+ <html lang="en">
184
+ <head>
185
+ <meta charset="UTF-8" />
186
+ <title>My App</title>
187
+ </head>
188
+ <body>
189
+ <div id="metaowl"></div>
190
+ <script type="module" src="/metaowl.js"></script>
191
+ </body>
192
+ </html>
193
+ ```
194
+
195
+ ---
196
+
197
+ ## File-based Routing
198
+
199
+ Pages placed in `pagesDir` are automatically mapped to URL paths using the same conventions as Nuxt and Next.js:
200
+
201
+ | File | URL |
202
+ |---|---|
203
+ | `pages/index/Index.js` | `/` |
204
+ | `pages/about/About.js` | `/about` |
205
+ | `pages/blog/post/Post.js` | `/blog/post` |
206
+
207
+ **Rules:**
208
+ - The *directory* path relative to `pages/` becomes the URL segment.
209
+ - A top-level directory named `index` maps to `/`.
210
+ - The component must be the `default` export, or the first function export in the module.
211
+
212
+ Enable file-based routing by passing `import.meta.glob` to `boot()`:
213
+
214
+ ```js
215
+ boot(import.meta.glob('./pages/**/*.js', { eager: true }))
216
+ ```
217
+
218
+ SSG path variants (`.html`, trailing slash, `index.html`) are added automatically so your builds work with any static host.
219
+
220
+ ---
221
+
222
+ ## CLI Reference
223
+
224
+ metaowl ships four CLI commands that use its own bundled Vite, Prettier, and ESLint binaries — no need to install them separately in your project.
225
+
226
+ | Command | Description |
227
+ |---|---|
228
+ | `metaowl-create` | Scaffold a new project interactively |
229
+ | `metaowl-dev` | Start the Vite development server |
230
+ | `metaowl-build` | Lint then production build (Rollup via Vite) |
231
+ | `metaowl-generate` | Lint, build, then SSG — pre-renders every page to static HTML |
232
+ | `metaowl-lint` | Run Prettier + ESLint across project source files |
233
+
234
+ Add them to your `package.json` scripts:
235
+
236
+ ```json
237
+ {
238
+ "scripts": {
239
+ "dev": "metaowl-dev",
240
+ "build": "metaowl-build",
241
+ "generate": "metaowl-generate",
242
+ "lint": "metaowl-lint"
243
+ }
244
+ }
245
+ ```
246
+
247
+ ### Configuring lint targets
248
+
249
+ By default, `metaowl-lint` targets `src/metaowl.js`, `src/css.js`, `src/owl/pages/**`, and `src/owl/components/**`. Override in `package.json`:
250
+
251
+ ```json
252
+ {
253
+ "metaowl": {
254
+ "lint": ["src/metaowl.js", "src/pages/**", "src/components/**"]
255
+ }
256
+ }
257
+ ```
258
+
259
+ ### Configuring SSG output
260
+
261
+ `metaowl-generate` reads `pagesDir` and `outDir` from `package.json`:
262
+
263
+ ```json
264
+ {
265
+ "metaowl": {
266
+ "pagesDir": "src/pages",
267
+ "outDir": "dist"
268
+ }
269
+ }
270
+ ```
271
+
272
+ ---
273
+
274
+ ## API Reference
275
+
276
+ ### `boot(routes)`
277
+
278
+ Resolves the current URL against a route table and mounts the matching OWL component into `#app`.
279
+
280
+ ```ts
281
+ boot(routesOrModules: Record<string, object> | RouteDefinition[]): Promise<void>
282
+ ```
283
+
284
+ Accepts either:
285
+ - An `import.meta.glob` result (file-based routing, recommended)
286
+ - A manual array of route objects: `{ name, path: string[], component }`
287
+
288
+ ---
289
+
290
+ ### `Fetch`
291
+
292
+ A static class wrapping the Fetch API with a shared base URL and error handler.
293
+
294
+ #### `Fetch.configure(options)`
295
+
296
+ Call once before `boot()`.
297
+
298
+ ```ts
299
+ Fetch.configure({
300
+ baseUrl?: string, // Prepended to every internal request
301
+ onError?: Function // Invoked on network errors
302
+ })
303
+ ```
304
+
305
+ #### `Fetch.url(url, method?, data?, internal?, triggerErrorHandler?)`
306
+
307
+ ```ts
308
+ Fetch.url(
309
+ url: string,
310
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' = 'GET',
311
+ data: object | null = null,
312
+ internal: boolean = true,
313
+ triggerErrorHandler: boolean = true
314
+ ): Promise<any | null>
315
+ ```
316
+
317
+ When `internal` is `true` (default), `baseUrl` is prepended to `url`. Returns the parsed JSON response, or `null` on error.
318
+
319
+ ---
320
+
321
+ ### `Cache`
322
+
323
+ A static async-style `localStorage` wrapper. Values are automatically JSON-serialised.
324
+
325
+ ```ts
326
+ Cache.get(key: string): Promise<any>
327
+ Cache.set(key: string, value: any): Promise<void>
328
+ Cache.remove(key: string): Promise<void>
329
+ Cache.clear(): Promise<void>
330
+ Cache.keys(): Promise<string[]>
331
+ ```
332
+
333
+ ---
334
+
335
+ ### `Meta`
336
+
337
+ Programmatically set document meta tags. Each function is idempotent — the tag is created if it does not already exist.
338
+
339
+ ```ts
340
+ import { Meta } from 'metaowl'
341
+
342
+ Meta.title('My Page')
343
+ Meta.description('Page description')
344
+ Meta.keywords('owl, odoo, framework')
345
+ Meta.author('Jane Doe')
346
+ Meta.canonical('https://example.com/page')
347
+
348
+ // Open Graph
349
+ Meta.ogTitle('My Page')
350
+ Meta.ogDescription('Page description')
351
+ Meta.ogImage('https://example.com/og.png')
352
+ Meta.ogUrl('https://example.com/page')
353
+
354
+ // Twitter Card
355
+ Meta.twitterCard('summary_large_image')
356
+ Meta.twitterTitle('My Page')
357
+ Meta.twitterDescription('Page description')
358
+ Meta.twitterImage('https://example.com/og.png')
359
+ ```
360
+
361
+ The full list of helpers: `title`, `description`, `keywords`, `author`, `canonical`, `ogTitle`, `ogDescription`, `ogImage`, `ogUrl`, `ogLocale`, `ogImageWidth`, `ogImageHeight`, `twitterCard`, `twitterSite`, `twitterCreator`, `twitterTitle`, `twitterDescription`, `twitterImage`, `twitterImageAlt`, `twitterUrl`, `twitterSiteId`, `twitterCreatorId`.
362
+
363
+ ---
364
+
365
+ ### `configureOwl(config)`
366
+
367
+ Override the default OWL `mount()` options before calling `boot()`.
368
+
369
+ ```ts
370
+ configureOwl(config: Partial<OwlMountConfig>): void
371
+ ```
372
+
373
+ **Defaults:**
374
+
375
+ ```js
376
+ {
377
+ warnIfNoStaticProps: true,
378
+ willStartTimeout: 10000,
379
+ translatableAttributes: ['title', 'placeholder', 'label', 'alt']
380
+ }
381
+ ```
382
+
383
+ ---
384
+
385
+ ### `buildRoutes(modules)`
386
+
387
+ Converts an `import.meta.glob` result into a metaowl route table. Called automatically by `boot()` when a glob result is passed.
388
+
389
+ ```ts
390
+ buildRoutes(modules: Record<string, object>): RouteDefinition[]
391
+ ```
392
+
393
+ ---
394
+
395
+ ## Vite Plugin
396
+
397
+ ### `metaowlPlugin(options)`
398
+
399
+ Returns an array of Vite plugins that configure the full metaowl build pipeline.
400
+
401
+ | Option | Default | Description |
402
+ |---|---|---|
403
+ | `root` | `'src'` | Vite root directory |
404
+ | `outDir` | `'../dist'` | Build output directory |
405
+ | `publicDir` | `'../public'` | Public assets directory |
406
+ | `componentsDir` | `'src/components'` | OWL components directory (glob base) |
407
+ | `pagesDir` | `'src/pages'` | OWL pages directory (glob base) |
408
+ | `vendorPackages` | `['@odoo/owl']` | npm packages bundled into the `vendor` chunk |
409
+ | `frameworkEntry` | `'./node_modules/metaowl/index.js'` | Entry for the `framework` chunk |
410
+ | `restartGlobs` | `[]` | Additional globs that trigger dev-server restart |
411
+ | `envPrefix` | `undefined` | Only expose `process.env` vars with this prefix (plus `NODE_ENV`) |
412
+
413
+ **What the plugin does:**
414
+
415
+ - Injects `COMPONENTS` (array of XML template paths) and `DEV_MODE` as global defines
416
+ - Filters `process.env` to prevent accidental secret leakage
417
+ - Copies OWL XML templates and `assets/images` to the output directory after build
418
+ - Auto-imports CSS/SCSS files from `componentsDir` and `pagesDir`
419
+ - Configures Rollup chunk splitting (`vendor` + `framework`)
420
+ - Resolves `@odoo/owl` to the bundled ES module
421
+ - Enables TypeScript path aliases via `vite-tsconfig-paths`
422
+
423
+ ---
424
+
425
+ ### `metaowlConfig(options)`
426
+
427
+ A convenience wrapper that returns a complete `vite.UserConfig` with sensible defaults.
428
+
429
+ ```js
430
+ import { metaowlConfig } from 'metaowl/vite'
431
+
432
+ export default metaowlConfig({
433
+ componentsDir: 'src/owl/components',
434
+ pagesDir: 'src/owl/pages',
435
+ vendorPackages: ['@odoo/owl', 'apexcharts'],
436
+ envPrefix: 'APP_',
437
+ server: { port: 3333 },
438
+ preview: { port: 8080 }
439
+ })
440
+ ```
441
+
442
+ `server`, `preview`, and `build` are applied directly to the Vite config; all other options are forwarded to `metaowlPlugin`.
443
+
444
+ ---
445
+
446
+ ## ESLint Config
447
+
448
+ metaowl ships a ready-to-use flat ESLint config. No additional ESLint packages are needed in your project.
449
+
450
+ ```js
451
+ // eslint.config.js
452
+ import { eslintConfig } from 'metaowl/eslint'
453
+ export default eslintConfig
454
+ ```
455
+
456
+ Extend or override rules:
457
+
458
+ ```js
459
+ import { eslintConfig } from 'metaowl/eslint'
460
+
461
+ export default [
462
+ ...eslintConfig,
463
+ {
464
+ rules: {
465
+ 'no-console': 'warn'
466
+ }
467
+ }
468
+ ]
469
+ ```
470
+
471
+ ---
472
+
473
+ ## PostCSS Config
474
+
475
+ metaowl ships a PostCSS config factory that enables [PurgeCSS](https://purgecss.com) in production builds to eliminate unused styles.
476
+
477
+ ```js
478
+ // postcss.config.cjs
479
+ const { createPostcssConfig } = require('metaowl/postcss')
480
+ module.exports = createPostcssConfig()
481
+ ```
482
+
483
+ Extend with a custom safelist or additional content globs:
484
+
485
+ ```js
486
+ module.exports = createPostcssConfig({
487
+ safelist: [/^my-custom-/, 'another-class'],
488
+ content: ['./templates/**/*.html']
489
+ })
490
+ ```
491
+
492
+ PurgeCSS scans `.xml`, `.html`, and `src/**/*.js` files by default.
493
+
494
+ ---
495
+
496
+ ## TypeScript / jsconfig
497
+
498
+ Extend from the included base configs to get sensible defaults:
499
+
500
+ **`jsconfig.json`** (JavaScript projects):
501
+
502
+ ```json
503
+ {
504
+ "extends": "./node_modules/metaowl/config/jsconfig.base.json",
505
+ "compilerOptions": {
506
+ "baseUrl": "src",
507
+ "paths": {
508
+ "@pages/*": ["owl/pages/*"],
509
+ "@components/*": ["owl/components/*"]
510
+ }
511
+ },
512
+ "include": ["src"]
513
+ }
514
+ ```
515
+
516
+ **`tsconfig.json`** (TypeScript projects):
517
+
518
+ ```json
519
+ {
520
+ "extends": "./node_modules/metaowl/config/tsconfig.base.json",
521
+ "compilerOptions": {
522
+ "baseUrl": "src"
523
+ },
524
+ "include": ["src"]
525
+ }
526
+ ```
527
+
528
+ ---
529
+
530
+ ## Contributing
531
+
532
+ Contributions are welcome! Please open an issue before submitting a pull request so we can discuss the change.
533
+
534
+ 1. Fork the repository
535
+ 2. Create a feature branch: `git checkout -b feat/my-feature`
536
+ 3. Commit your changes following [Conventional Commits](https://www.conventionalcommits.org)
537
+ 4. Open a pull request
538
+
539
+ ---
540
+
541
+ ## License
542
+
543
+ [LGPL v3](LICENSE) © [Dennis Schott](https://github.com/dennisschott)
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * metaowl build — lint then production build.
4
+ */
5
+ import { banner, bin, cwd, metaowlRoot, run, success } from './utils.js'
6
+
7
+ banner('build')
8
+ run('Linting', `node "${metaowlRoot}/bin/metaowl-lint.js"`)
9
+ run('Building', `"${bin}/vite" build`)
10
+ success('Build complete')
11
+ console.log()
12
+