import { createLogger } from '@design-systems/cli-utils' ; import { getMonorepoRoot } from '@design-systems/cli-utils' ; import fs from 'fs-extra' ; import path from 'path' ; import postcss from 'postcss' ; import postcssload , { PostCSSConfig } from 'postcss-load-config' ; import { codeFrameColumns } from '@babel/code-frame' ; import { getOutPath , formatError } from './utils' ; const logger = createLogger ( { scope : 'build' } ) ; /** Get the output path for a css file. */ export function getCSSPath ( inFile , inDir , outDir ) { const name = path . basename ( inFile ) ; const outPath = getOutPath ( inDir , inFile , outDir ) ; const cssFile = path . join ( outPath , name ) ; return cssFile ; } /**
 * Check package and monorepo cwd for postcss.config.js and returns
 * path. Defaults to empty string.
 */ function getUserPostcssConfig ( multiBuildConfigFile ? , cwd = process . cwd ( ) , configFilename = 'postcss.config.js' ) { logger . trace ( 'Getting User PostCSS config at' , configFilename ) ; const pkgConfigPath = path . join ( cwd , configFilename ) ; if ( fs . existsSync ( pkgConfigPath ) ) { return pkgConfigPath ; } if ( multiBuildConfigFile ) { const monoRepoConfigPath = path . join ( getMonorepoRoot ( cwd ) , multiBuildConfigFile ) ; if ( fs . existsSync ( monoRepoConfigPath ) ) { return monoRepoConfigPath ; } } const monoRepoConfigPath = path . join ( getMonorepoRoot ( cwd ) , configFilename ) ; if ( fs . existsSync ( monoRepoConfigPath ) ) { return monoRepoConfigPath ; } return '' ; } interface LoadOptions { /** Use css modules */ useModules ? : boolean ; /** The path to the config file */ configFile ? : string ; /** The path to the multibuild config file */ multiBuildConfigFile ? : string ; /** Where to store the build results */ outDir ? : string ; /** Dir to start looking for configs in */ cwd ? : string ; /**
     * Report whether there were errors when loading the config.
     * If we are building a package that runs before everything is built
     * and it doesn't have css (in the typescript build). We don't care about the error.
     */ reportError ? : boolean ; /** A multibuild theme name */ themeName ? : string ; } /** Fail the process if the postcss.config has errors */ const reportConfigError = ( error ) => { if ( ! error . message . includes ( 'No PostCSS Config found' ) ) { logger . error ( 'Something is wrong in your postcss.config!' ) ; console . log ( error ) ; process . exit ( 1 ) ; } } ; /** Load the user's postcss config or our default. */ export async function getPostCssConfig ( { useModules , configFile = require . resolve ( './configs/postcss.config' ) , multiBuildConfigFile = undefined , outDir = 'dist' , cwd = process . cwd ( ) , reportError = true , themeName , } ) { const context = useModules ? { env : 'module' , outDir , moduleHash : themeName , } : { } ; try { return await postcssload ( context , getUserPostcssConfig ( multiBuildConfigFile , cwd ) ) ; } catch ( error ) { if ( reportError ) { reportConfigError ( error ) ; logger . trace ( error ) ; } return postcssload ( context , configFile ) ; } } /** Load the user's postcss config or our default synchronously. */ export function getPostCssConfigSync ( { useModules , configFile = require . resolve ( './configs/postcss.config' ) , multiBuildConfigFile = undefined , outDir = 'dist' , cwd = process . cwd ( ) , reportError = true , } ) { const context = useModules ? { env : 'module' , outDir , } : { } ; try { return postcssload . sync ( context , getUserPostcssConfig ( multiBuildConfigFile , cwd ) ) ; } catch ( error ) { if ( reportError ) { reportConfigError ( error ) ; logger . trace ( error ) ; } return postcssload . sync ( context , configFile ) ; } } interface TranspileOptions { /** Css file to transpile */ inFile : string ; /** Directory file is in */ inDir : string ; /** Directory to put the build output */ outDir : string ; /** Where the postcss.config is */ configFile : string ; /** A multibuild theme name */ themeName ? : string ; /** A multibuild config file to use if not overridden */ multiBuildConfigFile ? : string ; /** If this css file represents everything */ isCssMain : boolean ; /** The builder was started in watch mode */ watch : boolean ; } returns export default async function transpile ( { inFile , inDir , isCssMain , outDir , configFile , themeName , multiBuildConfigFile , watch , } ) { const cssFile = getCSSPath ( inFile , inDir , outDir ) ; const { plugins , options } = await getPostCssConfig ( { useModules : true , configFile , multiBuildConfigFile , outDir , themeName , } ) ; const processor = postcss ( plugins ) ; const fileContents = await fs . readFile ( inFile ) ; try { const result = await processor . process ( fileContents , { ... options , plugins , to : isCssMain ? cssFile : cssFile . replace ( '.css' , `-${ themeName }.css` ) , from : inFile , map : { inline : false } , } ) ; if ( isCssMain ) { await fs . outputFile ( `${ cssFile }.map` , result . map . toString ( ) . replace ( /<input css (\d+)>/g , '../src/<input css $1>' ) ) ; } return result ; } catch ( error ) { logger . error ( 'Something went wrong building your css!' ) ; let message = error ; if ( error . name === 'CssSyntaxError' ) { const { line , column , file , reason } = error ; message = formatError ( { file , line , column , message : reason , tool : 'POSTCSS' , code : codeFrameColumns ( error . source , { start : { line , column } } , { highlightCode : true } ) , } ) ; } console . log ( message ) ; if ( ! watch ) { process . exit ( 1 ) ; } } }