diff --git a/packages/fontaine/README.md b/packages/fontaine/README.md index 35f33774..ba508d2e 100644 --- a/packages/fontaine/README.md +++ b/packages/fontaine/README.md @@ -43,6 +43,16 @@ Or, with `yarn` yarn add -D fontaine ``` +## CLI usage + +You can also run fontaine directly against a CSS file: + +```bash +npx fontaine ./src/styles.css ./dist/styles.css +``` + +If the output file is omitted, fontaine writes to `.fontaine.css`. + ## Usage ```js diff --git a/packages/fontaine/package.json b/packages/fontaine/package.json index 04041c28..92cb03d9 100644 --- a/packages/fontaine/package.json +++ b/packages/fontaine/package.json @@ -33,6 +33,9 @@ "main": "./dist/index.cjs", "module": "./dist/index.mjs", "types": "./dist/index.d.ts", + "bin": { + "fontaine": "./dist/cli.mjs" + }, "files": [ "dist" ], diff --git a/packages/fontaine/src/cli.ts b/packages/fontaine/src/cli.ts new file mode 100644 index 00000000..f5ad58f5 --- /dev/null +++ b/packages/fontaine/src/cli.ts @@ -0,0 +1,97 @@ +#!/usr/bin/env node +import type { FontaineTransformOptions } from './transform' +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' +import { basename, dirname, extname, resolve } from 'node:path' +import process from 'node:process' +import { pathToFileURL } from 'node:url' +import { FontaineTransform } from './transform' + +/** Options for transforming a CSS file from the fontaine CLI or API. */ +export interface FontaineCliOptions extends Partial { + /** CSS file to transform. */ + input: string + /** Output file path. Defaults to `.fontaine.css`. */ + output?: string +} + +/** + * Transform a CSS file with Fontaine and write the generated output. + * + * @returns The resolved output path. + */ +export async function transformCssFile({ input, output, ...options }: FontaineCliOptions): Promise { + const inputPath = resolve(input) + const source = readFileSync(inputPath, 'utf8') + const { fallbacks = ['Arial'], resolvePath, ...transformOptions } = options + const plugin = FontaineTransform.rollup({ + ...transformOptions, + fallbacks, + resolvePath: resolvePath || (id => pathToFileURL(resolve(dirname(inputPath), id)).toString()), + }) + const transformPlugin = Array.isArray(plugin) ? plugin[0]! : plugin + const transform = transformPlugin.transform + if (!transform || typeof transform === 'string' || typeof transform === 'function') + throw new Error('Fontaine transform hook is unavailable') + + const transformed = await transform.handler.call({} as any, source, inputPath) + const code = typeof transformed === 'string' ? transformed : (transformed?.code ?? source) + const outputPath = output + ? resolve(output) + : `${inputPath.slice(0, Math.max(0, inputPath.length - extname(inputPath).length))}.fontaine.css` + + mkdirSync(dirname(outputPath), { recursive: true }) + writeFileSync(outputPath, code) + return outputPath +} + +/** Print command-line usage information. */ +function printHelp() { + console.log(`Usage: fontaine [output.css] + +Transforms a CSS file and writes fallback font-face rules using fontaine. + +Arguments: + input.css CSS file to transform + output.css Output path. Defaults to .fontaine.css +`) +} + +/** Run the Fontaine command-line interface. */ +async function main() { + const [, , ...args] = process.argv + + if (args.includes('-h') || args.includes('--help')) { + printHelp() + return + } + + const [input, output] = args + + if (args.length > 2) { + console.error('Too many positional arguments. Expected: fontaine [output.css]') + process.exitCode = 1 + return + } + + if (!input) { + printHelp() + process.exitCode = 1 + return + } + + if (!existsSync(input)) { + console.error(`Input file not found: ${input}`) + process.exitCode = 1 + return + } + + const outputPath = await transformCssFile({ input, output }) + console.log(`Generated ${basename(outputPath)}`) +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + main().catch((error) => { + console.error(error) + process.exitCode = 1 + }) +} diff --git a/packages/fontaine/src/index.ts b/packages/fontaine/src/index.ts index c31a2ad9..041febf4 100644 --- a/packages/fontaine/src/index.ts +++ b/packages/fontaine/src/index.ts @@ -1,7 +1,8 @@ +export { transformCssFile } from './cli' +export type { FontaineCliOptions } from './cli' export { generateFallbackName, generateFontFace } from './css' export { DEFAULT_CATEGORY_FALLBACKS, type FontCategory, resolveCategoryFallbacks } from './fallbacks' export type { ResolveCategoryFallbacksOptions } from './fallbacks' export { getMetricsForFamily, readMetrics } from './metrics' - export { FontaineTransform } from './transform' export type { FontaineTransformOptions } from './transform' diff --git a/packages/fontaine/tsdown.config.ts b/packages/fontaine/tsdown.config.ts index 5bcf1e28..fae2385e 100644 --- a/packages/fontaine/tsdown.config.ts +++ b/packages/fontaine/tsdown.config.ts @@ -2,7 +2,7 @@ import fs from 'node:fs' import { defineConfig } from 'tsdown' export default defineConfig({ - entry: ['src/index.ts'], + entry: ['src/index.ts', 'src/cli.ts'], format: ['es', 'cjs'], dts: { oxc: true,