Ctrl K
ComunidadGuías…

apps/marketing/scripts/sync-blog-content.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// Makes the blog content available to the marketing site. Posts live in a
// separate private repository (posts/*.md + images/), configured with:
//
// - BLOG_CONTENT_DIR: path to a local checkout of the content repo, relative to
//   apps/marketing. Its images are symlinked so new ones show up in dev.
// - BLOG_CONTENT_REPO ("owner/name") and BLOG_CONTENT_TOKEN: shallow clone the
//   repo into .blog-content. Used for deployments. The token only needs
//   read-only contents access to that one repository.
//
// With neither set, the site builds and runs without a blog.
import { execFileSync } from 'node:child_process';
import { cpSync, existsSync, rmSync, symlinkSync } from 'node:fs';
import path from 'node:path';

const { BLOG_CONTENT_DIR, BLOG_CONTENT_REPO, BLOG_CONTENT_TOKEN, VERCEL_ENV } =
  process.env;

const CLONE_DIR = '.blog-content';
const PUBLIC_ASSETS_DIR = 'public/blog-assets';

rmSync(PUBLIC_ASSETS_DIR, { recursive: true, force: true });

if (BLOG_CONTENT_DIR) {
  const imagesDir = path.resolve(BLOG_CONTENT_DIR, 'images');
  if (!existsSync(path.resolve(BLOG_CONTENT_DIR, 'posts'))) {
    console.error(`BLOG_CONTENT_DIR has no posts/ folder: ${BLOG_CONTENT_DIR}`);
    process.exit(1);
  }
  if (existsSync(imagesDir)) symlinkSync(imagesDir, PUBLIC_ASSETS_DIR);
  console.log('Blog content: using local directory');
} else if (BLOG_CONTENT_REPO && BLOG_CONTENT_TOKEN) {
  if (!/^[\w.-]+\/[\w.-]+$/.test(BLOG_CONTENT_REPO)) {
    console.error('BLOG_CONTENT_REPO must look like "owner/name"');
    process.exit(1);
  }

  rmSync(CLONE_DIR, { recursive: true, force: true });
  const auth = Buffer.from(`x-access-token:${BLOG_CONTENT_TOKEN}`).toString(
    'base64'
  );

  try {
    execFileSync(
      'git',
      [
        'clone',
        '--depth=1',
        '--quiet',
        `https://github.com/${BLOG_CONTENT_REPO}.git`,
        CLONE_DIR,
      ],
      {
        stdio: ['ignore', 'ignore', 'pipe'],
        // Passed through the environment so the token is not in the process
        // arguments or the remote URL.
        env: {
          ...process.env,
          GIT_TERMINAL_PROMPT: '0',
          GIT_CONFIG_COUNT: '1',
          GIT_CONFIG_KEY_0: 'http.https://github.com/.extraheader',
          GIT_CONFIG_VALUE_0: `Authorization: Basic ${auth}`,
        },
      }
    );
  } catch (error) {
    // The repository name is deliberately kept out of build logs.
    const stderr = String(error.stderr ?? '')
      .replaceAll(BLOG_CONTENT_REPO, '<BLOG_CONTENT_REPO>')
      .trim();
    console.error(`Blog content: failed to clone repository\n${stderr}`);
    process.exit(1);
  }

  const imagesDir = path.join(CLONE_DIR, 'images');
  if (existsSync(imagesDir)) {
    cpSync(imagesDir, PUBLIC_ASSETS_DIR, { recursive: true });
  }
  console.log('Blog content: cloned repository');
} else if (VERCEL_ENV === 'production') {
  // Don't let a missing env var silently ship a site without its blog.
  console.error(
    'Blog content is not configured: set BLOG_CONTENT_REPO and BLOG_CONTENT_TOKEN'
  );
  process.exit(1);
} else {
  console.log('Blog content: not configured, building without the blog');
}