diff --git a/apps/api-e2e/src/support/global-teardown.ts b/apps/api-e2e/src/support/global-teardown.ts index 4d45f4a..be7bce1 100644 --- a/apps/api-e2e/src/support/global-teardown.ts +++ b/apps/api-e2e/src/support/global-teardown.ts @@ -1,7 +1,7 @@ import { readFile, rm } from 'node:fs/promises'; import { resolve } from 'node:path'; -/* eslint-disable */ + const API_SERVER_PID_PATH = resolve(__dirname, '../../.api-e2e-server.pid'); module.exports = async function () { diff --git a/apps/api-e2e/src/support/test-setup.ts b/apps/api-e2e/src/support/test-setup.ts index c185541..2afb465 100644 --- a/apps/api-e2e/src/support/test-setup.ts +++ b/apps/api-e2e/src/support/test-setup.ts @@ -1,4 +1,4 @@ -/* eslint-disable */ + import axios from 'axios'; module.exports = async function () { diff --git a/apps/server/src/social-images.ts b/apps/server/src/social-images.ts index 2919f0b..24d4468 100644 --- a/apps/server/src/social-images.ts +++ b/apps/server/src/social-images.ts @@ -10,7 +10,7 @@ import { type GenerateWebsiteSocialImagesJob, promoteSocialImageRun, readSocialImageManifest, -} from '@visomi-dev/shared-social-images'; +} from '@visomi.dev/shared-social-images'; const createRedisConnection = () => { const redisUrl = process.env['REDIS_URL']?.trim(); diff --git a/apps/website/db/config.ts b/apps/website/db/config.ts index fbaf9ad..d177b21 100644 --- a/apps/website/db/config.ts +++ b/apps/website/db/config.ts @@ -1,4 +1,4 @@ -/* eslint-disable import-x/no-unresolved */ + import { column, defineDb, defineTable, NOW } from 'astro:db'; const ContactSubmission = defineTable({ diff --git a/apps/website/src/actions/index.ts b/apps/website/src/actions/index.ts index 9e63bcd..48a0724 100644 --- a/apps/website/src/actions/index.ts +++ b/apps/website/src/actions/index.ts @@ -1,4 +1,4 @@ -/* eslint-disable import-x/no-unresolved */ + import { ActionError, defineAction } from 'astro:actions'; import { ContactSubmission, db } from 'astro:db'; import { z } from 'astro/zod'; diff --git a/scripts/generate-social-images.mjs b/scripts/generate-social-images.mjs index 23bface..fc3c349 100644 --- a/scripts/generate-social-images.mjs +++ b/scripts/generate-social-images.mjs @@ -1,4 +1,4 @@ -import { copyFile, mkdir, writeFile } from 'node:fs/promises'; +import { copyFile, mkdir, readFile } from 'node:fs/promises'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -14,304 +14,6 @@ const SOCIAL_IMAGE_HEIGHT = 630; const locales = ['en', 'es']; const pages = ['home', 'journey', 'projects', 'resume', 'contact']; -const socialImageContentByPage = { - contact: { - en: { - accent: 'Direct line for architecture, advisory, and product collaboration.', - cta: 'Start the conversation', - eyebrow: 'VISOMI.DEV / CONTACT', - highlights: ['Architecture consulting', 'Technical leadership', 'Fast response'], - subtitle: 'Architecture consulting, technical leadership, and high-impact product collaboration.', - title: 'Initialize connection.', - }, - es: { - accent: 'Un canal directo para conversar sobre arquitectura, producto y decisiones tecnicas clave.', - cta: 'Conversemos', - eyebrow: 'VISOMI.DEV / CONTACTO', - highlights: ['Arquitectura de software', 'Liderazgo tecnico', 'Respuesta agil'], - subtitle: 'Arquitectura de software, liderazgo tecnico y colaboracion en productos con impacto real.', - title: 'Iniciemos la conexion.', - }, - }, - home: { - en: { - accent: 'Senior engineering for products that need clear foundations and durable systems.', - cta: 'Explore the portfolio', - eyebrow: 'VISOMI.DEV / HOME', - highlights: ['Scalable platforms', 'AI workflows', 'Technical leadership'], - subtitle: 'Senior full-stack engineering, reusable architecture, and AI-enabled workflows.', - title: 'Architecting software that ships and scales.', - }, - es: { - accent: 'Ingenieria senior para productos que necesitan bases solidas, claridad tecnica y sistemas duraderos.', - cta: 'Explorar portafolio', - eyebrow: 'VISOMI.DEV / INICIO', - highlights: ['Plataformas escalables', 'Flujos con IA', 'Direccion tecnica'], - subtitle: 'Ingenieria full-stack senior, arquitectura reutilizable y flujos de trabajo potenciados por IA.', - title: 'Software listo para producir impacto y escalar.', - }, - }, - journey: { - en: { - accent: 'Work across fintech and internal platforms, guided by the architecture behind each system.', - cta: 'See the timeline', - eyebrow: 'VISOMI.DEV / JOURNEY', - highlights: ['Fintech', 'Internal platforms', 'Leadership'], - subtitle: 'A career timeline across fintech, internal platforms, product systems, and technical leadership.', - title: 'Engineering across scale, teams, and platforms.', - }, - es: { - accent: 'Un recorrido entre fintech y plataformas internas, guiado por decisiones de arquitectura con contexto.', - cta: 'Ver trayectoria', - eyebrow: 'VISOMI.DEV / TRAYECTORIA', - highlights: ['Fintech', 'Plataformas internas', 'Liderazgo tecnico'], - subtitle: 'Una trayectoria entre fintech, plataformas internas, sistemas de producto y liderazgo tecnico.', - title: 'Trayectoria en escala, equipos y plataformas.', - }, - }, - projects: { - en: { - accent: 'Editorial case studies shaped by delivery constraints, interface choices, and practical system design.', - cta: 'Open the case studies', - eyebrow: 'VISOMI.DEV / PROJECTS', - highlights: ['Fintech', 'SaaS', 'Product architecture'], - subtitle: 'Case studies across fintech, SaaS, internal tools, and scalable product architecture.', - title: 'Selected projects under real delivery constraints.', - }, - es: { - accent: 'Casos de estudio construidos desde restricciones reales, decisiones de interfaz y diseno de sistemas.', - cta: 'Ver casos de estudio', - eyebrow: 'VISOMI.DEV / PROYECTOS', - highlights: ['Fintech', 'SaaS', 'Arquitectura de producto'], - subtitle: 'Casos de estudio sobre fintech, SaaS, herramientas internas y arquitectura de producto escalable.', - title: 'Proyectos con restricciones reales de entrega.', - }, - }, - resume: { - en: { - accent: 'A focused snapshot of roles, leadership, and engineering depth across multiple product stages.', - cta: 'Review the resume', - eyebrow: 'VISOMI.DEV / RESUME', - highlights: ['Experience', 'Leadership', 'Systems thinking'], - subtitle: 'Experience, leadership, systems thinking, and delivery across multiple product stages.', - title: 'Resume and experience snapshot.', - }, - es: { - accent: 'Una vista clara de experiencia, liderazgo y criterio tecnico en distintas etapas de producto.', - cta: 'Ver experiencia', - eyebrow: 'VISOMI.DEV / EXPERIENCIA', - highlights: ['Experiencia', 'Liderazgo', 'Vision sistemica'], - subtitle: 'Experiencia, liderazgo, vision sistemica y ejecucion en distintas etapas de producto.', - title: 'Resumen de experiencia profesional.', - }, - }, -}; - -const clampText = (value, maxLength) => { - const normalizedValue = value.replace(/\s+/g, ' ').trim(); - if (normalizedValue.length <= maxLength) return normalizedValue; - return `${normalizedValue.slice(0, maxLength - 1).trimEnd()}…`; -}; - -const normalizeCard = (card, page, locale) => ({ - accent: clampText(card.accent, 96), - cta: clampText(card.cta, 26), - eyebrow: card.eyebrow, - highlights: card.highlights.map((h) => clampText(h, 28)).slice(0, 3), - locale, - page, - previewLabel: locale === 'es' ? 'vista previa' : 'preview state', - subtitle: clampText(card.subtitle, 140), - title: clampText(card.title, 72), -}); - -const escapeHtml = (value) => - value - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - -const renderHtml = (card) => { - const highlightsMarkup = card.highlights.map((h) => `${escapeHtml(h)}`).join(''); - const highlightsLabel = card.locale === 'es' ? 'claves' : 'highlights'; - - return ` - - - - - ${escapeHtml(card.title)} - - - -
-
-
-

${escapeHtml(card.eyebrow)}

-

${escapeHtml(card.title)}

-

${escapeHtml(card.subtitle)}

-

${escapeHtml(card.accent)}

-
- - ${escapeHtml(card.cta)} -
-
-
- -
- -`; -}; - const chromiumPathCandidates = ['/usr/bin/chromium', '/usr/bin/chromium-browser', '/usr/bin/google-chrome-stable']; const findChromium = async () => { @@ -341,22 +43,19 @@ const generateImages = async () => { let count = 0; for (const locale of locales) { for (const page of pages) { - const content = socialImageContentByPage[page]?.[locale]; - const card = normalizeCard(content, page, locale); - const html = renderHtml(card); + const previewDir = resolve(rootDir, 'apps', 'app', 'public', 'social-image-previews', locale); + const previewPath = resolve(previewDir, `${page}.html`); + const html = await readFile(previewPath, 'utf8'); const outDir = resolve(tmpDir, locale); const outPath = resolve(outDir, `${page}.png`); const publicDir = resolve(rootDir, 'apps', 'website', 'public', 'images', 'seo', locale); const publicPath = resolve(publicDir, `${page}.png`); const distDir = resolve(rootDir, 'dist', 'apps', 'website', 'client', 'images', 'seo', locale); const distPath = resolve(distDir, `${page}.png`); - const previewDir = resolve(rootDir, 'apps', 'app', 'public', 'social-image-previews', locale); - const previewPath = resolve(previewDir, `${page}.html`); await mkdir(outDir, { recursive: true }); await mkdir(publicDir, { recursive: true }); await mkdir(distDir, { recursive: true }); - await mkdir(previewDir, { recursive: true }); const page2 = await browser.newPage(); try { @@ -367,7 +66,6 @@ const generateImages = async () => { await page2.close(); } - await writeFile(previewPath, html, 'utf8'); await copyFile(outPath, publicPath); await copyFile(outPath, distPath); count++;