diff --git a/app/scripts/generate-company-pages.mjs b/app/scripts/generate-company-pages.mjs new file mode 100644 index 0000000..472a0be --- /dev/null +++ b/app/scripts/generate-company-pages.mjs @@ -0,0 +1,110 @@ +#!/usr/bin/env node +// Generates static company directory pages at dist/directory/{slug}/index.html. +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { TeknavData } from '../teknav-data.js'; +import { renderStaticFallback, replaceStaticFallback } from './static-fallback.mjs'; + +const SITE_URL = (process.env.TEKNAV_SITE_URL || process.env.VITE_SITE_URL || 'https://www.teknav.ir').replace(/\/$/, ''); +const companies = TeknavData.companies ?? []; + +if (!companies.length) { + console.log('[company-pages] no companies defined — skipping'); + process.exit(0); +} + +const template = readFileSync(resolve('dist/index.html'), 'utf8'); + +function esc(value) { + return String(value ?? '') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>'); +} + +function clean(value) { + return String(value ?? '').replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim(); +} + +function truncate(value, max = 155) { + const text = clean(value); + return text.length > max ? `${text.slice(0, max - 1).trim()}…` : text; +} + +function buildPage(company) { + const canonical = `${SITE_URL}/directory/${company.slug}`; + const title = esc(`${company.name} | دایرکتوری شرکت‌های فناوری | تکناو`); + const description = esc(truncate(company.metaDescription || company.description, 160)); + const keywords = esc([company.name, 'شرکت فناوری', ...(company.techStack || [])].slice(0, 10).join(', ')); + + const jsonLd = JSON.stringify({ + '@context': 'https://schema.org', + '@graph': [ + { + '@type': ['Organization', 'NewsMediaOrganization'], + '@id': `${SITE_URL}/#organization`, + name: 'تکناو', + url: SITE_URL, + logo: { '@type': 'ImageObject', url: `${SITE_URL}/favicon.png` }, + }, + { + '@type': 'Organization', + name: company.name, + description: clean(company.description), + url: company.websiteUrl || canonical, + sameAs: company.websiteUrl ? [company.websiteUrl] : [], + }, + { + '@type': 'BreadcrumbList', + itemListElement: [ + { '@type': 'ListItem', position: 1, name: 'تکناو', item: SITE_URL }, + { '@type': 'ListItem', position: 2, name: 'دایرکتوری', item: `${SITE_URL}/directory` }, + { '@type': 'ListItem', position: 3, name: company.name }, + ], + }, + ], + }); + + let html = template; + html = html.replace(//, ''); + html = html.replace(/[^<]*<\/title>/, `<title>${title}`); + html = html.replace(/(/, ``); + html = html.replace(/`); + html = replaceStaticFallback(html, renderStaticFallback({ + eyebrow: 'دایرکتوری شرکت‌های فناوری', + title: clean(company.name), + description: clean(company.description || company.metaDescription), + facts: [ + company.category ? { label: 'دسته', value: company.category } : null, + company.location ? { label: 'موقعیت', value: company.location } : null, + company.techStack?.length ? { label: 'فناوری‌ها', value: company.techStack.join('، ') } : null, + company.websiteUrl ? { label: 'وب‌سایت', value: company.websiteUrl } : null, + ].filter(Boolean), + links: [ + company.websiteUrl ? { href: company.websiteUrl, label: `وب‌سایت ${company.name}`, text: 'لینک رسمی شرکت' } : null, + { href: '/directory', label: 'دایرکتوری شرکت‌ها', text: 'فهرست شرکت‌های فناوری' }, + { href: '/articles', label: 'مقاله‌های فناوری', text: 'تحلیل‌های تکناو' }, + ].filter(Boolean), + })); + + return html; +} + +let count = 0; +for (const company of companies) { + const dir = resolve(`dist/directory/${company.slug}`); + mkdirSync(dir, { recursive: true }); + writeFileSync(`${dir}/index.html`, buildPage(company), 'utf8'); + count++; +} + +console.log(`[company-pages] generated ${count} company pages in dist/directory/`);