Skip to content

Commit 50a6854

Browse files
committed
feat(devices): publish sourced 2026 Mac catalog
1 parent ee9b633 commit 50a6854

17 files changed

Lines changed: 897 additions & 277 deletions

‎components/search-stork.vue‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171

7272
<!-- hasStartedAnyQuery: {{ hasStartedAnyQuery }} -->
7373

74-
<template v-if="chunkedListings.length === 0">
74+
<template v-if="chunkedListings.length === 0 && hasStartedAnyQuery">
7575
<div
7676
class="text-center py-4"
7777
>
@@ -111,6 +111,11 @@
111111
/>
112112
</div>
113113
</template>
114+
<template v-else-if="chunkedListings.length === 0">
115+
<div class="text-center text-sm text-gray-400 py-8">
116+
Start typing an app name or choose a status filter to search the compatibility database.
117+
</div>
118+
</template>
114119

115120
<ul
116121
v-for="(listingsChunk, chunkIndex) in chunkedListings"

‎data/apple-silicon-devices.json‎

Lines changed: 449 additions & 0 deletions
Large diffs are not rendered by default.

‎helpers/api/sitemap/devices.js‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
function escapeXml ( value ) {
2+
return value
3+
.replaceAll( '&', '&amp;' )
4+
.replaceAll( '<', '&lt;' )
5+
.replaceAll( '>', '&gt;' )
6+
.replaceAll( '"', '&quot;' )
7+
.replaceAll( '\'', '&apos;' )
8+
}
9+
10+
const deviceUrlPattern = /<url>(?:(?!<\/url>)[\s\S])*?<loc>[^<]*\/device\/[^<]*<\/loc>(?:(?!<\/url>)[\s\S])*?<\/url>/gu
11+
12+
export function replaceDeviceUrlsInSitemap ( sitemapXml, deviceUrls ) {
13+
if ( !sitemapXml.includes( '</urlset>' ) ) {
14+
throw new Error( 'Cannot update device URLs in a sitemap without a urlset root' )
15+
}
16+
17+
const withoutExistingDevices = sitemapXml.replace( deviceUrlPattern, '' )
18+
const deviceEntries = deviceUrls
19+
.map( url => `<url><loc>${ escapeXml( url ) }</loc></url>` )
20+
.join( '' )
21+
22+
return withoutExistingDevices.replace( '</urlset>', `${ deviceEntries }</urlset>` )
23+
}

‎helpers/build-device-list.js‎

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { makeSlug } from './slug.js'
2-
import { getJson } from './http.js'
1+
import { getListedDeviceListings } from './device-catalog.js'
32

43
export function getDeviceEndpoint ( slug ) {
54
return `/device/${ slug }`
@@ -8,22 +7,8 @@ export function getDeviceEndpoint ( slug ) {
87

98

109
export default async function () {
11-
12-
const devicesJsonUrl = `${process.env.VFUNCTIONS_URL}/api/devices`
13-
14-
const rawDeviceList = await getJson( devicesJsonUrl )
15-
.catch(function (error) {
16-
// handle error
17-
console.warn('Error fetching device list', error)
18-
})
19-
20-
return rawDeviceList.filter( device => ( device.enabled !== 'no' ) ).map( device => {
21-
const slug = makeSlug( device.name )
22-
23-
return {
24-
...device,
25-
slug,
26-
endpoint: getDeviceEndpoint( slug ),
27-
}
28-
})
10+
return getListedDeviceListings().map( device => ({
11+
...device,
12+
endpoint: getDeviceEndpoint( device.slug ),
13+
}))
2914
}

‎helpers/config-node.js‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ function mapMetaTag ( tag ) {
238238

239239
function mapLinkTag ( tag ) {
240240
return [
241-
`type-${tag.type}`,
241+
`link-${ tag.rel || 'unknown' }-${ tag.type || 'none' }-${ tag.href || 'none' }`,
242242
makeTag(tag, 'link')
243243
]
244244
}
@@ -347,6 +347,10 @@ export class PageHead {
347347

348348
const linkTags = {
349349
...this.defaultLinkTags,
350+
...Object.fromEntries( [{
351+
rel: 'canonical',
352+
href: this.pageUrlString
353+
}].map( mapLinkTag ) ),
350354
...Object.fromEntries( this.link.map( mapLinkTag ) )
351355
}
352356

‎helpers/device-catalog.js‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import deviceCatalogText from '~/data/apple-silicon-devices.json?raw'
2+
3+
export const deviceCatalog = JSON.parse( deviceCatalogText )
4+
5+
export const deviceListings = deviceCatalog.devices
6+
7+
export function getListedDeviceListings () {
8+
return deviceListings.filter( device => device.listed )
9+
}
10+
11+
export function getDeviceListingBySlug ( slug ) {
12+
return deviceListings.find( device => device.slug === slug ) || null
13+
}

‎helpers/device-list-fallbacks.js‎

Lines changed: 0 additions & 136 deletions
This file was deleted.

‎helpers/site-listings.js‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import videoListingsText from '~/static/api/youtube-video-listings.json?raw'
22

3-
import { deviceListingFallbacks } from './device-list-fallbacks.js'
3+
import { getDeviceListingBySlug } from './device-catalog.js'
44

55
const parsedVideoListings = JSON.parse( videoListingsText )
66

7-
export function getDeviceListingBySlug ( slug ) {
8-
return deviceListingFallbacks.find( device => device.slug === slug ) || null
9-
}
7+
export { getDeviceListingBySlug }
108

119
export async function getVideoListingBySlug ( slug ) {
1210
return parsedVideoListings[slug] || null

‎scripts/download-sitemaps.js‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
sitemapIndexFileName,
77
} from '~/helpers/constants.js'
88

9+
import { getListedDeviceListings } from '~/helpers/device-catalog.js'
10+
import { replaceDeviceUrlsInSitemap } from '~/helpers/api/sitemap/devices.js'
911
import { parseSitemapXml } from '~/helpers/api/sitemap/parse.js'
1012
import { getText } from '~/helpers/http.js'
1113

@@ -25,6 +27,8 @@ import { getText } from '~/helpers/http.js'
2527
const urlEntries = parseSitemapXml( sitemapIndexXML )
2628

2729

30+
const downloadedSitemapPaths = []
31+
2832
// Fetch each sitemap
2933
for ( const entry of urlEntries ) {
3034

@@ -48,6 +52,21 @@ import { getText } from '~/helpers/http.js'
4852

4953
// Save file
5054
await fs.writeFile( sitemapIndexFilePath, sitemapXML )
55+
downloadedSitemapPaths.push( sitemapIndexFilePath )
56+
}
57+
58+
const deviceUrls = getListedDeviceListings().map( device => {
59+
return new URL( device.endpoint, process.env.PUBLIC_URL ).href
60+
} )
61+
62+
for ( const [ index, sitemapPath ] of downloadedSitemapPaths.entries() ) {
63+
const sitemapXml = await fs.readFile( sitemapPath, 'utf8' )
64+
const updatedSitemapXml = replaceDeviceUrlsInSitemap(
65+
sitemapXml,
66+
index === 0 ? deviceUrls : []
67+
)
68+
69+
await fs.writeFile( sitemapPath, updatedSitemapXml )
5170
}
5271

5372

‎scripts/health‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const routeGroups = {
1414
'/app/spotify',
1515
'/formula/bash',
1616
'/kind/developer-tools',
17-
'/device/2023-m3-imac',
17+
'/device/2026-macbook-air-13-inch-m5',
1818
'/app/expressvpn/benchmarks'
1919
],
2020
video: [

0 commit comments

Comments
 (0)