kremalicious/blog

View on GitHub
scripts/create-icons/index.ts

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
//
// Generate Astro components from SVG files.
// adapted from https://github.com/astro-community/icons
//
import fs from 'node:fs/promises'
import ps from 'node:path/posix'
import ora from 'ora'
import chalk from 'chalk'
import { toInnerSvg } from './svg.ts'
import { toAstroComponent } from './toAstroComponent.ts'
import { toReactComponent } from './toReactComponent.ts'

// Current directory.
const currentDir = ps.resolve('.')

// // Source directories
const srcDirs = [
  ps.resolve(currentDir, 'node_modules/feather-icons/dist/icons'),
  ps.resolve(currentDir, 'src/images')
]

// Distribution directory.
const distDir = ps.resolve(currentDir, 'src/images/components')

// Data related to each icon exported by this package.
const icons = []

export async function generateIcons(distDir: string) {
  const spinner = ora(
    `${chalk.bold('[create-icons]')} Create icon components`
  ).start()

  // clean the distribution directory
  await fs.rm(distDir, { force: true, recursive: true })
  await fs.mkdir(distDir, { recursive: true })
  await fs.mkdir(`${distDir}/react`, { recursive: true })

  // copy the attribute typings file
  await fs.copyFile(
    ps.resolve(currentDir, 'scripts/create-icons/Props.d.ts'),
    ps.resolve(distDir, 'Props.d.ts')
  )
  await fs.copyFile(
    ps.resolve(currentDir, 'scripts/create-icons/Props.d.ts'),
    ps.resolve(`${distDir}/react`, 'Props.d.ts')
  )

  // convert the SVG files into Astro & React components
  let contentOfIndexJS = '// @ts-nocheck\n'
  let contentOfIndexReactJS = '// @ts-nocheck\n'

  for (const src of srcDirs) {
    for (let filepath of await fs.readdir(src, { encoding: 'utf8' })) {
      // ignore non-svg files
      if (!filepath.endsWith('.svg')) continue

      // Base name of the SVG.
      const name = filepath.replace(/\.svg$/, '')

      // get filepath as a full path
      filepath = ps.resolve(src, filepath)

      // Inner contents of the SVG file.
      const innerSVG = toInnerSvg(await fs.readFile(filepath, 'utf8'))

      // Formatted title.
      const title = name
        .replace(
          // uppercase alphabetic characters after the start or a dash
          /(?<=^|-)([a-z])/g,
          (_0, $1) => $1.toUpperCase()
        )
        .replace(
          // replace non-alphanumeric characters with space
          /[^A-Za-z0-9]+/g,
          ' '
        )
        .replace(
          // respect 'GitHub' brand casing
          'Github Logo',
          'GitHub Logo'
        )

      // Base name, which is the formatted title without spaces (PascalCase)
      const baseName = title.replace(/ /g, '')

      // write the astro component to a file
      await fs.writeFile(
        ps.resolve(distDir, `${baseName}.astro`),
        toAstroComponent(innerSVG, title),
        'utf8'
      )

      // write the react component to a file
      await fs.writeFile(
        ps.resolve(`${distDir}/react`, `${baseName}.tsx`),
        toReactComponent(innerSVG, title),
        'utf8'
      )

      // add the astro component export to the main entry `index.ts` file
      contentOfIndexJS += `\nexport { default as ${baseName} } from './${baseName}.astro'`

      // add the react component export to the main entry `react/index.ts` file
      contentOfIndexReactJS += `\nexport { Icon as ${baseName} }  from './${baseName}.tsx'`

      icons.push({ name, baseName, title })
    }
  }

  // write the main Astro entry `index.ts` file
  await fs.writeFile(ps.resolve(distDir, 'index.ts'), contentOfIndexJS, 'utf8')

  // write the main React entry `react/index.ts` file
  await fs.writeFile(
    ps.resolve(`${distDir}/react`, 'index.ts'),
    contentOfIndexReactJS,
    'utf8'
  )

  spinner.succeed(
    `${chalk.bold('[create-icons]')} Generated ${
      icons.length
    } icons into @images/components.`
  )
}

generateIcons(distDir)