import type { AstroIntegration } from 'astro'; import { existsSync, readFileSync } from 'node:fs'; import { join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; // Video.js v8 URLs to check for migration status const V8_URLS = [ 'https://videojs.org/city', 'https://videojs.org/fantasy', 'https://videojs.org/forest', 'https://videojs.org/sea', 'https://videojs.org/getting-started/', 'https://videojs.org/blog/videojs-8-and-vhs-3/', 'https://videojs.org/blog/announcing-the-new-videojs-com/', 'https://videojs.org/blog/inband-captions-support-with-vhs/', 'https://videojs.org/blog/video-js-7-4/', 'https://videojs.org/blog/bugpost-disconnects-and-reconnects/', 'https://videojs.org/blog/introducing-video-js-http-streaming-vhs/', 'https://videojs.org/blog/video-js-7-3-responsive-layout-fill-mode-createLogger/', 'https://videojs.org/blog/video-js-7-1-and-6-11-autoplay-and-fullscreen-changes/', 'https://videojs.org/blog/video-js-7-is-here/', 'https://videojs.org/blog/autoplay-best-practices-with-video-js/', 'https://videojs.org/blog/videojs-contrib-ads-6/', 'https://videojs.org/blog/video-js-7-roadmap/', 'https://videojs.org/blog/videojs-vr-now-under-the-video-js-org/', 'https://videojs.org/blog/video-js-6-7-1-released/', 'https://videojs.org/blog/video-js-6-5-0-release/', 'https://videojs.org/blog/video-js-6-4-0-release/', 'https://videojs.org/blog/video-js-6-0-release/', 'https://videojs.org/blog/feature-spotlight-middleware/', 'https://videojs.org/blog/video-js-removes-flash-from-core-player/', 'https://videojs.org/blog/feature-spotlight-accessibility/', 'https://videojs.org/blog/feature-spotlight-advanced-plugins/', 'https://videojs.org/blog/video-js-6-0-0-rc-0-the-first-rc/', 'https://videojs.org/blog/introducing-thumbcoil/', 'https://videojs.org/blog/video-js-5-12-0-and-5-11-5-releases/', 'https://videojs.org/blog/the-end-of-html-first/', 'https://videojs.org/blog/video-js-5-11-0-prelease/', 'https://videojs.org/blog/video-js-5-s-fluid-mode-and-playlist-picker/', 'https://videojs.org/blog/video-js-5-the-only-thing-thatโ€™s-changed-is-everything-except-for-like-3-things-that-didn-t-including-the-name/', 'https://videojs.org/blog/it-s-here-5-0-release-candidates/', 'https://videojs.org/blog/video-js-4-12-the-last-of-the-4-minors/', 'https://videojs.org/blog/video-js-4-9-now-can-join-the-party/', 'https://videojs.org/blog/video-js-4-8-0-released-prost/', 'https://videojs.org/blog/video-js-v4-7-0-built-mostly-by-new-contributors-also-google-chooses-video-js/', 'https://videojs.org/blog/dash-everywhere-ish-hack-project/', 'https://videojs.org/blog/video-js-version-4-6-0-released-it-s-been-a-productive-month/', 'https://videojs.org/blog/video-js-version-4-5-0-released-nothing-to-see-here-move-along/', 'https://videojs.org/blog/video-js-version-4-4-0-released-now-supporting-requirejs-and-browserify/', 'https://videojs.org/blog/video-js-version-4-3-0-released-w-shiny-new-api-docs/', 'https://videojs.org/blog/the-guardian-uses-video-js-in-feature-article/', 'https://videojs.org/blog/4-2-2-patch-release/', 'https://videojs.org/blog/running-video-js-unit-tests-in-real-browsers-with-karma/', 'https://videojs.org/blog/unauthorized-modification-of-video-js-cdn-files/', 'https://videojs.org/blog/video-js-4-2-0-released-rtmp-css-designer-and-stability/', 'https://videojs.org/blog/hiding-and-showing-video-player-controls/', 'https://videojs.org/blog/new-player-skin-designer-for-video-js/', 'https://videojs.org/blog/video-js-4-1-0-released/', 'https://videojs.org/blog/video-js-4-0-now-available/', 'https://videojs.org/blog/repo-moved/', 'https://videojs.org/blog/site-and-support-updates/', 'https://videojs.org/blog/brightcove-acquires-zencoder/', 'https://videojs.org/blog/version-3-2-update/', 'https://videojs.org/blog/version-3-1-update/', 'https://videojs.org/blog/video-js-version-3-0/', 'https://videojs.org/blog/lynda-com-html5-video-tutorial-released/', 'https://videojs.org/blog/how-are-you-using-video-js/', 'https://videojs.org/blog/new-mpeg-la-webm-vp8-patent-pool/', 'https://videojs.org/blog/apple-adds-airplay-to-mobile-safari/', 'https://videojs.org/blog/google-is-dropping-h-264-from-chrome/', 'https://videojs.org/blog/over-50-of-web-users-now-support-html5-video/', 'https://videojs.org/blog/html5-video-google-maps-mashup/', 'https://videojs.org/blog/2-0-2-release-subtitle-optimization-safari-on-leopard-fs-fix/', 'https://videojs.org/blog/2-0-0-release-behaviors-fallback-apis-and-more/', 'https://videojs.org/blog/make-sites-serve-you-html5-video-in-safari/', 'https://videojs.org/blog/1-1-5-release-subtitles-using-track-android-fix-more/', 'https://videojs.org/blog/version-1-1-4-release-css-loading-spinner-more/', 'https://videojs.org/blog/version-1-1-3-release/', 'https://videojs.org/blog/facebook-adds-html5-video/', 'https://videojs.org/blog/ipad-iphone-video-poster-fix-bonus-javascript-placement-fix/', 'https://videojs.org/blog/videojs-is-ie9-compatible/', 'https://videojs.org/blog/new-videojs-site-now-with-more-html5/', 'https://videojs.org/blog', 'https://videojs.org/blog/2', 'https://videojs.org/blog/3', 'https://videojs.org/blog/4', 'https://videojs.org/blog/5', 'https://videojs.org/blog/6', 'https://videojs.org/blog/7', 'https://videojs.org/tags/news', 'https://videojs.org/tags/vhs', 'https://videojs.org/tags/videojs-http-streaming', 'https://videojs.org/tags/playback', 'https://videojs.org/tags/captions', 'https://videojs.org/tags/cea-608', 'https://videojs.org/tags/608', 'https://videojs.org/tags/a11y', 'https://videojs.org/tags/responsive', 'https://videojs.org/tags/languages', 'https://videojs.org/tags/live-ui', 'https://videojs.org/tags/focus-visible', 'https://videojs.org/tags/http-streaming', 'https://videojs.org/tags/bugs', 'https://videojs.org/tags/log', 'https://videojs.org/tags/fill', 'https://videojs.org/tags/fluid', 'https://videojs.org/tags/playerresize', 'https://videojs.org/tags/autoplay', 'https://videojs.org/tags/fullscreen', 'https://videojs.org/tags/IE8', 'https://videojs.org/tags/IE', 'https://videojs.org/tags/google%20analytics', 'https://videojs.org/tags/VHS', 'https://videojs.org/tags/HLS', 'https://videojs.org/tags/DASH', 'https://videojs.org/tags/webpack', 'https://videojs.org/tags/rollup', 'https://videojs.org/tags/vr', 'https://videojs.org/tags/360', 'https://videojs.org/tags/plugins', 'https://videojs.org/tags/middleware', 'https://videojs.org/tags/video.js%206.0', 'https://videojs.org/tags/accessibility', 'https://videojs.org/tags/flash', 'https://videojs.org/tags/html5', 'https://videojs.org/tags/thumbcoil', 'https://videojs.org/tags/mse', 'https://videojs.org/tags/hls', 'https://videojs.org/tags/releases', 'https://videojs.org/tags/version', 'https://videojs.org/tags/babel', 'https://videojs.org/tags/html5%20video', 'https://videojs.org/tags/videojs%205', 'https://videojs.org/tags/release', 'https://videojs.org/tags/videojs', 'https://videojs.org/tags/gallery', 'https://videojs.org/tags/code', 'https://videojs.org/tags/stats', 'https://videojs.org/guides/angular/', 'https://videojs.org/guides/audio-tracks/', 'https://videojs.org/guides/components/', 'https://videojs.org/guides/debugging/', 'https://videojs.org/guides/embeds/', 'https://videojs.org/guides/event-target/', 'https://videojs.org/guides/faqs/', 'https://videojs.org/guides/hooks/', 'https://videojs.org/guides/languages/', 'https://videojs.org/guides/layout/', 'https://videojs.org/guides/live/', 'https://videojs.org/guides/middleware/', 'https://videojs.org/guides/modal-dialog/', 'https://videojs.org/guides/picture-in-picture/', 'https://videojs.org/guides/tech/', 'https://videojs.org/guides/player-workflows/', 'https://videojs.org/guides/react/', 'https://videojs.org/guides/skins/', 'https://videojs.org/guides/spatial-navigation/', 'https://videojs.org/guides/text-tracks/', 'https://videojs.org/guides/troubleshooting/', 'https://videojs.org/guides/video-tracks/', 'https://videojs.org/guides/videojs-7-to-8/', 'https://videojs.org/guides/options/', 'https://videojs.org/guides/plugins/', 'https://videojs.org/guides/setup/', 'https://videojs.org/guides/vue/', 'https://videojs.org/guides/webpack/', 'https://videojs.org/guides', 'https://videojs.org/advanced/', 'https://videojs.org/html5-video-support/', 'https://videojs.org/', 'https://videojs.org/plugins/', 'https://videojs.org/privacy/', ]; interface V8UrlStatus { url: string; pathname: string; status: 'migrated' | 'redirected' | 'needs migration'; } interface VercelRedirect { source: string; destination: string; statusCode?: number; permanent?: boolean; } /** * Check if a pathname matches a Vercel redirect source pattern * Supports exact matches and :path(.*) wildcard patterns */ function matchesVercelPattern(pathname: string, pattern: string): boolean { // Remove trailing slash for consistent matching const normalizedPath = pathname.replace(/\/$/, ''); const normalizedPattern = pattern.replace(/\/$/, ''); // Exact match if (normalizedPattern === normalizedPath) { return true; } // Handle :path(.*) wildcard pattern // Convert /tags/:path(.*) to a regex that matches /tags/anything if (normalizedPattern.includes(':path(')) { const regexPattern = normalizedPattern .replace(/:[^/]+\(\.\*\)/g, '.*') // :path(.*) -> .* .replace(/\//g, '\\/'); // Escape slashes const regex = new RegExp(`^${regexPattern}$`); return regex.test(normalizedPath); } return false; } export default function checkV8Urls(): AstroIntegration { return { name: 'check-v8-urls', hooks: { 'astro:build:done': async ({ dir, logger }) => { // Convert URL to file path const buildDir = fileURLToPath(dir); // Check for Vercel config files // buildDir is dist/client/, so go up two levels to site/ const siteDir = resolve(buildDir, '..', '..'); const vercelConfigPath = resolve(siteDir, '.vercel', 'output', 'config.json'); const vercelJsonPath = resolve(siteDir, 'vercel.json'); // Read vercel.json redirects if it exists let vercelJsonRedirects: VercelRedirect[] = []; if (existsSync(vercelJsonPath)) { try { const vercelJson = JSON.parse(readFileSync(vercelJsonPath, 'utf-8')); vercelJsonRedirects = vercelJson.redirects || []; } catch { // Ignore JSON parse errors } } // Check each URL const results: V8UrlStatus[] = []; for (const url of V8_URLS) { // Extract pathname from URL const urlObj = new URL(url); const pathname = decodeURIComponent(urlObj.pathname); // Check for HTML files const htmlPath1 = join(buildDir, `${pathname}.html`); const htmlPath2 = join(buildDir, pathname, 'index.html'); let status: V8UrlStatus['status'] = 'needs migration'; if (existsSync(htmlPath1) || existsSync(htmlPath2)) { status = 'migrated'; } else { // Check for redirects in vercel.json first const hasVercelJsonRedirect = vercelJsonRedirects.some((redirect) => { // Check if redirect has a redirect status code (or permanent flag) const isRedirect = redirect.statusCode ? redirect.statusCode >= 301 && redirect.statusCode <= 308 : redirect.permanent !== undefined; if (isRedirect) { return matchesVercelPattern(pathname, redirect.source); } return false; }); if (hasVercelJsonRedirect) { status = 'redirected'; } else { // Fall back to checking .vercel/output/config.json (adapter-generated) if (existsSync(vercelConfigPath)) { try { const vercelConfig = JSON.parse(readFileSync(vercelConfigPath, 'utf-8')); const routes = vercelConfig.routes || []; // Check if this pathname has a redirect (status 301/302/307/308) const hasRedirect = routes.some((route: any) => { if (route.status && route.status >= 301 && route.status <= 308) { // Route has a redirect status code const src = route.src; // Match against pathname (src is a regex pattern) // Simple check: convert pathname to regex pattern const pathPattern = `^${pathname.replace(/\/$/, '')}$`; const pathPatternWithSlash = `^${pathname}$`; return src === pathPattern || src === pathPatternWithSlash; } return false; }); if (hasRedirect) { status = 'redirected'; } } catch { // Ignore JSON parse errors } } } } results.push({ url, pathname, status, }); } // Calculate statistics const total = results.length; const migrated = results.filter(r => r.status === 'migrated').length; const redirected = results.filter(r => r.status === 'redirected').length; const needsMigration = results.filter(r => r.status === 'needs migration').length; const migratedPercent = ((migrated / total) * 100).toFixed(1); const redirectedPercent = ((redirected / total) * 100).toFixed(1); const needsMigrationPercent = ((needsMigration / total) * 100).toFixed(1); // Log results logger.info(''); logger.info('V8 URL Migration Status'); logger.info('========================'); logger.info(`โœ… Migrated: ${migrated}/${total} (${migratedPercent}%)`); logger.info(`๐Ÿ”€ Redirected: ${redirected}/${total} (${redirectedPercent}%)`); logger.info(`โš ๏ธ Needs migration: ${needsMigration}/${total} (${needsMigrationPercent}%)`); logger.info(''); // Log table logger.info('Status | URL'); logger.info('------ | ---'); const statusEmoji: Record = { migrated: 'โœ…', redirected: '๐Ÿ”€', 'needs migration': 'โš ๏ธ ', }; for (const result of results) { logger.info(`${statusEmoji[result.status]} | ${result.pathname}`); } logger.info(''); }, }, }; }