UNPKG

astro-photostream

Version:

An Astro integration for creating photo galleries with AI metadata generation and geolocation features

231 lines (204 loc) 7.92 kB
import { defineIntegration, createResolver, addVirtualImports, addVitePlugin, } from 'astro-integration-kit'; import type { AstroIntegrationLogger } from 'astro'; import { integrationOptionsSchema, type IntegrationOptions } from './types.js'; /** * Astro Photo Stream Integration * * Creates sophisticated photo galleries and streams with AI-powered metadata generation, * geolocation features, and responsive design. */ export default defineIntegration({ name: 'astro-photostream', optionsSchema: integrationOptionsSchema, setup({ options }: { options: IntegrationOptions }) { const { resolve } = createResolver(import.meta.url); // Create a resolver for the source files (routes/components) relative to package root const resolveSource = (path: string) => resolve(`../src/${path}`); return { hooks: { 'astro:config:setup': async (params: any) => { const { updateConfig, injectRoute, command, logger, config } = params; if (!options.enabled) { logger.info('Astro Photo Stream integration is disabled'); return; } logger.info('Setting up Astro Photo Stream integration...'); // Resolve layout wrapper path relative to project root const resolvedLayoutWrapper = options.layout?.wrapper ? new URL(options.layout.wrapper, config.root).pathname : null; // Debug logging if (options.layout?.enabled) { logger.info( `Layout wrapper enabled: ${options.layout.wrapper} -> ${resolvedLayoutWrapper}` ); } // Add virtual imports for configuration addVirtualImports(params, { name: 'astro-photostream', imports: { 'virtual:astro-photostream/config': ` export const config = ${JSON.stringify(options, null, 2)}; `, 'virtual:astro-photostream/utils': ` export { generatePhotoMetadata } from '${resolve('./utils/metadata.js')}'; export { processPhotoCollection } from '${resolve('./utils/collection.js')}'; export { createPhotoRoutes } from '${resolve('./utils/routing.js')}'; `, 'virtual:astro-photostream/layout': ` export const layoutConfig = ${JSON.stringify(options.layout || {}, null, 2)}; export const shouldUseLayout = ${options.layout?.enabled || false}; export const layoutWrapper = ${resolvedLayoutWrapper ? `"${resolvedLayoutWrapper}"` : 'null'}; export const layoutProps = ${JSON.stringify(options.layout?.props || {}, null, 2)}; `, 'virtual:astro-photostream/layout-wrapper': resolvedLayoutWrapper ? ` import LayoutComponent from "${resolvedLayoutWrapper}"; export { LayoutComponent }; ` : ` export const LayoutComponent = null; `, }, }); // Inject photo gallery routes if (command === 'dev' || command === 'build') { // Determine route template suffix based on layout configuration const routeSuffix = options.layout?.enabled ? '.content.astro' : '.astro'; // Individual photo pages (inject first for priority) injectRoute({ pattern: '/photos/[slug]', entrypoint: resolveSource(`routes/photos/[slug]${routeSuffix}`), }); // Main photo gallery with pagination (handles /photos and /photos/2) injectRoute({ pattern: '/photos/[...page]', entrypoint: resolveSource( `routes/photos/[...page]${routeSuffix}` ), }); // Tag-based photo filtering with pagination if (options.gallery.enableTags) { injectRoute({ pattern: '/photos/tags/[tag]/[...page]', entrypoint: resolveSource( `routes/photos/tags/[tag]/[...page]${routeSuffix}` ), }); } // OpenGraph image generation endpoints if (options.seo.generateOpenGraph) { // Individual photo OG images (text-only, works) injectRoute({ pattern: '/api/og/photo/[slug].png', entrypoint: resolveSource('routes/og-image.ts'), }); // Gallery OG images with photo grids injectRoute({ pattern: '/og-image/photos.png', entrypoint: resolveSource('routes/og-image/photos.png.ts'), }); injectRoute({ pattern: '/og-image/photos/[...page].png', entrypoint: resolveSource( 'routes/og-image/photos/[...page].png.ts' ), }); } } // Update Astro configuration updateConfig({ vite: { define: { 'import.meta.env.ASTRO_PHOTO_STREAM_CONFIG': JSON.stringify(options), }, optimizeDeps: { include: ['exifr', 'sharp'], exclude: ['@resvg/resvg-js'], }, }, }); // Add Vite plugin for photo processing during build addVitePlugin(params, { plugin: { name: 'astro-photostream-processor', configResolved(config: Record<string, any>) { if (config.command === 'build') { logger.info('Photo processing will run during build'); } }, buildStart() { if (options.ai.enabled && !options.ai.apiKey) { logger.warn( 'AI metadata generation is enabled but no API key provided' ); } }, }, }); logger.info('Astro Photo Stream integration setup complete'); }, 'astro:config:done': ({ logger, }: { logger: AstroIntegrationLogger; }) => { if (!options.enabled) return; // Validate configuration if (options.gallery.itemsPerPage > 100) { logger.warn('Large itemsPerPage value may impact performance'); } logger.info(`Photo directory: ${options.photos.directory}`); logger.info( `Gallery configuration: ${options.gallery.itemsPerPage} items per page` ); if (options.ai.enabled) { logger.info( `AI metadata generation enabled using ${options.ai.provider}` ); } if (options.geolocation.enabled) { logger.info( `Geolocation enabled with ${options.geolocation.privacy.enabled ? 'privacy protection' : 'full precision'}` ); } }, 'astro:build:start': ({ logger, }: { logger: AstroIntegrationLogger; }) => { if (!options.enabled) return; logger.info('Starting photo processing for build...'); // Photo processing logic will be implemented in Phase 2 }, 'astro:build:done': ({ logger, }: { logger: AstroIntegrationLogger; }) => { if (!options.enabled) return; logger.info('Photo stream build complete!'); logger.info('Your photo gallery is ready to deploy'); }, }, }; }, }); // Export types for users export type { IntegrationOptions, PhotoMetadata, PhotoCardProps, PhotoGridProps, PhotoStreamProps, MultiMarkerMapProps, } from './types.js';