metaowl 0.4.0 → 0.5.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 (79) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +13 -15
  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 +141 -0
  10. package/build/runtime/modules/app-mounter.js +65 -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/error-boundary.js +116 -0
  15. package/build/runtime/modules/fetch.js +31 -0
  16. package/build/runtime/modules/file-router.js +205 -0
  17. package/build/runtime/modules/forms.js +193 -0
  18. package/build/runtime/modules/i18n.js +167 -0
  19. package/build/runtime/modules/layouts.js +163 -0
  20. package/build/runtime/modules/link.js +141 -0
  21. package/build/runtime/modules/meta.js +117 -0
  22. package/build/runtime/modules/odoo-rpc.js +264 -0
  23. package/build/runtime/modules/pwa.js +262 -0
  24. package/build/runtime/modules/router.js +389 -0
  25. package/build/runtime/modules/seo.js +186 -0
  26. package/build/runtime/modules/store.js +196 -0
  27. package/build/runtime/modules/templates-manager.js +52 -0
  28. package/build/runtime/modules/test-utils.js +238 -0
  29. package/build/runtime/vite/plugin.js +183 -0
  30. package/eslint.js +29 -0
  31. package/package.json +29 -11
  32. package/CONTRIBUTING.md +0 -49
  33. package/bin/metaowl-build.js +0 -12
  34. package/bin/metaowl-dev.js +0 -12
  35. package/bin/metaowl-generate.js +0 -339
  36. package/bin/metaowl-lint.js +0 -71
  37. package/bin/utils.js +0 -82
  38. package/index.js +0 -328
  39. package/modules/app-mounter.js +0 -104
  40. package/modules/auto-import.js +0 -225
  41. package/modules/cache.js +0 -59
  42. package/modules/composables.js +0 -600
  43. package/modules/error-boundary.js +0 -228
  44. package/modules/fetch.js +0 -51
  45. package/modules/file-router.js +0 -478
  46. package/modules/forms.js +0 -353
  47. package/modules/i18n.js +0 -333
  48. package/modules/layouts.js +0 -431
  49. package/modules/link.js +0 -255
  50. package/modules/meta.js +0 -119
  51. package/modules/odoo-rpc.js +0 -511
  52. package/modules/pwa.js +0 -515
  53. package/modules/router.js +0 -769
  54. package/modules/seo.js +0 -501
  55. package/modules/store.js +0 -409
  56. package/modules/templates-manager.js +0 -89
  57. package/modules/test-utils.js +0 -532
  58. package/test/auto-import.test.js +0 -110
  59. package/test/cache.test.js +0 -55
  60. package/test/composables.test.js +0 -103
  61. package/test/dynamic-routes.test.js +0 -469
  62. package/test/error-boundary.test.js +0 -126
  63. package/test/fetch.test.js +0 -100
  64. package/test/file-router.test.js +0 -55
  65. package/test/forms.test.js +0 -203
  66. package/test/i18n.test.js +0 -188
  67. package/test/layouts.test.js +0 -395
  68. package/test/link.test.js +0 -189
  69. package/test/meta.test.js +0 -146
  70. package/test/odoo-rpc.test.js +0 -547
  71. package/test/pwa.test.js +0 -154
  72. package/test/router-guards.test.js +0 -229
  73. package/test/router.test.js +0 -77
  74. package/test/seo.test.js +0 -353
  75. package/test/store.test.js +0 -476
  76. package/test/templates-manager.test.js +0 -83
  77. package/test/test-utils.test.js +0 -314
  78. package/vite/plugin.js +0 -277
  79. 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
- })