nuxt-schema-org 6.1.3 → 6.2.1

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 (127) hide show
  1. package/dist/devtools/components/schema-org/SchemaValidator.vue +326 -0
  2. package/dist/devtools/lib/schema-org/rpc.ts +47 -0
  3. package/dist/devtools/lib/schema-org/state.ts +15 -0
  4. package/dist/devtools/lib/schema-org/util/schema-validation.ts +470 -0
  5. package/dist/devtools/nuxt.config.ts +7 -0
  6. package/dist/devtools/pages/schema-org/debug.vue +23 -0
  7. package/dist/devtools/pages/schema-org/docs.vue +3 -0
  8. package/dist/devtools/pages/schema-org/index.vue +8 -0
  9. package/dist/devtools/pages/schema-org/raw.vue +25 -0
  10. package/dist/devtools/pages/schema-org.vue +56 -0
  11. package/dist/module.cjs +28 -7
  12. package/dist/module.d.cts +1 -1
  13. package/dist/module.d.mts +1 -1
  14. package/dist/module.d.ts +1 -1
  15. package/dist/module.json +1 -1
  16. package/dist/module.mjs +28 -7
  17. package/dist/schema.cjs +2147 -9
  18. package/dist/schema.d.cts +185 -1
  19. package/dist/schema.d.mts +185 -1
  20. package/dist/schema.d.ts +185 -1
  21. package/dist/schema.mjs +2035 -1
  22. package/dist/shared/nuxt-schema-org.BQ-jW5j5.d.cts +2770 -0
  23. package/dist/shared/nuxt-schema-org.BQ-jW5j5.d.mts +2770 -0
  24. package/dist/shared/nuxt-schema-org.BQ-jW5j5.d.ts +2770 -0
  25. package/dist/vendor/schema-org-v2/LICENSE +21 -0
  26. package/dist/vendor/schema-org-v2/chunks/index.mjs +23 -0
  27. package/dist/vendor/schema-org-v2/chunks/index10.mjs +63 -0
  28. package/dist/vendor/schema-org-v2/chunks/index11.mjs +35 -0
  29. package/dist/vendor/schema-org-v2/chunks/index12.mjs +48 -0
  30. package/dist/vendor/schema-org-v2/chunks/index13.mjs +36 -0
  31. package/dist/vendor/schema-org-v2/chunks/index14.mjs +25 -0
  32. package/dist/vendor/schema-org-v2/chunks/index15.mjs +37 -0
  33. package/dist/vendor/schema-org-v2/chunks/index16.mjs +27 -0
  34. package/dist/vendor/schema-org-v2/chunks/index17.mjs +34 -0
  35. package/dist/vendor/schema-org-v2/chunks/index18.mjs +32 -0
  36. package/dist/vendor/schema-org-v2/chunks/index19.mjs +31 -0
  37. package/dist/vendor/schema-org-v2/chunks/index2.mjs +12 -0
  38. package/dist/vendor/schema-org-v2/chunks/index20.mjs +31 -0
  39. package/dist/vendor/schema-org-v2/chunks/index21.mjs +30 -0
  40. package/dist/vendor/schema-org-v2/chunks/index22.mjs +30 -0
  41. package/dist/vendor/schema-org-v2/chunks/index23.mjs +82 -0
  42. package/dist/vendor/schema-org-v2/chunks/index24.mjs +14 -0
  43. package/dist/vendor/schema-org-v2/chunks/index25.mjs +33 -0
  44. package/dist/vendor/schema-org-v2/chunks/index26.mjs +31 -0
  45. package/dist/vendor/schema-org-v2/chunks/index27.mjs +29 -0
  46. package/dist/vendor/schema-org-v2/chunks/index28.mjs +12 -0
  47. package/dist/vendor/schema-org-v2/chunks/index29.mjs +46 -0
  48. package/dist/vendor/schema-org-v2/chunks/index3.mjs +317 -0
  49. package/dist/vendor/schema-org-v2/chunks/index30.mjs +53 -0
  50. package/dist/vendor/schema-org-v2/chunks/index31.mjs +41 -0
  51. package/dist/vendor/schema-org-v2/chunks/index32.mjs +26 -0
  52. package/dist/vendor/schema-org-v2/chunks/index33.mjs +47 -0
  53. package/dist/vendor/schema-org-v2/chunks/index34.mjs +29 -0
  54. package/dist/vendor/schema-org-v2/chunks/index35.mjs +34 -0
  55. package/dist/vendor/schema-org-v2/chunks/index36.mjs +33 -0
  56. package/dist/vendor/schema-org-v2/chunks/index37.mjs +34 -0
  57. package/dist/vendor/schema-org-v2/chunks/index38.mjs +51 -0
  58. package/dist/vendor/schema-org-v2/chunks/index39.mjs +17 -0
  59. package/dist/vendor/schema-org-v2/chunks/index4.mjs +54 -0
  60. package/dist/vendor/schema-org-v2/chunks/index40.mjs +29 -0
  61. package/dist/vendor/schema-org-v2/chunks/index5.mjs +31 -0
  62. package/dist/vendor/schema-org-v2/chunks/index6.mjs +29 -0
  63. package/dist/vendor/schema-org-v2/chunks/index7.mjs +35 -0
  64. package/dist/vendor/schema-org-v2/chunks/index8.mjs +18 -0
  65. package/dist/vendor/schema-org-v2/chunks/index9.mjs +20 -0
  66. package/dist/vendor/schema-org-v2/index.d.mts +32 -0
  67. package/dist/vendor/schema-org-v2/index.d.ts +32 -0
  68. package/dist/vendor/schema-org-v2/index.mjs +46 -0
  69. package/dist/vendor/schema-org-v2/shared/schema-org.BR4WcSqZ.d.ts +21 -0
  70. package/dist/vendor/schema-org-v2/shared/schema-org.Ba7D0Hp1.mjs +19 -0
  71. package/dist/vendor/schema-org-v2/shared/schema-org.CAKsqzbX.d.ts +1022 -0
  72. package/dist/vendor/schema-org-v2/shared/schema-org.CFcsqFfN.d.mts +1824 -0
  73. package/dist/vendor/schema-org-v2/shared/schema-org.CFcsqFfN.d.ts +1824 -0
  74. package/dist/vendor/schema-org-v2/shared/schema-org.CHbRCiep.mjs +52 -0
  75. package/dist/vendor/schema-org-v2/shared/schema-org.Dryb3EoR.mjs +884 -0
  76. package/dist/vendor/schema-org-v2/shared/schema-org.F44ipjVJ.mjs +27 -0
  77. package/dist/vendor/schema-org-v2/shared/schema-org.UT1u1UYq.d.mts +1022 -0
  78. package/dist/vendor/schema-org-v2/shared/schema-org.oFHFm6my.d.mts +21 -0
  79. package/dist/vendor/schema-org-v2/vendor.json +4 -0
  80. package/dist/vendor/schema-org-v2/vue.d.mts +88 -0
  81. package/dist/vendor/schema-org-v2/vue.d.ts +88 -0
  82. package/dist/vendor/schema-org-v2/vue.mjs +262 -0
  83. package/dist/vendor/schema-org-v3/LICENSE +21 -0
  84. package/dist/vendor/schema-org-v3/index.d.mts +34 -0
  85. package/dist/vendor/schema-org-v3/index.d.ts +34 -0
  86. package/dist/vendor/schema-org-v3/index.mjs +4 -0
  87. package/dist/vendor/schema-org-v3/shared/schema-org.D9o9UOh2.d.mts +11 -0
  88. package/dist/vendor/schema-org-v3/shared/schema-org.DNQroCjX.d.mts +2770 -0
  89. package/dist/vendor/schema-org-v3/shared/schema-org.DNQroCjX.d.ts +2770 -0
  90. package/dist/vendor/schema-org-v3/shared/schema-org.DYFTMLZ0.mjs +2035 -0
  91. package/dist/vendor/schema-org-v3/shared/schema-org.DjDPlqPB.d.mts +150 -0
  92. package/dist/vendor/schema-org-v3/shared/schema-org.mKZYVZ3E.d.ts +150 -0
  93. package/dist/vendor/schema-org-v3/shared/schema-org.rS-TANmN.d.ts +11 -0
  94. package/dist/vendor/schema-org-v3/vendor.json +4 -0
  95. package/dist/vendor/schema-org-v3/vue.d.mts +100 -0
  96. package/dist/vendor/schema-org-v3/vue.d.ts +100 -0
  97. package/dist/vendor/schema-org-v3/vue.mjs +302 -0
  98. package/package.json +12 -12
  99. package/dist/devtools/200.html +0 -1
  100. package/dist/devtools/404.html +0 -1
  101. package/dist/devtools/_nuxt/B1DdtD2d.js +0 -30
  102. package/dist/devtools/_nuxt/BOpHBWj1.js +0 -1
  103. package/dist/devtools/_nuxt/BdQEHo9i.js +0 -1
  104. package/dist/devtools/_nuxt/BvDCh0XJ.js +0 -1
  105. package/dist/devtools/_nuxt/C6vgB62o.js +0 -1
  106. package/dist/devtools/_nuxt/CCjZBs6a.js +0 -3
  107. package/dist/devtools/_nuxt/CHHO6nw6.js +0 -1
  108. package/dist/devtools/_nuxt/CQr7N5ou.js +0 -6
  109. package/dist/devtools/_nuxt/CRABcd13.js +0 -1
  110. package/dist/devtools/_nuxt/Cy67RPmZ.js +0 -1
  111. package/dist/devtools/_nuxt/D-Oj-loM.js +0 -154
  112. package/dist/devtools/_nuxt/D6u4txBo.js +0 -1
  113. package/dist/devtools/_nuxt/DKZUJ--l.js +0 -1
  114. package/dist/devtools/_nuxt/DLXftbE1.js +0 -1
  115. package/dist/devtools/_nuxt/DevtoolsSnippet.CuW0O0Zu.css +0 -1
  116. package/dist/devtools/_nuxt/QcnxyHc5.js +0 -1
  117. package/dist/devtools/_nuxt/builds/latest.json +0 -1
  118. package/dist/devtools/_nuxt/builds/meta/b29085bd-1521-40f3-8c81-bdc818184bef.json +0 -1
  119. package/dist/devtools/_nuxt/entry.DprwPCib.css +0 -2
  120. package/dist/devtools/_nuxt/fira-code.Bc8wnsZt.woff2 +0 -0
  121. package/dist/devtools/_nuxt/hubot-sans.DLGyhQVu.woff2 +0 -0
  122. package/dist/devtools/_nuxt/pages.BpqfEWCe.css +0 -1
  123. package/dist/devtools/debug/index.html +0 -1
  124. package/dist/devtools/docs/index.html +0 -1
  125. package/dist/devtools/index.html +0 -1
  126. package/dist/devtools/nodes/index.html +0 -1
  127. package/dist/devtools/raw/index.html +0 -1
@@ -0,0 +1,470 @@
1
+ // Rich result schema types that Google supports
2
+ export const richResultTypes = new Set([
3
+ 'Article',
4
+ 'NewsArticle',
5
+ 'BlogPosting',
6
+ 'ScholarlyArticle',
7
+ 'Product',
8
+ 'AggregateOffer',
9
+ 'Offer',
10
+ 'FAQPage',
11
+ 'Question',
12
+ 'HowTo',
13
+ 'HowToStep',
14
+ 'Recipe',
15
+ 'Event',
16
+ 'LocalBusiness',
17
+ 'Restaurant',
18
+ 'JobPosting',
19
+ 'Course',
20
+ 'Movie',
21
+ 'Book',
22
+ 'SoftwareApplication',
23
+ 'VideoObject',
24
+ 'Review',
25
+ 'AggregateRating',
26
+ 'BreadcrumbList',
27
+ 'SearchAction',
28
+ 'Dataset',
29
+ 'SpecialAnnouncement',
30
+ 'Person',
31
+ 'NewsMediaOrganization',
32
+ 'Organization',
33
+ ])
34
+
35
+ // Google Rich Results requirements for each schema type
36
+ export const googleRichResultsRequirements: Record<string, {
37
+ required: string[]
38
+ recommended: string[]
39
+ documentationUrl: string
40
+ }> = {
41
+ Article: {
42
+ required: [],
43
+ recommended: ['author', 'author.name', 'dateModified', 'datePublished', 'headline', 'image'],
44
+ documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/article',
45
+ },
46
+ NewsArticle: {
47
+ required: [],
48
+ recommended: ['author', 'author.name', 'dateModified', 'datePublished', 'headline', 'image'],
49
+ documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/article',
50
+ },
51
+ BlogPosting: {
52
+ required: [],
53
+ recommended: ['author', 'author.name', 'dateModified', 'datePublished', 'headline', 'image'],
54
+ documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/article',
55
+ },
56
+ Product: {
57
+ required: ['name'],
58
+ recommended: ['description', 'offers', 'offers.price', 'offers.priceCurrency', 'offers.availability', 'aggregateRating', 'review', 'image'],
59
+ documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/product',
60
+ },
61
+ FAQPage: {
62
+ required: ['mainEntity'],
63
+ recommended: [],
64
+ documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/faqpage',
65
+ },
66
+ Recipe: {
67
+ required: ['name', 'image'],
68
+ recommended: ['aggregateRating', 'author', 'cookTime', 'datePublished', 'description', 'keywords', 'nutrition', 'prepTime', 'recipeCategory', 'recipeCuisine', 'recipeIngredient', 'recipeInstructions', 'recipeYield', 'totalTime', 'video'],
69
+ documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/recipe',
70
+ },
71
+ Event: {
72
+ required: ['name', 'location', 'startDate'],
73
+ recommended: ['description', 'endDate', 'eventStatus', 'image', 'offers', 'performer', 'organizer'],
74
+ documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/event',
75
+ },
76
+ LocalBusiness: {
77
+ required: ['name', 'address'],
78
+ recommended: ['aggregateRating', 'department', 'geo', 'openingHoursSpecification', 'priceRange', 'telephone', 'url'],
79
+ documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/local-business',
80
+ },
81
+ Restaurant: {
82
+ required: ['name', 'address'],
83
+ recommended: ['aggregateRating', 'servesCuisine', 'hasMenu', 'geo', 'openingHoursSpecification', 'priceRange', 'telephone', 'url'],
84
+ documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/local-business',
85
+ },
86
+ Review: {
87
+ required: ['author', 'itemReviewed', 'reviewRating'],
88
+ recommended: ['datePublished', 'reviewRating.bestRating', 'reviewRating.worstRating'],
89
+ documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/review-snippet',
90
+ },
91
+ AggregateRating: {
92
+ required: ['itemReviewed', 'ratingValue'],
93
+ recommended: ['bestRating', 'worstRating', 'ratingCount', 'reviewCount'],
94
+ documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/review-snippet',
95
+ },
96
+ BreadcrumbList: {
97
+ required: ['itemListElement'],
98
+ recommended: [],
99
+ documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/breadcrumb',
100
+ },
101
+ Organization: {
102
+ required: [],
103
+ recommended: ['name', 'logo', 'url', 'email', 'telephone', 'contactPoint', 'sameAs', 'address', 'description'],
104
+ documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/organization',
105
+ },
106
+ Person: {
107
+ required: [],
108
+ recommended: ['name', 'url', 'image', 'sameAs', 'jobTitle', 'worksFor'],
109
+ documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/person',
110
+ },
111
+ SoftwareApplication: {
112
+ required: ['name', 'offers'],
113
+ recommended: ['applicationCategory', 'operatingSystem', 'aggregateRating', 'review'],
114
+ documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/software-app',
115
+ },
116
+ VideoObject: {
117
+ required: ['name', 'thumbnailUrl', 'uploadDate'],
118
+ recommended: ['contentUrl', 'description', 'duration', 'embedUrl'],
119
+ documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/video',
120
+ },
121
+ JobPosting: {
122
+ required: ['datePosted', 'description', 'hiringOrganization', 'jobLocation', 'title'],
123
+ recommended: ['applicantLocationRequirements', 'baseSalary', 'employmentType', 'jobLocationType', 'validThrough'],
124
+ documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/job-posting',
125
+ },
126
+ Course: {
127
+ required: ['description', 'name'],
128
+ recommended: ['provider'],
129
+ documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/course',
130
+ },
131
+ }
132
+
133
+ // Recursively extract all typed objects from a schema node
134
+ function extractNestedTypes(obj: any, nodes: any[], seen: Set<any>): void {
135
+ if (!obj || typeof obj !== 'object' || seen.has(obj))
136
+ return
137
+ seen.add(obj)
138
+
139
+ if (obj['@type'] && Object.keys(obj).length > 1) {
140
+ nodes.push(obj)
141
+ }
142
+
143
+ for (const value of Object.values(obj)) {
144
+ if (Array.isArray(value)) {
145
+ for (const item of value) {
146
+ extractNestedTypes(item, nodes, seen)
147
+ }
148
+ }
149
+ else if (typeof value === 'object') {
150
+ extractNestedTypes(value, nodes, seen)
151
+ }
152
+ }
153
+ }
154
+
155
+ // Resolve @id references in a node
156
+ function resolveNodeReferences(node: any, nodeMap: Map<string, any>): any {
157
+ const resolved = { ...node }
158
+
159
+ Object.keys(resolved).forEach((key) => {
160
+ const value = resolved[key]
161
+
162
+ if (
163
+ value
164
+ && typeof value === 'object'
165
+ && !Array.isArray(value)
166
+ && Object.keys(value).length === 1
167
+ && value['@id']
168
+ && nodeMap.has(value['@id'])
169
+ ) {
170
+ resolved[key] = { ...nodeMap.get(value['@id']) }
171
+ }
172
+ else if (Array.isArray(value)) {
173
+ resolved[key] = value.map((item: any) => {
174
+ if (
175
+ item
176
+ && typeof item === 'object'
177
+ && Object.keys(item).length === 1
178
+ && item['@id']
179
+ && nodeMap.has(item['@id'])
180
+ ) {
181
+ return { ...nodeMap.get(item['@id']) }
182
+ }
183
+ return item
184
+ })
185
+ }
186
+ else if (value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length > 1) {
187
+ resolved[key] = resolveNodeReferences(value, nodeMap)
188
+ }
189
+ })
190
+
191
+ return resolved
192
+ }
193
+
194
+ // Extract all nodes from a schema graph (including nested @graph and nested types)
195
+ export function extractSchemaNodes(data: any): any[] {
196
+ const nodes: any[] = []
197
+ if (!data)
198
+ return nodes
199
+
200
+ const seen = new Set<any>()
201
+
202
+ if (data['@graph'] && Array.isArray(data['@graph'])) {
203
+ const nodeMap = new Map<string, any>()
204
+ data['@graph'].forEach((node: any) => {
205
+ if (node && typeof node === 'object' && node['@id']) {
206
+ nodeMap.set(node['@id'], node)
207
+ }
208
+ })
209
+
210
+ const addedIds = new Set<string>()
211
+
212
+ data['@graph'].forEach((node: any) => {
213
+ if (node && typeof node === 'object') {
214
+ if (Object.keys(node).length === 1 && node['@id'])
215
+ return
216
+ if (node['@id'] && addedIds.has(node['@id']))
217
+ return
218
+
219
+ const resolvedNode = resolveNodeReferences(node, nodeMap)
220
+ extractNestedTypes(resolvedNode, nodes, seen)
221
+
222
+ if (node['@id'])
223
+ addedIds.add(node['@id'])
224
+ }
225
+ })
226
+ }
227
+ else if (data['@type']) {
228
+ extractNestedTypes(data, nodes, seen)
229
+ }
230
+
231
+ // Deduplicate nodes that appear multiple times due to @id reference resolution
232
+ const uniqueNodes: any[] = []
233
+ const seenIds = new Set<string>()
234
+ for (const node of nodes) {
235
+ const id = node['@id']
236
+ if (id) {
237
+ if (seenIds.has(id))
238
+ continue
239
+ seenIds.add(id)
240
+ }
241
+ uniqueNodes.push(node)
242
+ }
243
+
244
+ return uniqueNodes
245
+ }
246
+
247
+ export function getNodeType(node: any): string {
248
+ if (!node || !node['@type'])
249
+ return 'Unknown'
250
+ return Array.isArray(node['@type']) ? node['@type'][0] : node['@type']
251
+ }
252
+
253
+ export function isRichResultType(type: string): boolean {
254
+ return richResultTypes.has(type)
255
+ }
256
+
257
+ export function getNodeDescription(node: any): string {
258
+ const type = getNodeType(node)
259
+
260
+ if (node.name)
261
+ return node.name
262
+ if (node.headline)
263
+ return node.headline
264
+ if (node.title)
265
+ return node.title
266
+ if (node.description) {
267
+ return typeof node.description === 'string'
268
+ ? node.description.substring(0, 100) + (node.description.length > 100 ? '...' : '')
269
+ : ''
270
+ }
271
+ if (node['@id'])
272
+ return node['@id']
273
+ if (node.url)
274
+ return node.url
275
+
276
+ return `${type} Schema`
277
+ }
278
+
279
+ export function getNestedProperty(obj: any, path: string): any {
280
+ const parts = path.split('.')
281
+ let current = obj
282
+
283
+ for (const part of parts) {
284
+ if (current && typeof current === 'object' && part in current) {
285
+ current = current[part]
286
+ }
287
+ else {
288
+ return undefined
289
+ }
290
+ }
291
+
292
+ return current
293
+ }
294
+
295
+ export function analyzeNodeProperties(node: any): {
296
+ missingRequired: string[]
297
+ missingRecommended: string[]
298
+ presentProperties: Record<string, any>
299
+ } {
300
+ const type = getNodeType(node)
301
+ const requirements = googleRichResultsRequirements[type]
302
+
303
+ if (!requirements) {
304
+ return {
305
+ missingRequired: [],
306
+ missingRecommended: [],
307
+ presentProperties: {},
308
+ }
309
+ }
310
+
311
+ const missingRequired: string[] = []
312
+ const missingRecommended: string[] = []
313
+ const presentProperties: Record<string, any> = {}
314
+
315
+ requirements.required.forEach((prop) => {
316
+ const value = getNestedProperty(node, prop)
317
+ if (value === undefined || value === null || value === '') {
318
+ missingRequired.push(prop)
319
+ }
320
+ else {
321
+ presentProperties[prop] = value
322
+ }
323
+ })
324
+
325
+ requirements.recommended.forEach((prop) => {
326
+ const value = getNestedProperty(node, prop)
327
+ if (value === undefined || value === null || value === '') {
328
+ missingRecommended.push(prop)
329
+ }
330
+ else {
331
+ presentProperties[prop] = value
332
+ }
333
+ })
334
+
335
+ return { missingRequired, missingRecommended, presentProperties }
336
+ }
337
+
338
+ export function formatPropertyValue(value: any): string {
339
+ if (value === null || value === undefined)
340
+ return 'null'
341
+ if (typeof value === 'string')
342
+ return value.length > 50 ? `${value.substring(0, 50)}...` : value
343
+ if (typeof value === 'number')
344
+ return value.toString()
345
+ if (typeof value === 'boolean')
346
+ return value ? 'true' : 'false'
347
+ if (Array.isArray(value))
348
+ return `[${value.length} items]`
349
+ if (typeof value === 'object' && value['@type'])
350
+ return value['@type']
351
+ if (typeof value === 'object')
352
+ return '{...}'
353
+ return String(value)
354
+ }
355
+
356
+ export function getSchemaIcon(type: string): string {
357
+ const iconMap: Record<string, string> = {
358
+ Article: 'carbon:document',
359
+ NewsArticle: 'carbon:news',
360
+ BlogPosting: 'carbon:blog',
361
+ Product: 'carbon:shopping-cart',
362
+ FAQPage: 'carbon:help',
363
+ Organization: 'carbon:building',
364
+ LocalBusiness: 'carbon:location',
365
+ Person: 'carbon:user',
366
+ Event: 'carbon:calendar',
367
+ SoftwareApplication: 'carbon:application',
368
+ Recipe: 'carbon:restaurant',
369
+ HowTo: 'carbon:list-numbered',
370
+ WebSite: 'carbon:earth',
371
+ WebPage: 'carbon:page-first',
372
+ BreadcrumbList: 'carbon:flow',
373
+ VideoObject: 'carbon:video',
374
+ Review: 'carbon:star',
375
+ AggregateRating: 'carbon:star-filled',
376
+ SearchAction: 'carbon:search',
377
+ }
378
+ return iconMap[type] || 'carbon:code'
379
+ }
380
+
381
+ export interface ValidationSummary {
382
+ totalNodes: number
383
+ richResultNodes: number
384
+ totalErrors: number
385
+ totalWarnings: number
386
+ }
387
+
388
+ // Google structured data page slugs mapped to schema types
389
+ export const googleStructuredDataLinks: Record<string, string[]> = {
390
+ 'article': ['Article', 'NewsArticle', 'BlogPosting'],
391
+ 'book': ['Book'],
392
+ 'breadcrumb': ['BreadcrumbList'],
393
+ 'carousel': ['ItemList'],
394
+ 'course-info': ['Course'],
395
+ 'course': ['Course'],
396
+ 'dataset': ['Dataset'],
397
+ 'discussion-forum': ['DiscussionForumPosting'],
398
+ 'education-qa': ['Question', 'Answer'],
399
+ 'employer-rating': ['EmployerAggregateRating'],
400
+ 'estimated-salary': ['OccupationalExperienceRequirements'],
401
+ 'event': ['Event'],
402
+ 'factcheck': ['ClaimReview'],
403
+ 'faqpage': ['FAQPage'],
404
+ 'image-license-metadata': ['ImageObject'],
405
+ 'job-posting': ['JobPosting'],
406
+ 'learning-video': ['LearningResource', 'VideoObject'],
407
+ 'local-business': ['LocalBusiness'],
408
+ 'math-solvers': ['MathSolver'],
409
+ 'movie': ['Movie'],
410
+ 'organization': ['Organization'],
411
+ 'practice-problems': ['Quiz', 'Question'],
412
+ 'product': ['Product'],
413
+ 'product-snippet': ['Product'],
414
+ 'merchant-listing': ['Product', 'Offer'],
415
+ 'product-variants': ['Product'],
416
+ 'profile-page': ['ProfilePage', 'Person'],
417
+ 'qapage': ['QAPage'],
418
+ 'recipe': ['Recipe'],
419
+ 'review-snippet': ['Review'],
420
+ 'software-app': ['SoftwareApplication'],
421
+ 'speakable': ['SpeakableSpecification'],
422
+ 'special-announcements': ['SpecialAnnouncement'],
423
+ 'paywalled-content': ['CreativeWork'],
424
+ 'vacation-rental': ['Accommodation', 'LodgingBusiness'],
425
+ 'vehicle-listing': ['Vehicle'],
426
+ 'video': ['VideoObject'],
427
+ }
428
+
429
+ export function nodeToSchemaOrgLink(type: string) {
430
+ const simpleType = type.replace('https://schema.org/', '')
431
+ const googlePage = Object.entries(googleStructuredDataLinks)
432
+ .find(([_, types]) => types.includes(simpleType))?.[0]
433
+ return {
434
+ type: simpleType,
435
+ schemaOrg: `https://schema.org/${simpleType}`,
436
+ googlePage: googlePage ? `https://developers.google.com/search/docs/appearance/structured-data/${googlePage}` : null,
437
+ }
438
+ }
439
+
440
+ export function asArray<T>(value: T | T[]): T[] {
441
+ return Array.isArray(value) ? value : [value]
442
+ }
443
+
444
+ export function validateGraph(data: any): { nodes: any[], summary: ValidationSummary } {
445
+ const nodes = extractSchemaNodes(data)
446
+
447
+ let totalErrors = 0
448
+ let totalWarnings = 0
449
+ let richResultNodes = 0
450
+
451
+ for (const node of nodes) {
452
+ const type = getNodeType(node)
453
+ if (isRichResultType(type))
454
+ richResultNodes++
455
+
456
+ const analysis = analyzeNodeProperties(node)
457
+ totalErrors += analysis.missingRequired.length
458
+ totalWarnings += analysis.missingRecommended.length
459
+ }
460
+
461
+ return {
462
+ nodes,
463
+ summary: {
464
+ totalNodes: nodes.length,
465
+ richResultNodes,
466
+ totalErrors,
467
+ totalWarnings,
468
+ },
469
+ }
470
+ }
@@ -0,0 +1,7 @@
1
+ import { resolve } from 'pathe'
2
+
3
+ // Nuxt SEO devtools panel, shipped as a layer (Model C). Components flat-registered
4
+ // so intra-panel references resolve by name.
5
+ export default defineNuxtConfig({
6
+ components: [{ path: resolve(__dirname, './components'), pathPrefix: false }],
7
+ })
@@ -0,0 +1,23 @@
1
+ <script setup lang="ts">
2
+ import { fetchGlobalDebug } from '../../lib/schema-org/state'
3
+
4
+ const { data } = fetchGlobalDebug()
5
+ </script>
6
+
7
+ <template>
8
+ <div class="space-y-5 animate-fade-up">
9
+ <DevtoolsSection>
10
+ <template #text>
11
+ <h3 class="opacity-80 text-base mb-1">
12
+ <UIcon name="carbon:settings" class="mr-1" />
13
+ Runtime Config
14
+ </h3>
15
+ </template>
16
+ <DevtoolsSnippet
17
+ :code="JSON.stringify(data?.runtimeConfig || {}, null, 2)"
18
+ lang="json"
19
+ label="Runtime Config"
20
+ />
21
+ </DevtoolsSection>
22
+ </div>
23
+ </template>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <DevtoolsDocs url="https://nuxtseo.com/schema-org" />
3
+ </template>
@@ -0,0 +1,8 @@
1
+ <script setup lang="ts">
2
+ import SchemaValidator from '../../components/schema-org/SchemaValidator.vue'
3
+ import { schemaOrgGraph } from '../../lib/schema-org/rpc'
4
+ </script>
5
+
6
+ <template>
7
+ <SchemaValidator :graph="schemaOrgGraph" />
8
+ </template>
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import { schemaOrgGraph } from '../../lib/schema-org/rpc'
3
+ </script>
4
+
5
+ <template>
6
+ <div class="space-y-4">
7
+ <div class="flex items-center gap-3">
8
+ <UButton
9
+ icon="carbon:launch"
10
+ to="https://validator.schema.org/"
11
+ target="_blank"
12
+ >
13
+ Structured Data Test
14
+ </UButton>
15
+ <UButton
16
+ icon="carbon:launch"
17
+ to="https://search.google.com/test/rich-results"
18
+ target="_blank"
19
+ >
20
+ Rich Results Test
21
+ </UButton>
22
+ </div>
23
+ <DevtoolsSnippet :code="schemaOrgGraph" lang="json" label="schema.json" />
24
+ </div>
25
+ </template>
@@ -0,0 +1,56 @@
1
+ <script setup lang="ts">
2
+ import { loadShiki } from 'nuxtseo-layer-devtools/composables/shiki'
3
+ import { isProductionMode, refreshSources } from 'nuxtseo-layer-devtools/composables/state'
4
+ import { computed, watch } from 'vue'
5
+ import { navigateTo, useRoute } from '#imports'
6
+ import { schemaOrgGraph } from '../lib/schema-org/rpc'
7
+ import { fetchGlobalDebug } from '../lib/schema-org/state'
8
+
9
+ await loadShiki()
10
+
11
+ const { data } = fetchGlobalDebug()
12
+
13
+ const route = useRoute()
14
+ const currentTab = computed(() => {
15
+ const path = route.path
16
+ if (path.startsWith('/schema-org/raw'))
17
+ return 'raw'
18
+ if (path.startsWith('/schema-org/debug'))
19
+ return 'debug'
20
+ if (path.startsWith('/schema-org/docs'))
21
+ return 'docs'
22
+ return 'validate'
23
+ })
24
+
25
+ const navItems = [
26
+ { value: 'validate', to: '/schema-org', icon: 'carbon:connect-source', label: 'Nodes', devOnly: false },
27
+ { value: 'raw', to: '/schema-org/raw', icon: 'carbon:code', label: 'Raw', devOnly: true },
28
+ { value: 'debug', to: '/schema-org/debug', icon: 'carbon:debug', label: 'Debug', devOnly: true },
29
+ { value: 'docs', to: '/schema-org/docs', icon: 'carbon:book', label: 'Docs', devOnly: false },
30
+ ]
31
+
32
+ const runtimeVersion = computed(() => {
33
+ return data.value?.runtimeConfig?.version || 'unknown'
34
+ })
35
+
36
+ watch(isProductionMode, (isProd) => {
37
+ if (isProd && ['raw', 'debug'].includes(currentTab.value))
38
+ return navigateTo('/schema-org')
39
+ })
40
+ </script>
41
+
42
+ <template>
43
+ <DevtoolsLayout
44
+ v-model:active-tab="currentTab"
45
+ module-name="nuxt-schema-org"
46
+ title="Schema.org"
47
+ icon="carbon:image-search"
48
+ :version="runtimeVersion"
49
+ :nav-items="navItems"
50
+ github-url="https://github.com/harlan-zw/nuxt-schema-org"
51
+ :loading="!schemaOrgGraph || schemaOrgGraph === 'loading'"
52
+ @refresh="refreshSources"
53
+ >
54
+ <NuxtPage />
55
+ </DevtoolsLayout>
56
+ </template>
package/dist/module.cjs CHANGED
@@ -1,13 +1,14 @@
1
1
  'use strict';
2
2
 
3
+ const node_url = require('node:url');
3
4
  const kit = require('@nuxt/kit');
4
5
  const defu = require('defu');
5
6
  const kit$1 = require('nuxt-site-config/kit');
6
7
  const pkgTypes = require('pkg-types');
7
8
  const devtools = require('nuxtseo-shared/devtools');
8
9
  const kit$2 = require('nuxtseo-shared/kit');
10
+ const node_fs = require('node:fs');
9
11
  const node_path = require('node:path');
10
- const node_url = require('node:url');
11
12
 
12
13
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
13
14
  function setupDevToolsUI(_options, resolve, nuxt = kit.useNuxt()) {
@@ -79,9 +80,12 @@ async function resolveHostUnheadMajor(rootDir) {
79
80
  }
80
81
  return 3;
81
82
  }
82
- function schemaOrgVendor(major) {
83
+ function schemaOrgVendor(major, resolve) {
84
+ const dir = resolve(`./vendor/schema-org-v${major}`);
85
+ if (node_fs.existsSync(dir))
86
+ return { vendored: true, dir, main: `${dir}/index.mjs`, vue: `${dir}/vue.mjs` };
83
87
  const pkg = major === 2 ? "@unhead/schema-org-v2" : "@unhead/schema-org";
84
- return { main: pkg, vue: `${pkg}/vue` };
88
+ return { vendored: false, main: pkg, vue: `${pkg}/vue` };
85
89
  }
86
90
 
87
91
  const module$1 = kit.defineNuxtModule({
@@ -132,15 +136,32 @@ const module$1 = kit.defineNuxtModule({
132
136
  if (!nuxt.options.ssr && nuxt.options.dev)
133
137
  logger.warn("You are using Schema.org with SSR disabled. This is not recommended, Google may not detect your Schema.org, and it adds extra page weight");
134
138
  const unheadMajor = await resolveHostUnheadMajor(nuxt.options.rootDir);
135
- const vendor = schemaOrgVendor(unheadMajor);
136
- const { defineWebPage } = await import(vendor.main);
137
- const { schemaOrgAutoImports, schemaOrgComponents } = await import(vendor.vue);
138
- if (vendor.main !== "@unhead/schema-org") {
139
+ const vendor = schemaOrgVendor(unheadMajor, resolve);
140
+ const { defineWebPage } = await (vendor.vendored ? import(node_url.pathToFileURL(vendor.main).href) : import(vendor.main));
141
+ const { schemaOrgAutoImports, schemaOrgComponents } = await (vendor.vendored ? import(node_url.pathToFileURL(vendor.vue).href) : import(vendor.vue));
142
+ if (vendor.vendored) {
143
+ logger.debug(`Detected unhead v${unheadMajor}, aliasing @unhead/schema-org -> ${vendor.dir}`);
144
+ nuxt.options.alias["@unhead/schema-org/vue"] = vendor.vue;
145
+ nuxt.options.alias["@unhead/schema-org"] = vendor.main;
146
+ nuxt.options.build.transpile.push(vendor.dir);
147
+ nuxt.hooks.hook("nitro:config", (nitroConfig) => {
148
+ nitroConfig.alias = nitroConfig.alias || {};
149
+ nitroConfig.alias["@unhead/schema-org/vue"] = vendor.vue;
150
+ nitroConfig.alias["@unhead/schema-org"] = vendor.main;
151
+ nitroConfig.externals = nitroConfig.externals || {};
152
+ nitroConfig.externals.inline = nitroConfig.externals.inline || [];
153
+ nitroConfig.externals.inline.push(vendor.dir);
154
+ });
155
+ } else if (vendor.main !== "@unhead/schema-org") {
139
156
  logger.debug(`Detected unhead v${unheadMajor}, aliasing @unhead/schema-org -> ${vendor.main}`);
140
157
  nuxt.options.alias["@unhead/schema-org"] = vendor.main;
158
+ nuxt.options.build.transpile.push(vendor.main);
141
159
  nuxt.hooks.hook("nitro:config", (nitroConfig) => {
142
160
  nitroConfig.alias = nitroConfig.alias || {};
143
161
  nitroConfig.alias["@unhead/schema-org"] = vendor.main;
162
+ nitroConfig.externals = nitroConfig.externals || {};
163
+ nitroConfig.externals.inline = nitroConfig.externals.inline || [];
164
+ nitroConfig.externals.inline.push(vendor.main);
144
165
  });
145
166
  }
146
167
  await kit$1.installNuxtSiteConfig();
package/dist/module.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { NuxtModule } from '@nuxt/schema';
2
- import { OrganizationSimple, PersonSimple, LocalBusinessSimple } from '@unhead/schema-org';
2
+ import { O as OrganizationSimple, P as PersonSimple, L as LocalBusinessSimple } from './shared/nuxt-schema-org.BQ-jW5j5.cjs';
3
3
  import { Script } from '@unhead/vue/types';
4
4
 
5
5
  type SchemaOrgScriptAttributes = Partial<Script> & Record<string, unknown>;
package/dist/module.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { NuxtModule } from '@nuxt/schema';
2
- import { OrganizationSimple, PersonSimple, LocalBusinessSimple } from '@unhead/schema-org';
2
+ import { O as OrganizationSimple, P as PersonSimple, L as LocalBusinessSimple } from './shared/nuxt-schema-org.BQ-jW5j5.mjs';
3
3
  import { Script } from '@unhead/vue/types';
4
4
 
5
5
  type SchemaOrgScriptAttributes = Partial<Script> & Record<string, unknown>;