feat: improve SEO with structured data, breadcrumbs, and comprehensive favicon support while optimizing images and config performance.
No files matched your search
@@ -0,0 +1,7 @@
|
|||||||
|
chrome >= 90
|
||||||
|
firefox >= 90
|
||||||
|
edge >= 90
|
||||||
|
safari >= 15
|
||||||
|
ios_saf >= 15
|
||||||
|
and_chr >= 90
|
||||||
|
not dead
|
||||||
|
After Width: | Height: | Size: 4.1 KiB |
@@ -1,4 +1,4 @@
|
|||||||
import { paginaMeta } from "@/lib/seo";
|
import { paginaMeta, jsonLd } from "@/lib/seo";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Header from "@/components/Header";
|
import Header from "@/components/Header";
|
||||||
import Footer from "@/components/Footer";
|
import Footer from "@/components/Footer";
|
||||||
@@ -33,6 +33,22 @@ export default async function FrameworkDetail({ params }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-gradient-to-br from-slate-100 via-slate-50 to-purple-100/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
|
<div className="bg-gradient-to-br from-slate-100 via-slate-50 to-purple-100/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
|
||||||
|
{/* Ficha técnica do framework e trilha de navegação para o Google */}
|
||||||
|
<script
|
||||||
|
type="application/ld+json"
|
||||||
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd.framework(key, framework)) }}
|
||||||
|
/>
|
||||||
|
<script
|
||||||
|
type="application/ld+json"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: JSON.stringify(jsonLd.trilha([
|
||||||
|
{ nome: "Início", caminho: "/" },
|
||||||
|
{ nome: "Frameworks", caminho: "/frameworks" },
|
||||||
|
{ nome: framework.nome, caminho: `/frameworks/${encodeURIComponent(key)}` },
|
||||||
|
])),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
<main className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
|
<main className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
|
||||||
|
|||||||
@@ -374,6 +374,15 @@ export default function Glossario() {
|
|||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd.glossario(categorias)) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd.glossario(categorias)) }}
|
||||||
/>
|
/>
|
||||||
|
<script
|
||||||
|
type="application/ld+json"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: JSON.stringify(jsonLd.trilha([
|
||||||
|
{ nome: "Início", caminho: "/" },
|
||||||
|
{ nome: "Glossário", caminho: "/glossario" },
|
||||||
|
])),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,20 @@ export const metadata = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
icons: { icon: "/marca/espdocs-marca.svg", apple: "/marca/espdocs-marca.svg" },
|
icons: {
|
||||||
|
icon: [
|
||||||
|
{ url: "/favicon.ico", sizes: "any" },
|
||||||
|
{ url: "/icon.svg", type: "image/svg+xml" },
|
||||||
|
{ url: "/favicon-48x48.png", sizes: "48x48", type: "image/png" },
|
||||||
|
{ url: "/icon-192.png", sizes: "192x192", type: "image/png" },
|
||||||
|
{ url: "/icon-512.png", sizes: "512x512", type: "image/png" },
|
||||||
|
],
|
||||||
|
apple: [
|
||||||
|
{ url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" },
|
||||||
|
],
|
||||||
|
shortcut: "/favicon.ico",
|
||||||
|
},
|
||||||
|
manifest: "/site.webmanifest",
|
||||||
formatDetection: { telephone: false, address: false, email: false },
|
formatDetection: { telephone: false, address: false, email: false },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import Header from "@/components/Header";
|
import Header from "@/components/Header";
|
||||||
import Footer from "@/components/Footer";
|
import Footer from "@/components/Footer";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import Image from "next/image";
|
||||||
import {
|
import {
|
||||||
SlidersHorizontal,
|
SlidersHorizontal,
|
||||||
ShoppingBag,
|
ShoppingBag,
|
||||||
@@ -188,10 +187,12 @@ export default function Home() {
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="group mb-6"
|
className="group mb-6"
|
||||||
>
|
>
|
||||||
<img
|
<Image
|
||||||
src="/logo.png"
|
src="/logo.png"
|
||||||
alt="Ciência Embarcada Logo"
|
alt="Ciência Embarcada Logo"
|
||||||
className="h-16 md:h-20 mb-2 transition-transform duration-300 group-hover:scale-105"
|
width={80}
|
||||||
|
height={80}
|
||||||
|
className="h-16 w-16 md:h-20 md:w-20 mb-2 transition-transform duration-300 group-hover:scale-105 object-contain"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { paginaMeta } from "@/lib/seo";
|
|||||||
import Header from "@/components/Header";
|
import Header from "@/components/Header";
|
||||||
import Footer from "@/components/Footer";
|
import Footer from "@/components/Footer";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import Image from "next/image";
|
||||||
import {
|
import {
|
||||||
Languages,
|
Languages,
|
||||||
GitCompare,
|
GitCompare,
|
||||||
@@ -172,9 +173,11 @@ export default function Sobre() {
|
|||||||
<div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl border border-slate-300 dark:border-slate-800/80 rounded-3xl p-8 shadow-xl">
|
<div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl border border-slate-300 dark:border-slate-800/80 rounded-3xl p-8 shadow-xl">
|
||||||
<div className="flex flex-col items-center mb-6 select-none">
|
<div className="flex flex-col items-center mb-6 select-none">
|
||||||
<div className="w-20 h-20 bg-gradient-to-br from-purple-500/10 to-pink-500/10 rounded-2xl flex items-center justify-center mb-4 border border-purple-500/20 shadow-inner">
|
<div className="w-20 h-20 bg-gradient-to-br from-purple-500/10 to-pink-500/10 rounded-2xl flex items-center justify-center mb-4 border border-purple-500/20 shadow-inner">
|
||||||
<img
|
<Image
|
||||||
src="/logo.png"
|
src="/logo.png"
|
||||||
alt="Ciência Embarcada Logo"
|
alt="Ciência Embarcada Logo"
|
||||||
|
width={64}
|
||||||
|
height={64}
|
||||||
className="h-16 w-16 object-contain"
|
className="h-16 w-16 object-contain"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export default function Header() {
|
|||||||
<Link href="/" className="group shrink-0">
|
<Link href="/" className="group shrink-0">
|
||||||
<span className="flex items-center gap-2.5">
|
<span className="flex items-center gap-2.5">
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img src="/marca/espdocs-marca.svg" alt="" aria-hidden="true" className="w-7 h-7 md:w-8 md:h-8 shrink-0" />
|
<img src="/marca/espdocs-marca.svg" alt="" aria-hidden="true" width={32} height={32} className="w-7 h-7 md:w-8 md:h-8 shrink-0" />
|
||||||
<h1 className="text-2xl md:text-3xl font-display font-extrabold bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 bg-clip-text text-transparent transition-all duration-300 group-hover:scale-[1.02]">
|
<h1 className="text-2xl md:text-3xl font-display font-extrabold bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 bg-clip-text text-transparent transition-all duration-300 group-hover:scale-[1.02]">
|
||||||
ESPDocs
|
ESPDocs
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
@@ -91,31 +91,95 @@ export const jsonLd = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Ficha técnica de uma série, com as especificações como propriedades. */
|
/** Documentação e ficha técnica da série como TechArticle para o Google. */
|
||||||
serie(chave, serie) {
|
serie(chave, serie) {
|
||||||
const prop = (nome, valor) =>
|
const prop = (nome, valor) =>
|
||||||
valor ? { "@type": "PropertyValue", name: nome, value: String(valor) } : null;
|
valor ? { "@type": "PropertyValue", name: nome, value: String(valor) } : null;
|
||||||
|
|
||||||
|
const propriedades = [
|
||||||
|
prop("Fabricante", "Espressif Systems"),
|
||||||
|
prop("Arquitetura", serie.arquitetura),
|
||||||
|
prop("Núcleos", serie.nucleos),
|
||||||
|
prop("Frequência máxima", serie.frequencia),
|
||||||
|
prop("SRAM", serie.memoria_sram),
|
||||||
|
prop("RTC SRAM", serie.memoria_sram_rtc),
|
||||||
|
prop("Flash suportada", serie.flash_suportada),
|
||||||
|
prop("PSRAM suportada", serie.psram_suportada),
|
||||||
|
prop("GPIO", serie.gpio),
|
||||||
|
prop("Wi-Fi", serie.wifi),
|
||||||
|
prop("Bluetooth", serie.bluetooth),
|
||||||
|
prop("Zigbee / Thread / 802.15.4", serie.zigbee_thread),
|
||||||
|
prop("Encapsulamento", serie.encapsulamento),
|
||||||
|
prop("ESP-IDF mínimo", serie.esp_idf_minimo),
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "Product",
|
"@type": "TechArticle",
|
||||||
"@id": `${SITE.url}/series/${chave}#produto`,
|
"@id": `${SITE.url}/series/${chave}#artigo`,
|
||||||
name: serie.nome_completo || chave,
|
headline: `${serie.nome_completo || chave}: especificações e pinagem`,
|
||||||
|
name: `${serie.nome_completo || chave} - Documentação Técnica e Especificações`,
|
||||||
description: serie.descricao,
|
description: serie.descricao,
|
||||||
category: "Microcontrolador",
|
inLanguage: "pt-BR",
|
||||||
brand: { "@type": "Brand", name: "Espressif Systems" },
|
mainEntityOfPage: `${SITE.url}/series/${chave}`,
|
||||||
url: `${SITE.url}/series/${chave}`,
|
url: `${SITE.url}/series/${chave}`,
|
||||||
additionalProperty: [
|
author: {
|
||||||
prop("Arquitetura", serie.arquitetura),
|
"@type": "Person",
|
||||||
prop("Núcleos", serie.nucleos),
|
name: SITE.autor,
|
||||||
prop("Frequência máxima", serie.frequencia),
|
url: SITE.autorUrl,
|
||||||
prop("SRAM", serie.memoria_sram),
|
},
|
||||||
prop("GPIO", serie.gpio),
|
publisher: {
|
||||||
prop("Wi-Fi", serie.wifi),
|
"@type": "Organization",
|
||||||
prop("Bluetooth", serie.bluetooth),
|
name: "Ciência Embarcada",
|
||||||
prop("Encapsulamento", serie.encapsulamento),
|
url: SITE.blog,
|
||||||
prop("ESP-IDF mínimo", serie.esp_idf_minimo),
|
logo: {
|
||||||
].filter(Boolean),
|
"@type": "ImageObject",
|
||||||
|
url: `${SITE.url}/marca/espdocs-marca.svg`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
about: {
|
||||||
|
"@type": "Thing",
|
||||||
|
name: serie.nome_completo || chave,
|
||||||
|
description: serie.descricao,
|
||||||
|
additionalProperty: propriedades,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Documentação técnica de framework. */
|
||||||
|
framework(chave, framework) {
|
||||||
|
return {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "TechArticle",
|
||||||
|
"@id": `${SITE.url}/frameworks/${encodeURIComponent(chave)}#artigo`,
|
||||||
|
headline: `${framework.nome} para ESP32: Guia e Documentação`,
|
||||||
|
name: `${framework.nome_completo || framework.nome} para ESP32`,
|
||||||
|
description: framework.descricao,
|
||||||
|
inLanguage: "pt-BR",
|
||||||
|
mainEntityOfPage: `${SITE.url}/frameworks/${encodeURIComponent(chave)}`,
|
||||||
|
url: `${SITE.url}/frameworks/${encodeURIComponent(chave)}`,
|
||||||
|
author: {
|
||||||
|
"@type": "Person",
|
||||||
|
name: SITE.autor,
|
||||||
|
url: SITE.autorUrl,
|
||||||
|
},
|
||||||
|
publisher: {
|
||||||
|
"@type": "Organization",
|
||||||
|
name: "Ciência Embarcada",
|
||||||
|
url: SITE.blog,
|
||||||
|
logo: {
|
||||||
|
"@type": "ImageObject",
|
||||||
|
url: `${SITE.url}/marca/espdocs-marca.svg`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
about: {
|
||||||
|
"@type": "SoftwareApplication",
|
||||||
|
name: framework.nome_completo || framework.nome,
|
||||||
|
applicationCategory: "DeveloperApplication",
|
||||||
|
operatingSystem: "Embedded",
|
||||||
|
description: framework.descricao,
|
||||||
|
url: framework.documentacao || `${SITE.url}/frameworks/${encodeURIComponent(chave)}`,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,16 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {};
|
const nextConfig = {
|
||||||
|
experimental: {
|
||||||
|
optimizePackageImports: [
|
||||||
|
"lucide-react",
|
||||||
|
"@mui/material",
|
||||||
|
"@mui/icons-material",
|
||||||
|
"framer-motion",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
images: {
|
||||||
|
formats: ["image/avif", "image/webp"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
"build": "next build --turbopack",
|
"build": "next build --turbopack",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
|
"favicons": "node scripts/generate-favicons.mjs",
|
||||||
"auditar-temas": "node scripts/auditar-temas.mjs",
|
"auditar-temas": "node scripts/auditar-temas.mjs",
|
||||||
"slides-pdf": "node scripts/slides-pdf.mjs"
|
"slides-pdf": "node scripts/slides-pdf.mjs"
|
||||||
},
|
},
|
||||||
|
|||||||
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 686 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 39 KiB |
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "ESPDocs",
|
||||||
|
"short_name": "ESPDocs",
|
||||||
|
"description": "Documentação do ecossistema ESP32 em português brasileiro",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#020617",
|
||||||
|
"theme_color": "#8b5cf6",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/favicon-48x48.png",
|
||||||
|
"sizes": "48x48",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import puppeteer from 'puppeteer';
|
||||||
|
|
||||||
|
const svgPath = path.resolve('app/icon.svg');
|
||||||
|
const svgContent = fs.readFileSync(svgPath, 'utf8');
|
||||||
|
|
||||||
|
function createIco(pngBuffers) {
|
||||||
|
// pngBuffers: array of { width, height, buffer }
|
||||||
|
const count = pngBuffers.length;
|
||||||
|
const headerSize = 6;
|
||||||
|
const dirEntrySize = 16;
|
||||||
|
let offset = headerSize + dirEntrySize * count;
|
||||||
|
|
||||||
|
const header = Buffer.alloc(headerSize);
|
||||||
|
header.writeUInt16LE(0, 0); // reserved
|
||||||
|
header.writeUInt16LE(1, 2); // ICO type
|
||||||
|
header.writeUInt16LE(count, 4); // count
|
||||||
|
|
||||||
|
const entries = [];
|
||||||
|
for (const img of pngBuffers) {
|
||||||
|
const entry = Buffer.alloc(dirEntrySize);
|
||||||
|
entry.writeUInt8(img.width >= 256 ? 0 : img.width, 0);
|
||||||
|
entry.writeUInt8(img.height >= 256 ? 0 : img.height, 1);
|
||||||
|
entry.writeUInt8(0, 2); // colors
|
||||||
|
entry.writeUInt8(0, 3); // reserved
|
||||||
|
entry.writeUInt16LE(1, 4); // planes
|
||||||
|
entry.writeUInt16LE(32, 6); // bpp
|
||||||
|
entry.writeUInt32LE(img.buffer.length, 8); // size
|
||||||
|
entry.writeUInt32LE(offset, 12); // offset
|
||||||
|
entries.push(entry);
|
||||||
|
offset += img.buffer.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Buffer.concat([header, ...entries, ...pngBuffers.map(b => b.buffer)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const browser = await puppeteer.launch({
|
||||||
|
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||||||
|
});
|
||||||
|
const page = await browser.newPage();
|
||||||
|
|
||||||
|
const html = `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
html, body { width: 100%; height: 100%; overflow: hidden; background: transparent; }
|
||||||
|
svg { width: 100%; height: 100%; display: block; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>${svgContent}</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
await page.setContent(html);
|
||||||
|
|
||||||
|
const sizes = [16, 32, 48, 96, 180, 192, 512];
|
||||||
|
const rendered = {};
|
||||||
|
|
||||||
|
for (const size of sizes) {
|
||||||
|
await page.setViewport({ width: size, height: size, deviceScaleFactor: 1 });
|
||||||
|
const buffer = await page.screenshot({
|
||||||
|
type: 'png',
|
||||||
|
omitBackground: true,
|
||||||
|
clip: { x: 0, y: 0, width: size, height: size }
|
||||||
|
});
|
||||||
|
rendered[size] = buffer;
|
||||||
|
console.log(`Rendered ${size}x${size}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await browser.close();
|
||||||
|
|
||||||
|
// Create ICO from 16, 32, 48
|
||||||
|
const icoBuffer = createIco([
|
||||||
|
{ width: 16, height: 16, buffer: rendered[16] },
|
||||||
|
{ width: 32, height: 32, buffer: rendered[32] },
|
||||||
|
{ width: 48, height: 48, buffer: rendered[48] }
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Write favicon.ico to public and app
|
||||||
|
fs.writeFileSync('public/favicon.ico', icoBuffer);
|
||||||
|
fs.writeFileSync('app/favicon.ico', icoBuffer);
|
||||||
|
console.log('Saved favicon.ico to public/ and app/');
|
||||||
|
|
||||||
|
// Write PNG favicons to public
|
||||||
|
fs.writeFileSync('public/favicon-48x48.png', rendered[48]);
|
||||||
|
fs.writeFileSync('public/favicon-32x32.png', rendered[32]);
|
||||||
|
fs.writeFileSync('public/favicon-16x16.png', rendered[16]);
|
||||||
|
fs.writeFileSync('public/apple-touch-icon.png', rendered[180]);
|
||||||
|
fs.writeFileSync('public/icon-192.png', rendered[192]);
|
||||||
|
fs.writeFileSync('public/icon-512.png', rendered[512]);
|
||||||
|
console.log('Saved PNG icons to public/');
|
||||||
|
|
||||||
|
// Create site.webmanifest
|
||||||
|
const manifest = {
|
||||||
|
name: "ESPDocs",
|
||||||
|
short_name: "ESPDocs",
|
||||||
|
description: "Documentação do ecossistema ESP32 em português brasileiro",
|
||||||
|
start_url: "/",
|
||||||
|
display: "standalone",
|
||||||
|
background_color: "#020617",
|
||||||
|
theme_color: "#8b5cf6",
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: "/favicon-48x48.png",
|
||||||
|
sizes: "48x48",
|
||||||
|
type: "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/icon-192.png",
|
||||||
|
sizes: "192x192",
|
||||||
|
type: "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/icon-512.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.writeFileSync('public/site.webmanifest', JSON.stringify(manifest, null, 2));
|
||||||
|
console.log('Saved site.webmanifest to public/');
|
||||||
|
}
|
||||||
|
|
||||||
|
run().catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||