metaowl 0.4.1 → 0.6.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.
Files changed (83) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +267 -2
  3. package/build/runtime/bin/metaowl-build.js +10 -0
  4. package/{bin → build/runtime/bin}/metaowl-create.js +96 -177
  5. package/build/runtime/bin/metaowl-dev.js +10 -0
  6. package/build/runtime/bin/metaowl-generate.js +231 -0
  7. package/build/runtime/bin/metaowl-lint.js +58 -0
  8. package/build/runtime/bin/utils.js +68 -0
  9. package/build/runtime/index.js +144 -0
  10. package/build/runtime/modules/app-mounter.js +73 -0
  11. package/build/runtime/modules/auto-import.js +140 -0
  12. package/build/runtime/modules/cache.js +49 -0
  13. package/build/runtime/modules/composables.js +353 -0
  14. package/build/runtime/modules/constants.js +38 -0
  15. package/build/runtime/modules/error-boundary.js +116 -0
  16. package/build/runtime/modules/fetch.js +31 -0
  17. package/build/runtime/modules/file-router.js +207 -0
  18. package/build/runtime/modules/fonts.js +172 -0
  19. package/build/runtime/modules/forms.js +193 -0
  20. package/build/runtime/modules/i18n.js +180 -0
  21. package/build/runtime/modules/image.js +175 -0
  22. package/build/runtime/modules/layouts.js +214 -0
  23. package/build/runtime/modules/link.js +141 -0
  24. package/build/runtime/modules/meta.js +117 -0
  25. package/build/runtime/modules/odoo-rpc.js +265 -0
  26. package/build/runtime/modules/pwa.js +272 -0
  27. package/build/runtime/modules/router.js +384 -0
  28. package/build/runtime/modules/seo.js +186 -0
  29. package/build/runtime/modules/store.js +198 -0
  30. package/build/runtime/modules/templates-manager.js +52 -0
  31. package/build/runtime/modules/test-utils.js +238 -0
  32. package/build/runtime/vite/plugin.js +197 -0
  33. package/eslint.js +29 -0
  34. package/package.json +45 -27
  35. package/CONTRIBUTING.md +0 -49
  36. package/bin/metaowl-build.js +0 -12
  37. package/bin/metaowl-dev.js +0 -12
  38. package/bin/metaowl-generate.js +0 -339
  39. package/bin/metaowl-lint.js +0 -71
  40. package/bin/utils.js +0 -82
  41. package/eslint.config.js +0 -3
  42. package/index.js +0 -328
  43. package/modules/app-mounter.js +0 -104
  44. package/modules/auto-import.js +0 -225
  45. package/modules/cache.js +0 -59
  46. package/modules/composables.js +0 -600
  47. package/modules/error-boundary.js +0 -228
  48. package/modules/fetch.js +0 -51
  49. package/modules/file-router.js +0 -478
  50. package/modules/forms.js +0 -353
  51. package/modules/i18n.js +0 -333
  52. package/modules/layouts.js +0 -431
  53. package/modules/link.js +0 -255
  54. package/modules/meta.js +0 -119
  55. package/modules/odoo-rpc.js +0 -511
  56. package/modules/pwa.js +0 -515
  57. package/modules/router.js +0 -769
  58. package/modules/seo.js +0 -501
  59. package/modules/store.js +0 -409
  60. package/modules/templates-manager.js +0 -89
  61. package/modules/test-utils.js +0 -532
  62. package/test/auto-import.test.js +0 -110
  63. package/test/cache.test.js +0 -55
  64. package/test/composables.test.js +0 -103
  65. package/test/dynamic-routes.test.js +0 -469
  66. package/test/error-boundary.test.js +0 -126
  67. package/test/fetch.test.js +0 -100
  68. package/test/file-router.test.js +0 -55
  69. package/test/forms.test.js +0 -203
  70. package/test/i18n.test.js +0 -188
  71. package/test/layouts.test.js +0 -395
  72. package/test/link.test.js +0 -189
  73. package/test/meta.test.js +0 -146
  74. package/test/odoo-rpc.test.js +0 -547
  75. package/test/pwa.test.js +0 -154
  76. package/test/router-guards.test.js +0 -229
  77. package/test/router.test.js +0 -77
  78. package/test/seo.test.js +0 -353
  79. package/test/store.test.js +0 -476
  80. package/test/templates-manager.test.js +0 -83
  81. package/test/test-utils.test.js +0 -314
  82. package/vite/plugin.js +0 -290
  83. package/vitest.config.js +0 -8
package/test/seo.test.js DELETED
@@ -1,353 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import {
3
- generateSitemap,
4
- generateRobotsTxt,
5
- jsonLd,
6
- createCanonicalUrl,
7
- generateOpenGraph,
8
- generateTwitterCard,
9
- validateSitemap,
10
- getPriorityByDepth,
11
- generateSitemapIndex,
12
- SEO
13
- } from '../modules/seo.js'
14
-
15
- describe('SEO', () => {
16
- describe('Exports', () => {
17
- it('should export all functions', () => {
18
- expect(typeof generateSitemap).toBe('function')
19
- expect(typeof generateRobotsTxt).toBe('function')
20
- expect(typeof jsonLd).toBe('function')
21
- expect(typeof createCanonicalUrl).toBe('function')
22
- expect(typeof generateOpenGraph).toBe('function')
23
- expect(typeof generateTwitterCard).toBe('function')
24
- expect(typeof validateSitemap).toBe('function')
25
- expect(typeof getPriorityByDepth).toBe('function')
26
- expect(typeof generateSitemapIndex).toBe('function')
27
- })
28
-
29
- it('should export SEO namespace', () => {
30
- expect(SEO.generateSitemap).toBe(generateSitemap)
31
- expect(SEO.generateRobotsTxt).toBe(generateRobotsTxt)
32
- expect(SEO.jsonLd).toBe(jsonLd)
33
- })
34
- })
35
-
36
- describe('generateSitemap', () => {
37
- it('should generate basic sitemap', () => {
38
- const sitemap = generateSitemap([
39
- { url: '/', priority: 1.0 },
40
- { url: '/about', priority: 0.8 }
41
- ], { baseUrl: 'https://example.com' })
42
-
43
- expect(sitemap).toContain('<?xml version="1.0" encoding="UTF-8"?>')
44
- expect(sitemap).toContain('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"')
45
- expect(sitemap).toContain('<loc>https://example.com/</loc>')
46
- expect(sitemap).toContain('<loc>https://example.com/about</loc>')
47
- expect(sitemap).toContain('<priority>1.0</priority>')
48
- })
49
-
50
- it('should include changefreq when provided', () => {
51
- const sitemap = generateSitemap([
52
- { url: '/', changefreq: 'daily' }
53
- ], { baseUrl: 'https://example.com' })
54
-
55
- expect(sitemap).toContain('<changefreq>daily</changefreq>')
56
- })
57
-
58
- it('should include lastmod when provided', () => {
59
- const sitemap = generateSitemap([
60
- { url: '/', lastmod: '2024-01-15' }
61
- ], { baseUrl: 'https://example.com' })
62
-
63
- expect(sitemap).toContain('<lastmod>2024-01-15</lastmod>')
64
- })
65
-
66
- it('should handle absolute URLs', () => {
67
- const sitemap = generateSitemap([
68
- { url: 'https://other.com/page' }
69
- ], { baseUrl: 'https://example.com' })
70
-
71
- expect(sitemap).toContain('<loc>https://other.com/page</loc>')
72
- })
73
-
74
- it('should normalize baseUrl trailing slash', () => {
75
- const sitemap = generateSitemap(
76
- [{ url: '/page' }],
77
- { baseUrl: 'https://example.com/' }
78
- )
79
-
80
- expect(sitemap).toContain('<loc>https://example.com/page</loc>')
81
- })
82
-
83
- it('should throw without baseUrl', () => {
84
- expect(() => generateSitemap([{ url: '/' }])).toThrow('baseUrl is required')
85
- })
86
-
87
- it('should clamp priority between 0 and 1', () => {
88
- const sitemap = generateSitemap([
89
- { url: '/high', priority: 2.0 },
90
- { url: '/low', priority: -0.5 }
91
- ], { baseUrl: 'https://example.com' })
92
-
93
- expect(sitemap).toContain('<priority>1.0</priority>')
94
- expect(sitemap).toContain('<priority>0.0</priority>')
95
- })
96
- })
97
-
98
- describe('generateRobotsTxt', () => {
99
- it('should generate basic robots.txt', () => {
100
- const robots = generateRobotsTxt({
101
- userAgent: '*',
102
- allow: ['/'],
103
- disallow: ['/admin/', '/private/']
104
- })
105
-
106
- expect(robots).toContain('User-agent: *')
107
- expect(robots).toContain('Allow: /')
108
- expect(robots).toContain('Disallow: /admin/')
109
- expect(robots).toContain('Disallow: /private/')
110
- })
111
-
112
- it('should include sitemap when provided', () => {
113
- const robots = generateRobotsTxt({
114
- userAgent: '*',
115
- sitemap: 'https://example.com/sitemap.xml'
116
- })
117
-
118
- expect(robots).toContain('Sitemap: https://example.com/sitemap.xml')
119
- })
120
-
121
- it('should include host when provided', () => {
122
- const robots = generateRobotsTxt({
123
- userAgent: '*',
124
- host: 'https://example.com'
125
- })
126
-
127
- expect(robots).toContain('Host: https://example.com')
128
- })
129
-
130
- it('should handle crawl delay', () => {
131
- const robots = generateRobotsTxt({
132
- userAgent: '*',
133
- crawlDelay: 10
134
- })
135
-
136
- expect(robots).toContain('Crawl-delay: 10')
137
- })
138
-
139
- it('should generate multiple user-agent sections', () => {
140
- const robots = generateRobotsTxt([
141
- { userAgent: 'Googlebot', allow: ['/'] },
142
- { userAgent: 'Bingbot', allow: ['/'], disallow: ['/admin/'] }
143
- ])
144
-
145
- expect(robots).toContain('User-agent: Googlebot')
146
- expect(robots).toContain('User-agent: Bingbot')
147
- })
148
- })
149
-
150
- describe('jsonLd', () => {
151
- it('should generate JSON-LD with default context', () => {
152
- const schema = jsonLd({
153
- '@type': 'Organization',
154
- name: 'My Company'
155
- })
156
-
157
- const parsed = JSON.parse(schema)
158
- expect(parsed['@context']).toBe('https://schema.org')
159
- expect(parsed['@type']).toBe('Organization')
160
- expect(parsed.name).toBe('My Company')
161
- })
162
-
163
- it('should allow custom context', () => {
164
- const schema = jsonLd({
165
- '@context': 'https://custom.org',
166
- '@type': 'Product'
167
- })
168
-
169
- const parsed = JSON.parse(schema)
170
- expect(parsed['@context']).toBe('https://custom.org')
171
- })
172
-
173
- it('should format JSON with indentation', () => {
174
- const schema = jsonLd({ '@type': 'Thing' })
175
- expect(schema).toContain('\n')
176
- expect(schema).toContain(' ')
177
- })
178
- })
179
-
180
- describe('createCanonicalUrl', () => {
181
- it('should create canonical URL', () => {
182
- const url = createCanonicalUrl('https://example.com', '/page')
183
- expect(url).toBe('https://example.com/page')
184
- })
185
-
186
- it('should preserve query parameters', () => {
187
- const url = createCanonicalUrl('https://example.com', '/page?id=123')
188
- expect(url).toBe('https://example.com/page?id=123')
189
- })
190
-
191
- it('should remove query parameters when specified', () => {
192
- const url = createCanonicalUrl(
193
- 'https://example.com',
194
- '/page?id=123',
195
- { removeQueryParams: true }
196
- )
197
- expect(url).toBe('https://example.com/page')
198
- })
199
-
200
- it('should filter allowed query parameters', () => {
201
- const url = createCanonicalUrl(
202
- 'https://example.com',
203
- '/page?id=123&utm_source=email',
204
- { allowedParams: ['id'] }
205
- )
206
- expect(url).toBe('https://example.com/page?id=123')
207
- })
208
-
209
- it('should normalize base URL slash', () => {
210
- const url = createCanonicalUrl('https://example.com/', '/page')
211
- expect(url).toBe('https://example.com/page')
212
- })
213
- })
214
-
215
- describe('generateOpenGraph', () => {
216
- it('should generate Open Graph tags', () => {
217
- const tags = generateOpenGraph({
218
- title: 'My Page',
219
- description: 'Description',
220
- url: 'https://example.com/page',
221
- image: 'https://example.com/image.png'
222
- })
223
-
224
- expect(tags['og:title']).toBe('My Page')
225
- expect(tags['og:description']).toBe('Description')
226
- expect(tags['og:type']).toBe('website')
227
- expect(tags['og:url']).toBe('https://example.com/page')
228
- expect(tags['og:image']).toBe('https://example.com/image.png')
229
- })
230
-
231
- it('should use custom type', () => {
232
- const tags = generateOpenGraph({
233
- title: 'Article',
234
- type: 'article'
235
- })
236
-
237
- expect(tags['og:type']).toBe('article')
238
- })
239
-
240
- it('should include site_name when provided', () => {
241
- const tags = generateOpenGraph({
242
- title: 'Page',
243
- siteName: 'My Site'
244
- })
245
-
246
- expect(tags['og:site_name']).toBe('My Site')
247
- })
248
- })
249
-
250
- describe('generateTwitterCard', () => {
251
- it('should generate Twitter Card tags', () => {
252
- const tags = generateTwitterCard({
253
- title: 'My Page',
254
- description: 'Description',
255
- image: 'https://example.com/image.png'
256
- })
257
-
258
- expect(tags['twitter:card']).toBe('summary_large_image')
259
- expect(tags['twitter:title']).toBe('My Page')
260
- expect(tags['twitter:description']).toBe('Description')
261
- expect(tags['twitter:image']).toBe('https://example.com/image.png')
262
- })
263
-
264
- it('should use custom card type', () => {
265
- const tags = generateTwitterCard({
266
- title: 'Page',
267
- card: 'summary'
268
- })
269
-
270
- expect(tags['twitter:card']).toBe('summary')
271
- })
272
-
273
- it('should include site handle when provided', () => {
274
- const tags = generateTwitterCard({
275
- title: 'Page',
276
- site: '@myhandle'
277
- })
278
-
279
- expect(tags['twitter:site']).toBe('@myhandle')
280
- })
281
- })
282
-
283
- describe('validateSitemap', () => {
284
- it('should validate correct entries', () => {
285
- const result = validateSitemap([
286
- { url: '/', priority: 0.5, changefreq: 'daily' }
287
- ])
288
-
289
- expect(result.valid).toBe(true)
290
- expect(result.errors).toHaveLength(0)
291
- })
292
-
293
- it('should detect missing url', () => {
294
- const result = validateSitemap([{ priority: 0.5 }])
295
-
296
- expect(result.valid).toBe(false)
297
- expect(result.errors[0]).toContain('Missing required')
298
- })
299
-
300
- it('should detect invalid priority', () => {
301
- const result = validateSitemap([{ url: '/', priority: 2.0 }])
302
-
303
- expect(result.valid).toBe(false)
304
- expect(result.errors[0]).toContain('Priority must be between')
305
- })
306
-
307
- it('should detect invalid changefreq', () => {
308
- const result = validateSitemap([{ url: '/', changefreq: 'invalid' }])
309
-
310
- expect(result.valid).toBe(false)
311
- expect(result.errors[0]).toContain('Invalid changefreq')
312
- })
313
-
314
- it('should detect invalid lastmod', () => {
315
- const result = validateSitemap([{ url: '/', lastmod: 'not-a-date' }])
316
-
317
- expect(result.valid).toBe(false)
318
- expect(result.errors[0]).toContain('Invalid lastmod')
319
- })
320
- })
321
-
322
- describe('getPriorityByDepth', () => {
323
- it('should return 1.0 for root', () => {
324
- expect(getPriorityByDepth('/')).toBe(1.0)
325
- })
326
-
327
- it('should decrease priority by depth', () => {
328
- expect(getPriorityByDepth('/products')).toBeGreaterThan(
329
- getPriorityByDepth('/products/electronics/phones')
330
- )
331
- })
332
-
333
- it('should respect maxDepth option', () => {
334
- const priority = getPriorityByDepth('/a/b/c/d', { maxDepth: 5 })
335
- expect(priority).toBeGreaterThanOrEqual(0.1)
336
- })
337
- })
338
-
339
- describe('generateSitemapIndex', () => {
340
- it('should generate sitemap index', () => {
341
- const index = generateSitemapIndex([
342
- { loc: 'https://example.com/sitemap1.xml', lastmod: '2024-01-01' },
343
- { loc: 'https://example.com/sitemap2.xml' }
344
- ])
345
-
346
- expect(index).toContain('<?xml version="1.0" encoding="UTF-8"?>')
347
- expect(index).toContain('<sitemapindex')
348
- expect(index).toContain('<loc>https://example.com/sitemap1.xml</loc>')
349
- expect(index).toContain('<lastmod>2024-01-01</lastmod>')
350
- expect(index).toContain('<loc>https://example.com/sitemap2.xml</loc>')
351
- })
352
- })
353
- })