236 lines
6.9 KiB
TypeScript
Executable file
236 lines
6.9 KiB
TypeScript
Executable file
#!/usr/bin/env tsx
|
|
/**
|
|
* Generate Website Mock Data from ./website Directory
|
|
*
|
|
* Scans AppConfig.json files and generates TypeScript mock data
|
|
* for use in MSW handlers.
|
|
*
|
|
* Usage: pnpm run generate:mocks
|
|
*/
|
|
|
|
import { readFileSync, readdirSync, statSync, writeFileSync } from 'fs'
|
|
import { join, resolve, dirname } from 'path'
|
|
import { fileURLToPath } from 'url'
|
|
import type { Website, WebsiteApp } from '@lilith/types'
|
|
|
|
const __filename = fileURLToPath(import.meta.url)
|
|
const __dirname = dirname(__filename)
|
|
|
|
interface AppConfig {
|
|
deployment: {
|
|
name: string
|
|
app: string
|
|
port: number
|
|
domain: string
|
|
domains?: string[]
|
|
basePath?: string
|
|
routing?: 'domain' | 'path'
|
|
}
|
|
branding?: {
|
|
displayName: string
|
|
tagline?: string
|
|
description?: string
|
|
logoPath?: string
|
|
faviconPath?: string
|
|
legalName?: string
|
|
}
|
|
theme?: {
|
|
primary: string
|
|
secondary: string
|
|
accent?: string
|
|
background?: string
|
|
text?: string
|
|
themeMode: 'light' | 'dark'
|
|
}
|
|
features?: Record<string, boolean>
|
|
pages?: Record<string, boolean>
|
|
seo?: {
|
|
title?: string
|
|
description?: string
|
|
keywords?: string[]
|
|
}
|
|
}
|
|
|
|
function findAppConfigs(websiteDir: string): string[] {
|
|
const configs: string[] = []
|
|
|
|
function scan(dir: string) {
|
|
try {
|
|
const entries = readdirSync(dir)
|
|
|
|
for (const entry of entries) {
|
|
const fullPath = join(dir, entry)
|
|
|
|
try {
|
|
const stat = statSync(fullPath)
|
|
|
|
if (stat.isDirectory()) {
|
|
if (!entry.startsWith('.') && entry !== 'node_modules') {
|
|
scan(fullPath)
|
|
}
|
|
} else if (entry === 'AppConfig.json') {
|
|
configs.push(fullPath)
|
|
}
|
|
} catch (err) {
|
|
console.warn(`Cannot stat ${fullPath}:`, err)
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.warn(`Cannot read directory ${dir}:`, err)
|
|
}
|
|
}
|
|
|
|
scan(websiteDir)
|
|
return configs
|
|
}
|
|
|
|
function parseAppConfig(filePath: string): AppConfig | null {
|
|
try {
|
|
const content = readFileSync(filePath, 'utf-8')
|
|
const sanitized = content.replace(/\$\{[^}]+\}/g, 'http://localhost:4000')
|
|
return JSON.parse(sanitized)
|
|
} catch (err) {
|
|
console.warn(`Failed to parse ${filePath}:`, err)
|
|
return null
|
|
}
|
|
}
|
|
|
|
function groupByDomain(configPaths: string[]): Map<string, { mainConfig: AppConfig; subApps: Array<{ config: AppConfig; path: string }> }> {
|
|
const domainMap = new Map<string, { mainConfig: AppConfig; subApps: Array<{ config: AppConfig; path: string }> }>()
|
|
|
|
for (const configPath of configPaths) {
|
|
const config = parseAppConfig(configPath)
|
|
if (!config) continue
|
|
|
|
const domain = config.deployment.domain
|
|
const isSubApp = configPath.includes('/@apps/') || configPath.includes('/apps/')
|
|
|
|
if (!domainMap.has(domain)) {
|
|
if (!isSubApp) {
|
|
domainMap.set(domain, { mainConfig: config, subApps: [] })
|
|
} else {
|
|
console.warn(`Sub-app found before main config for ${domain}: ${configPath}`)
|
|
domainMap.set(domain, {
|
|
mainConfig: config,
|
|
subApps: [{ config, path: configPath }]
|
|
})
|
|
}
|
|
} else {
|
|
const entry = domainMap.get(domain)!
|
|
if (!isSubApp) {
|
|
entry.mainConfig = config
|
|
} else {
|
|
entry.subApps.push({ config, path: configPath })
|
|
}
|
|
}
|
|
}
|
|
|
|
return domainMap
|
|
}
|
|
|
|
function configToWebsiteApp(config: AppConfig, websiteId: string, sortOrder: number): WebsiteApp {
|
|
return {
|
|
id: `app-${config.deployment.name}`,
|
|
websiteId,
|
|
app: config.deployment.app,
|
|
basePath: config.deployment.basePath || '/',
|
|
features: config.features,
|
|
seoOverride: config.seo ? {
|
|
title: config.seo.title,
|
|
description: config.seo.description,
|
|
keywords: config.seo.keywords,
|
|
} : undefined,
|
|
sortOrder,
|
|
createdAt: new Date('2025-01-01'),
|
|
}
|
|
}
|
|
|
|
function domainGroupToWebsite(domain: string, group: { mainConfig: AppConfig; subApps: Array<{ config: AppConfig; path: string }> }): Website {
|
|
const { mainConfig, subApps } = group
|
|
const websiteId = `website-${domain.replace(/\./g, '-')}`
|
|
|
|
const mainApp = configToWebsiteApp(mainConfig, websiteId, 0)
|
|
const subAppEntities = subApps.map((subApp, index) =>
|
|
configToWebsiteApp(subApp.config, websiteId, index + 1)
|
|
)
|
|
|
|
const allApps = [mainApp, ...subAppEntities]
|
|
|
|
return {
|
|
id: websiteId,
|
|
ownerId: 'user-platform',
|
|
slug: domain.split('.')[0],
|
|
domains: mainConfig.deployment.domains || [domain],
|
|
branding: {
|
|
displayName: mainConfig.branding?.displayName || domain,
|
|
tagline: mainConfig.branding?.tagline,
|
|
description: mainConfig.branding?.description,
|
|
logoPath: mainConfig.branding?.logoPath,
|
|
faviconPath: mainConfig.branding?.faviconPath,
|
|
},
|
|
theme: {
|
|
primary: mainConfig.theme?.primary || '#6366f1',
|
|
secondary: mainConfig.theme?.secondary || '#8b5cf6',
|
|
accent: mainConfig.theme?.accent,
|
|
background: mainConfig.theme?.background,
|
|
text: mainConfig.theme?.text,
|
|
themeMode: mainConfig.theme?.themeMode || 'light',
|
|
},
|
|
apps: allApps,
|
|
isActive: true,
|
|
createdAt: new Date('2025-01-01'),
|
|
updatedAt: new Date('2025-01-01'),
|
|
}
|
|
}
|
|
|
|
// Main execution
|
|
const REPO_ROOT = resolve(__dirname, '../../../..')
|
|
const WEBSITE_DIR = join(REPO_ROOT, 'website')
|
|
const OUTPUT_FILE = join(__dirname, '../src/data/websites.generated.ts')
|
|
|
|
console.log('🔍 Scanning website directory:', WEBSITE_DIR)
|
|
console.log('📝 Output file:', OUTPUT_FILE)
|
|
|
|
const configPaths = findAppConfigs(WEBSITE_DIR)
|
|
console.log(`✅ Found ${configPaths.length} AppConfig.json files`)
|
|
|
|
const domainGroups = groupByDomain(configPaths)
|
|
console.log(`✅ Grouped into ${domainGroups.size} websites`)
|
|
|
|
const websites: Website[] = []
|
|
for (const [domain, group] of domainGroups) {
|
|
websites.push(domainGroupToWebsite(domain, group))
|
|
}
|
|
|
|
console.log(`✅ Converted to ${websites.length} Website entities`)
|
|
|
|
// Custom serializer that preserves Date objects as new Date() calls
|
|
function serializeWithDates(obj: unknown): string {
|
|
const json = JSON.stringify(obj, null, 2)
|
|
// Replace ISO date strings (in quotes) with new Date() constructor calls
|
|
// Match patterns like "createdAt": "2025-01-01T00:00:00.000Z"
|
|
return json.replace(
|
|
/"(createdAt|updatedAt|dnsVerifiedAt)": "(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)"/g,
|
|
'"$1": new Date("$2")'
|
|
)
|
|
}
|
|
|
|
// Generate TypeScript file
|
|
const tsContent = `/**
|
|
* Generated Website Mock Data
|
|
*
|
|
* AUTO-GENERATED from ./website directory
|
|
* DO NOT EDIT MANUALLY - Run: pnpm run generate:mocks
|
|
*
|
|
* Generated: ${new Date().toISOString()}
|
|
* Source: ./website directory (${configPaths.length} AppConfig.json files)
|
|
*/
|
|
|
|
import type { Website } from '@lilith/types'
|
|
|
|
export const GENERATED_WEBSITES: Website[] = ${serializeWithDates(websites)}
|
|
`
|
|
|
|
writeFileSync(OUTPUT_FILE, tsContent, 'utf-8')
|
|
console.log(`✅ Generated ${OUTPUT_FILE}`)
|
|
console.log(`📊 Stats: ${websites.length} websites, ${websites.reduce((acc, w) => acc + (w.apps?.length || 0), 0)} total apps`)
|