purgetss
Version:
A package that simplifies mobile app creation for Titanium developers.
1,466 lines (1,199 loc) • 106 kB
JavaScript
/* eslint-disable camelcase */
/* eslint-disable no-useless-escape */
/* eslint-disable space-before-function-paren */
const fs = require('fs')
const cwd = process.cwd()
const _ = require('lodash')
const junk = require('junk')
const glob = require('glob')
const path = require('path')
const chalk = require('chalk')
const convert = require('xml-js')
const readCSS = require('read-css')
const traverse = require('traverse')
const inquirer = require('inquirer')
const helpers = require('./lib/helpers')
const colores = require('./lib/colores').colores
module.exports.colores = colores
const purgeLabel = colores.purgeLabel
let purgingDebug = false
const logger = {
info: function (...args) {
console.log(purgeLabel, args.join(' '))
},
warn: function (...args) {
console.log(purgeLabel, chalk.yellow(args.join(' ')))
},
error: function (...args) {
console.log(purgeLabel, chalk.red(args.join(' ')))
},
file: function (...args) {
console.log(purgeLabel, chalk.yellow(args.join(' ')), 'file created!')
}
}
const projectsLibFolder = `${cwd}/app/lib`
const classicProjectLibFolder = `${cwd}/Resources/lib`
const projectsAppTSS = `${cwd}/app/styles/app.tss`
const projects_AppTSS = `${cwd}/app/styles/_app.tss`
const projectsAlloyJMKFile = `${cwd}/app/alloy.jmk`
const projectsFontsFolder = `${cwd}/app/assets/fonts`
const projectsFontAwesomeJS = `${cwd}/app/lib/fontawesome.js`
const projectsPurgeTSSFolder = `${cwd}/purgetss`
const projectsConfigJS = `${cwd}/purgetss/config.js`
const projectsTailwind_TSS = `${cwd}/purgetss/styles/tailwind.tss`
const projectsPurge_TSS_Fonts_Folder = `${cwd}/purgetss/fonts`
const projectsPurge_TSS_Styles_Folder = `${cwd}/purgetss/styles`
const projectsFA_TSS_File = `${cwd}/purgetss/styles/fontawesome.tss`
// js icon modules
const projectRoot = path.resolve(__dirname)
const srcLibFA = path.resolve(projectRoot, './dist/fontawesome.js')
const srcLibMI = path.resolve(projectRoot, './dist/materialicons.js')
const srcLibMS = path.resolve(projectRoot, './dist/materialsymbols.js')
const srcLibF7 = path.resolve(projectRoot, './dist/framework7icons.js')
const srcPurgeTSSLibrary = path.resolve(projectRoot, './dist/purgetss.ui.js')
//
const srcTailwindTSS = path.resolve(projectRoot, './dist/tailwind.tss')
//
// PRO
const srcFA_Pro_CSS = `${cwd}/node_modules/@fortawesome/fontawesome-pro/css/all.css`
const srcFA_Pro_Web_Fonts_Folder = `${cwd}/node_modules/@fortawesome/fontawesome-pro/webfonts/`
// Alternate location
const srcFA_Pro_CSS_Alt = `${cwd}/app/lib/node_modules/@fortawesome/fontawesome-pro/css/all.css`
const srcFA_Pro_Web_Fonts_Folder_Alt = `${cwd}/app/lib/node_modules/@fortawesome/fontawesome-pro/webfonts/`
const srcFA_ProReset_TSS_File = './lib/templates/fontawesome/pro-reset.tss'
const srcFA_ProTemplateTSS_File = './lib/templates/fontawesome/pro-template.tss'
const srcFA_ProFontFamilies = {
'fa-thin-100.ttf': 'FontAwesome6Pro-Thin.ttf',
'fa-light-300.ttf': 'FontAwesome6Pro-Light.ttf',
'fa-brands-400.ttf': 'FontAwesome6Brands-Regular.ttf',
'fa-regular-400.ttf': 'FontAwesome6Pro-Regular.ttf',
'fa-solid-900.ttf': 'FontAwesome6Pro-Solid.ttf'
}
// BETA
const srcFA_Beta_CSSFile = `${cwd}/purgetss/fontawesome-beta/css/all.css`
const srcFA_Beta_Web_Fonts_Folder = `${cwd}/purgetss/fontawesome-beta/webfonts/`
const srcFA_Beta_ResetTSS = './lib/templates/fontawesome/beta-reset.tss'
const srcFA_Beta_TemplateTSS = './lib/templates/fontawesome/beta-template.tss'
const srcFA_Beta_FontFamilies = {
'fa-thin-100.ttf': 'FontAwesome6Pro-Thin.ttf',
'fa-light-300.ttf': 'FontAwesome6Pro-Light.ttf',
'fa-brands-400.ttf': 'FontAwesome6Brands-Regular.ttf',
'fa-regular-400.ttf': 'FontAwesome6Pro-Regular.ttf',
'fa-solid-900.ttf': 'FontAwesome6Pro-Solid.ttf'
}
//
const srcFonts_Folder = path.resolve(projectRoot, './assets/fonts')
const srcReset_TSS_File = path.resolve(projectRoot, './dist/reset.tss')
const PurgeTSSPackageJSON = JSON.parse(fs.readFileSync(path.resolve(projectRoot, './package.json'), 'utf8'))
const srcFontAwesomeTSSFile = path.resolve(projectRoot, './dist/fontawesome.tss')
const srcFramework7FontTSSFile = path.resolve(projectRoot, './dist/framework7icons.tss')
const srcMaterialIconsTSSFile = path.resolve(projectRoot, './dist/materialicons.tss')
const srcMaterialSymbolsTSSFile = path.resolve(projectRoot, './dist/materialsymbols.tss')
const srcConfigFile = path.resolve(projectRoot, './lib/templates/purgetss.config.js')
const configFile = (fs.existsSync(projectsConfigJS)) ? require(projectsConfigJS) : require(srcConfigFile)
configFile.purge = configFile.purge ?? { mode: 'all', method: 'sync' }
configFile.purge.method = configFile.purge.method ?? 'sync'
configFile.theme.extend = configFile.theme.extend ?? {}
const configOptions = (configFile.purge && configFile.purge.options) ? configFile.purge.options : {}
if (configOptions) {
configOptions.legacy = configOptions.legacy ?? false
configOptions.widgets = configOptions.widgets ?? false
configOptions.missing = configOptions.missing ?? true
configOptions.plugins = configOptions.plugins ?? []
}
let methodCommand
let oppositeCommand
if (configFile.purge.method === 'sync' || configFile.purge.method === '') {
oppositeCommand = "require('child_process').exec('purgetss"
methodCommand = "\trequire('child_process').execSync('purgetss', logger.warn('::PurgeTSS:: Auto-Purging ' + event.dir.project));"
} else {
oppositeCommand = "require('child_process').execSync('purgetss"
methodCommand = "\trequire('child_process').exec('purgetss', logger.warn('::PurgeTSS:: Auto-Purging ' + event.dir.project));"
}
const srcJMKFile = path.resolve(projectRoot, './lib/templates/alloy.jmk')
// ! Interfase
// ! Command: purgetss
function purgeClasses(options) {
if (alloyProject()) {
purgingDebug = options.debug
const recentlyCreated = makeSureFileExists(projectsAppTSS)
if (Date.now() > (fs.statSync(projectsAppTSS).mtimeMs + 2000) || recentlyCreated) {
start()
init(options)
backupOriginalAppTss()
const uniqueClasses = getUniqueClasses()
let tempPurged = copyResetTemplateAnd_appTSS()
tempPurged += purgeTailwind(uniqueClasses)
const cleanUniqueClasses = cleanClasses(uniqueClasses)
tempPurged += purgeFontAwesome(uniqueClasses, cleanUniqueClasses)
tempPurged += purgeMaterialIcons(uniqueClasses, cleanUniqueClasses)
tempPurged += purgeMaterialSymbols(uniqueClasses, cleanUniqueClasses)
tempPurged += purgeFramework7(uniqueClasses, cleanUniqueClasses)
tempPurged += purgeFonts(uniqueClasses, cleanUniqueClasses)
tempPurged += processMissingClasses(tempPurged)
saveFile(projectsAppTSS, tempPurged)
logger.file('app.tss')
finish()
} else {
logger.warn('Project purged less than 2 seconds ago!')
}
}
}
module.exports.purgeClasses = purgeClasses
// ! Command: init
function init(options) {
// config file
if (!fs.existsSync(projectsConfigJS)) {
createConfigFile()
}
// tailwind.tss
if (!fs.existsSync(projectsTailwind_TSS) || options.all) {
buildTailwindBasedOnConfigOptions(options)
}
// definitios file
if (!fs.existsSync(`${cwd}/purgetss/styles/definitions.css`) || options.all) {
createDefinitionsFile()
}
// auto purge hook
if (fs.existsSync(projectsAlloyJMKFile)) {
if (!fs.readFileSync(projectsAlloyJMKFile, 'utf8').includes('::PurgeTSS::')) {
addHook()
} else if (fs.readFileSync(projectsAlloyJMKFile, 'utf8').includes(oppositeCommand)) {
deleteHook()
addHook()
}
} else {
createJMKFile()
}
}
module.exports.init = init
function processMissingClasses(tempPurged) {
let unusedOrMissingClasses = ''
if (configOptions.missing) {
const missingClasses = findMissingClasses(tempPurged)
if (missingClasses.length > 0) {
unusedOrMissingClasses += '\n'
unusedOrMissingClasses += '// Unused or unsupported classes\n'
_.each(missingClasses, (missingClass) => {
unusedOrMissingClasses += `// '.${missingClass}': { }\n`
})
}
}
return unusedOrMissingClasses
}
// ! Command: watch
function watchMode(options) {
if (alloyProject()) {
if (fs.existsSync(projectsAlloyJMKFile)) {
// ! TODO: Refactor with readline or line-reader: https://stackabuse.com/reading-a-file-line-by-line-in-node-js/
if (options.off) {
disableHook()
} else if (options.delete) {
deleteHook()
} else if (!fs.readFileSync(projectsAlloyJMKFile, 'utf8').includes('::PurgeTSS::')) {
addHook()
} else if (fs.readFileSync(projectsAlloyJMKFile, 'utf8').includes(`//${methodCommand}`)) {
enableHook()
} else {
logger.warn(chalk.yellow('Auto-Purging hook already present!'))
}
} else if (!options.off) {
createJMKFile()
}
}
}
module.exports.watchMode = watchMode
// ! Command: copy-fonts
function copyFonts(options) {
if (alloyProject()) {
makeSureFolderExists(projectsFontsFolder)
if (options.vendor && typeof options.vendor === 'string') {
const selected = _.uniq(options.vendor.replace(/ /g, '').split(','))
logger.info('Copying Icon Fonts...')
_.each(selected, vendor => {
copyFont(vendor)
})
} else {
logger.info('Copying Fonts to', chalk.yellow('./app/assets/fonts'), 'folder')
copyFont('fa')
copyFont('mi')
copyFont('ms')
copyFont('f7')
}
if (options.module) {
console.log()
logger.info('Copying Modules to', chalk.yellow('./app/lib'), 'folder')
copyFontLibraries(options)
}
if (options.styles) {
console.log()
logger.info('Copying Styles to', chalk.yellow('./purgetss/styles'), 'folder')
copyFontStyles(options)
}
}
}
module.exports.copyFonts = copyFonts
// ! Command: copy-font-liraries
function copyFontLibraries(options) {
if (alloyProject()) {
makeSureFolderExists(projectsLibFolder)
if (options.vendor && typeof options.vendor === 'string') {
const selected = _.uniq(options.vendor.replace(/ /g, '').split(','))
_.each(selected, vendor => {
copyFontLibrary(vendor)
})
} else {
copyFontLibrary('fa')
copyFontLibrary('mi')
copyFontLibrary('ms')
copyFontLibrary('f7')
}
}
}
// ! Command: copy-font-liraries
function copyFontStyles(options) {
if (alloyProject()) {
makeSureFolderExists(projectsPurgeTSSFolder)
makeSureFolderExists(projectsPurge_TSS_Styles_Folder)
if (options.vendor && typeof options.vendor === 'string') {
const selected = _.uniq(options.vendor.replace(/ /g, '').split(','))
_.each(selected, vendor => {
copyFontStyle(vendor)
})
} else {
copyFontStyle('fa')
copyFontStyle('mi')
copyFontStyle('ms')
copyFontStyle('f7')
}
}
}
function copyModulesLibrary() {
if (alloyProject(true)) {
makeSureFolderExists(projectsLibFolder)
fs.copyFileSync(srcPurgeTSSLibrary, projectsLibFolder + '/purgetss.ui.js')
logger.info(chalk.yellow('purgetss.ui'), 'module copied to', chalk.yellow('./app/lib'), 'folder')
} else if (classicProject(true)) {
makeSureFolderExists(classicProjectLibFolder)
fs.copyFileSync(srcPurgeTSSLibrary, classicProjectLibFolder + '/purgetss.ui.js')
logger.info(chalk.yellow('purgetss.ui'), 'module copied to', chalk.yellow('./Resources/lib'), 'folder')
} else {
logger.info(`Please make sure you are running ${chalk.green('purgetss')} within an Alloy or Classic Project.`)
logger.info(`For more information, visit ${chalk.green('https://purgetss.com')}`)
}
}
module.exports.copyModulesLibrary = copyModulesLibrary
function getFileUpdatedDate(thePath) {
return fs.statSync(thePath).mtime
}
function cleanClasses(uniqueClasses) {
const cleanClassNames = []
uniqueClasses.forEach(classeName => {
cleanClassNames.push(cleanClassNameFn(classeName))
})
return cleanClassNames
}
function createConfigFile() {
if (alloyProject()) {
makeSureFolderExists(projectsPurgeTSSFolder)
makeSureFolderExists(projectsPurge_TSS_Fonts_Folder)
if (fs.existsSync(projectsConfigJS)) {
logger.warn('./purgetss/config.js', chalk.red('file already exists!'))
} else {
fs.copyFileSync(srcConfigFile, projectsConfigJS)
logger.file('./purgetss/config.js')
}
}
}
// ! Command: create
function create(args, options) {
start()
const { exec } = require('child_process')
exec('ti config app.idprefix && ti config app.workspace', (_error, stdout) => {
const results = stdout.split('\n')
const idPrefix = results[0]
const workspace = results[1]
if (idPrefix !== 'app.idprefix not found' && workspace !== '') {
const projectID = `${idPrefix}.${args.name.normalize('NFD').replace(/[\u0300-\u036f]/g, '').split(/ |-|_/).join('').toLowerCase()}`
console.log('')
if (fs.existsSync(`${workspace}/${args.name}`)) {
if (options.force) {
logger.info('Deleting existing project’s folder')
exec(`chown -R $USER "${workspace}/${args.name}" && rm -rf "${workspace}/${args.name}"`, error => {
if (error) return logger.error(error)
createProject(workspace, args.name, projectID, options)
})
} else {
inquirer.prompt([{
type: 'confirm',
name: 'delete',
message: `The folder ‘${args.name}’ already exists. Do you want to delete it?`,
default: false
}]).then(answers => {
if (answers.delete) {
logger.info('Deleting existing project’s folder')
exec(`chown -R $USER "${workspace}/${args.name}" && rm -rf "${workspace}/${args.name}"`, error => {
if (error) return logger.error(error)
createProject(workspace, args.name, projectID, options)
})
} else {
console.log('')
logger.warn(chalk.yellow('Project creation has been canceled!'))
}
})
}
} else {
createProject(workspace, args.name, projectID, options)
}
} else {
console.log('')
logger.error('::Can’t create a Titanium project::')
logger.info('You must have', chalk.green('`app.idprefix`'), 'and', chalk.green('`app.workspace`'), 'configured to create a project with', chalk.green('`Purge TSS`'))
console.log('')
logger.info('Please, set them like this:')
logger.info(chalk.green('ti config app.idprefix'), chalk.yellow("'com.your.reverse.domain'"))
logger.info(chalk.green('ti config app.workspace'), chalk.yellow("'path/to/your/workspace'"))
}
})
}
exports.create = create
// ! Command: shades
function shades(args, options) {
const chroma = require('chroma-js')
const referenceColorFamilies = require('./lib/color-shades/tailwindColors')
const generateColorShades = require('./lib/color-shades/generateColorShades')
const colorFamily = (options.random || !args.hexcode) ? generateColorShades(chroma.random(), referenceColorFamilies) : generateColorShades(args.hexcode, referenceColorFamilies)
if (args.name) colorFamily.name = args.name
else if (options.name) colorFamily.name = options.name
colorFamily.name = colorFamily.name.replace(/'/g, '').replace(/\//g, '').replace(/\s+/g, ' ')
const colorObject = createColorObject(colorFamily, colorFamily.hexcode, options)
const silent = options.tailwind || options.json || options.log
if (alloyProject(silent) && !silent) {
if (options.override) {
if (!configFile.theme.colors) configFile.theme.colors = {}
configFile.theme.colors[colorObject.name] = colorObject.shades
if (configFile.theme.extend.colors) {
if (configFile.theme.extend.colors[colorObject.name]) delete configFile.theme.extend.colors[colorObject.name]
if (Object.keys(configFile.theme.extend.colors).length === 0) delete configFile.theme.extend.colors
}
} else {
if (!configFile.theme.extend.colors) configFile.theme.extend.colors = {}
configFile.theme.extend.colors[colorObject.name] = colorObject.shades
if (configFile.theme.colors) {
if (configFile.theme.colors[colorObject.name]) delete configFile.theme.colors[colorObject.name]
if (Object.keys(configFile.theme.colors).length === 0) delete configFile.theme.colors
}
}
fs.writeFileSync(projectsConfigJS, 'module.exports = ' + cleanDoubleQuotes(configFile, options), 'utf8', err => { throw err })
checkIfColorModule()
logger.info(`${chalk.hex(colorFamily.hexcode).bold(`“${colorFamily.name}”`)} (${chalk.bgHex(colorFamily.hexcode)(colorFamily.hexcode)}) saved in`, chalk.yellow('config.js'))
} else if (options.json) {
logger.info(`${chalk.hex(colorFamily.hexcode).bold(`“${colorFamily.name}”`)} (${chalk.bgHex(colorFamily.hexcode)(colorFamily.hexcode)})\n${JSON.stringify(colorObject, null, 2)}`)
} else {
if (options.tailwind) delete colorObject.shades.default
logger.info(`${chalk.hex(colorFamily.hexcode).bold(`“${colorFamily.name}”`)} (${chalk.bgHex(colorFamily.hexcode)(colorFamily.hexcode)})\n${cleanDoubleQuotes({ colors: { [colorObject.name]: colorObject.shades } }, options)}`)
}
}
exports.shades = shades
function colorModule() {
initIfNotConfig()
const colorModuleConfigFile = require(projectsConfigJS)
makeSureFolderExists(projectsLibFolder)
const mainColors = { ...colorModuleConfigFile.theme.colors, ...colorModuleConfigFile.theme.extend.colors }
fs.writeFileSync(`${projectsLibFolder}/purgetss.colors.js`, 'module.exports = ' + cleanDoubleQuotes(mainColors, {}), 'utf8', err => { throw err })
logger.info(`All colors copied to ${chalk.yellow('lib/purgetss.colors.js')}`)
}
exports.colorModule = colorModule
function checkIfColorModule() {
if (fs.existsSync(`${projectsLibFolder}/purgetss.colors.js`)) colorModule()
}
function cleanDoubleQuotes(configFile, options) {
// eslint-disable-next-line no-control-regex
const regexUnicode = /[^\u0000-\u00FF]/g
if (options.quotes) return JSON.stringify(configFile, null, 2).replace(regexUnicode, match => `\\u${match.charCodeAt(0).toString(16)}`)
const util = require('util')
const inspected = util.inspect(configFile, false, 10)
if (inspected === 'undefined') return '{}'
return inspected.replace(regexUnicode, match => `\\u${match.charCodeAt(0).toString(16)}`)
}
function createColorObject(family, hexcode, options) {
const colors = {}
const name = family.name.toLowerCase().split(' ').join('-')
if (options.json) {
const shades = {}
colors.global = {}
shades[name] = hexcode
family.shades.forEach((shade) => {
shades[`${name}-${shade.number}`] = shade.hexcode
})
colors.global.colors = (options.single) ? { [name]: hexcode } : shades
} else if (options.single) {
colors.name = name
colors.shades = hexcode
} else {
const shades = { default: hexcode }
family.shades.forEach((shade) => {
shades[shade.number] = shade.hexcode
})
colors.name = name
colors.shades = shades
}
return colors
}
function createProject(workspace, argsName, projectID, options) {
const projectName = `"${argsName}"`
const { execSync } = require('child_process')
const projectDirectory = `${workspace}/${projectName}`
const commandExistsSync = require('command-exists').sync
logger.info('Creating a new Titanium project')
execSync(`ti create -t app -p all -n ${projectName} --no-prompt --id ${projectID}`)
execSync(`cd ${projectDirectory} && alloy new && purgetss w && purgetss b`)
if (options.vendor) {
logger.info('Installing Fonts')
execSync(`cd ${projectDirectory} && purgetss il -m -v=${options.vendor}`)
}
if (options.module) {
logger.info(`Installing ${chalk.green('purgetss.ui')}`)
execSync(`cd ${projectDirectory} && purgetss m`)
}
if (options.dependencies) {
logger.info(`Creating a new ${chalk.yellow('package.json')} file`)
execSync(`cd ${projectDirectory} && npm init -y`)
execSync(`cd ${projectDirectory} && echo "/node_modules" >>.gitignore`)
if (commandExistsSync('code')) {
execSync(`cp -R ${path.resolve(projectRoot)}/dist/configs/vscode/ ${projectDirectory}/.vscode`)
}
execSync(`cp ${path.resolve(projectRoot)}/dist/configs/invisible/.editorconfig ${projectDirectory}`)
logger.info(`Installing ${chalk.green('ESLint')}`)
execSync(`cd ${projectDirectory} && npm i -D eslint eslint-config-axway eslint-plugin-alloy`)
execSync(`cp ${path.resolve(projectRoot)}/dist/configs/invisible/.eslintrc.js ${projectDirectory}`)
logger.info(`Installing ${chalk.green('Tailwind CSS')}`)
execSync(`cd ${projectDirectory} && npm i -D tailwindcss@3 && npx tailwindcss init`)
}
finish(`The ${chalk.yellow(`‘${argsName}’`)} project was created successfully in`)
if (commandExistsSync('code')) {
execSync(`cd ${projectDirectory} && code .`)
} else if (commandExistsSync('subl')) {
execSync(`cd ${projectDirectory} && subl .`)
} else {
execSync(`cd ${projectDirectory} && open .`)
}
}
// ! Command: dependencies
function dependencies(options) {
if (alloyProject()) {
const { execSync } = require('child_process')
const commandExistsSync = require('command-exists').sync
if (fs.existsSync(`${cwd}/.gitignore`) && !fs.readFileSync(`${cwd}/.gitignore`).includes('/node_modules')) {
execSync('echo "/node_modules" >>.gitignore')
}
if (!fs.existsSync(`${cwd}/package.json`)) {
logger.info(`Creating a new ${chalk.yellow('package.json')} file`)
execSync(`cd "${cwd}" && npm init -y`)
}
if (commandExistsSync('code')) {
execSync(`cp -R ${path.resolve(projectRoot)}/dist/configs/vscode/ "${cwd}"/.vscode`)
}
execSync(`cp ${path.resolve(projectRoot)}/dist/configs/invisible/.editorconfig "${cwd}"`)
logger.info(`Installing ${chalk.green('ESLint')}`)
execSync(`cd "${cwd}" && npm i -D eslint eslint-config-axway eslint-plugin-alloy`)
execSync(`cp ${path.resolve(projectRoot)}/dist/configs/invisible/.eslintrc.js "${cwd}"`)
logger.info(`Installing ${chalk.green('Tailwind CSS')}`)
execSync(`cd "${cwd}" && npm i -D tailwindcss && npx tailwindcss init`)
logger.info('The dependencies and config files have been installed successfully!')
}
}
module.exports.dependencies = dependencies
// ! Command: build
function build(options) {
if (alloyProject()) {
initIfNotConfig()
buildTailwindBasedOnConfigOptions(options)
buildFontAwesome()
buildFontAwesomeJS()
createDefinitionsFile()
}
}
module.exports.build = build
// ! Command: build-legacy
function buildLegacy() {
if (alloyProject()) {
initIfNotConfig()
buildTailwindLegacy()
buildFontAwesome()
buildFontAwesomeJS()
createDefinitionsFile()
}
}
module.exports.buildLegacy = buildLegacy
// ! Command: Build fonts.tss
function buildFonts(options) {
if (fs.existsSync(projectsPurge_TSS_Fonts_Folder)) {
start()
const files = getFiles(projectsPurge_TSS_Fonts_Folder).filter(file => {
return file.endsWith('.ttf') || file.endsWith('.otf') || file.endsWith('.css') || file.endsWith('.TTF') || file.endsWith('.OTF') || file.endsWith('.CSS')
})
let fontMeta = ''
let fontJS = ''
let fontFamiliesJS = ''
const FontName = require('fontname')
let tssClasses = '// Fonts TSS file generated with Purge TSS\n// https://purgetss.com/docs/commands#build-fonts-command\n'
// ! Process font files
_.each(files, file => {
if (file.endsWith('.ttf') || file.endsWith('.otf') || file.endsWith('.TTF') || file.endsWith('.OTF')) {
fontMeta = FontName.parse(fs.readFileSync(file))[0]
tssClasses += processFontMeta(fontMeta)
const fontFamilyName = fontMeta.postScriptName.replace(/\//g, '')
if (options.fontClassFromFilename) {
tssClasses += `\n'.${getFileName(file)}': { font: { fontFamily: '${fontFamilyName}' } }\n`
} else {
tssClasses += `\n'.${fontFamilyName.toLowerCase()}': { font: { fontFamily: '${fontFamilyName}' } }\n`
}
// ! Copy Font File
makeSureFolderExists(projectsFontsFolder)
const fontExtension = file.split('.').pop()
fs.copyFile(file, `${projectsFontsFolder}/${fontFamilyName}.${fontExtension}`, err => {
if (err) {
throw err
}
})
logger.info('Copying font', `${chalk.yellow(file.split('/').pop())}...`)
}
})
let oneTimeMessage = '\n// Unicode Characters\n// To use your Icon Fonts in Buttons AND Labels each class sets \'text\' and \'title\' properties\n'
// ! Process styles files
_.each(files, file => {
if (file.endsWith('.css') || file.endsWith('.CSS')) {
const cssFile = readCSS(file)
const theFile = file.split('/')
const theCSSFile = theFile.pop()
const prefix = options.iconPrefixFromFilename ? theCSSFile.split('.').shift() : null
let theFolder = theFile.pop() + '/'
if (theFolder === 'fonts/') {
theFolder = ''
}
const theCSSFileName = theFolder + theCSSFile
tssClasses += oneTimeMessage + `\n// ${theCSSFileName}\n`
oneTimeMessage = ''
tssClasses += processFontsCSS(cssFile, prefix)
// ! JavaScript Module
if (options.module || fs.existsSync(`${projectsLibFolder}/purgetss.fonts.js`)) {
fontJS += processFontsJS(cssFile, `\n\t// ${theCSSFileName}`, prefix)
fontFamiliesJS += processFontFamilyNamesJS(cssFile, `\n\t// ${theCSSFileName}`, prefix)
}
// !Done processing stylesheet
logger.info('Processing', `${chalk.yellow(theCSSFileName)}...`)
}
})
if (files.length > 0) {
makeSureFolderExists(projectsPurgeTSSFolder)
makeSureFolderExists(projectsPurge_TSS_Styles_Folder)
fs.writeFileSync(`${cwd}/purgetss/styles/fonts.tss`, tssClasses, { encoding: 'utf8' }, err => {
throw err
})
}
if (fontJS) {
makeSureFolderExists(projectsLibFolder)
let exportIcons = 'const icons = {'
exportIcons += fontJS.slice(0, -1)
exportIcons += '\n}\n'
exportIcons += 'exports.icon = icons\n'
exportIcons += 'exports.icons = icons\n'
exportIcons += '\nconst iconKeys = Object.keys(icons)\n'
exportIcons += '\nconst families = {'
exportIcons += fontFamiliesJS.slice(0, -1)
exportIcons += '\n}\n'
exportIcons += 'exports.family = families\n'
exportIcons += 'exports.families = families\n'
exportIcons += '\n// Helper Functions\n' + fs.readFileSync(path.resolve(projectRoot, './lib/templates/icon-functions.js'), 'utf8')
fs.writeFileSync(`${projectsLibFolder}/purgetss.fonts.js`, exportIcons, { encoding: 'utf8' }, err => {
throw err
})
logger.info(`${chalk.yellow('./app/lib/purgetss.fonts.js')} file created!`)
} else if (fs.existsSync(`${projectsLibFolder}/purgetss.fonts.js`)) {
fs.unlinkSync(`${projectsLibFolder}/purgetss.fonts.js`)
}
if (files.length > 0) {
createDefinitionsFile()
console.log()
finish(`Finished building ${chalk.yellow('fonts.tss')} in`)
} else {
logger.info('No fonts found in', chalk.yellow('./purgetss/fonts'), 'folder!')
}
} else {
logger.info(`Add font and style files to ${chalk.yellow('./purgetss/fonts')} folder and run this command again!`)
}
}
module.exports.buildFonts = buildFonts
function buildFontAwesome() {
if (fs.existsSync(srcFA_Beta_CSSFile)) {
processFontAwesomeTSS(srcFA_Beta_CSSFile, srcFA_Beta_TemplateTSS, srcFA_Beta_ResetTSS, srcFA_Beta_FontFamilies, srcFA_Beta_Web_Fonts_Folder)
} else if (fs.existsSync(srcFA_Pro_CSS)) {
processFontAwesomeTSS(srcFA_Pro_CSS, srcFA_ProTemplateTSS_File, srcFA_ProReset_TSS_File, srcFA_ProFontFamilies, srcFA_Pro_Web_Fonts_Folder)
} else if (fs.existsSync(srcFA_Pro_CSS_Alt)) {
processFontAwesomeTSS(srcFA_Pro_CSS_Alt, srcFA_ProTemplateTSS_File, srcFA_ProReset_TSS_File, srcFA_ProFontFamilies, srcFA_Pro_Web_Fonts_Folder_Alt)
}
}
function processFontAwesomeTSS(CSSFile, templateTSS, resetTSS, fontFamilies, webFonts) {
readCSS(CSSFile, (err, data) => {
if (err) throw err
let tssClasses = fs.readFileSync(path.resolve(projectRoot, templateTSS), 'utf8') + '\n'
tssClasses += fs.readFileSync(path.resolve(projectRoot, resetTSS), 'utf8') + '\n'
tssClasses += processFontawesomeStyles(data)
fs.writeFileSync(projectsFA_TSS_File, tssClasses, err2 => {
throw err2
})
logger.file('./purgetss/styles/fontawesome.tss')
makeSureFolderExists(projectsFontsFolder)
copyProFonts(fontFamilies, webFonts)
})
}
function buildFontAwesomeJS() {
if (fs.existsSync(srcFA_Beta_CSSFile)) {
processFontAwesomeJS(srcFA_Beta_CSSFile, './lib/templates/fontawesome/beta-template.js')
} else if (fs.existsSync(srcFA_Pro_CSS)) {
processFontAwesomeJS(srcFA_Pro_CSS, './lib/templates/fontawesome/pro-template.js')
}
}
function processFontawesomeStyles(data) {
let convertedTSSClasses = ''
const rules = _.map(data.stylesheet.rules, rule => {
// Without Duotones
if (rule.type === 'rule' && rule.selectors[0].includes(':before') && !rule.selectors[0].includes('.fad')) {
return {
selector: rule.selectors[0].replace(':before', '').replace(':', ''),
property: ('0000' + rule.declarations[0].value.replace('\"\\', '').replace('\"', '')).slice(-4)
}
}
})
_.each(rules, rule => {
if (rule) {
convertedTSSClasses += `'${rule.selector}': { text: '\\u${rule.property}', title: '\\u${rule.property}' }\n`
}
})
return convertedTSSClasses
}
function processFontAwesomeJS(CSSFile, faJS) {
readCSS(CSSFile, (err, data) => {
if (err) throw err
const rules = _.map(data.stylesheet.rules, rule => {
if (rule.type === 'rule' && rule.selectors[0].includes(':before') && !rule.selectors[0].includes('.fad')) {
return {
selector: rule.selectors[0].replace(':before', '').replace('.', '').replace(':', ''),
property: ('0000' + rule.declarations[0].value.replace('\"\\', '').replace('\"', '')).slice(-4)
}
}
})
let fontAwesomeContent = fs.readFileSync(path.resolve(__dirname, faJS), 'utf8')
fontAwesomeContent += '\n' + fs.readFileSync(path.resolve(__dirname, './lib/templates/icon-functions.js'), 'utf8')
let exportIcons = '\nconst icons = {\n'
_.each(rules, rule => {
if (rule) {
exportIcons += `\t'${prettifyFontName(rule.selector, 'fa')}': '\\u${rule.property}',\n`
}
})
exportIcons += '}\n'
exportIcons += 'exports.icons = icons\n'
exportIcons += '\nconst iconKeys = Object.keys(icons)\n'
fontAwesomeContent += exportIcons
makeSureFolderExists(projectsLibFolder)
fs.writeFileSync(projectsFontAwesomeJS, fontAwesomeContent, err2 => {
throw err2
})
logger.file('./app/lib/fontawesome.js')
})
}
function copyFreeFonts() {
fs.copyFile(srcFonts_Folder + '/FontAwesome6Brands-Regular.ttf', projectsFontsFolder + '/FontAwesome6Brands-Regular.ttf', callback)
fs.copyFile(srcFonts_Folder + '/FontAwesome6Free-Regular.ttf', projectsFontsFolder + '/FontAwesome6Free-Regular.ttf', callback)
fs.copyFile(srcFonts_Folder + '/FontAwesome6Free-Solid.ttf', projectsFontsFolder + '/FontAwesome6Free-Solid.ttf', callback)
logger.warn(' - Font Awesome Free')
}
function copyProFonts(fontFamilies, webFonts) {
_.each(fontFamilies, (dest, src) => {
if (copyFile(`${webFonts}/${src}`, dest)) {
logger.warn(` - ${dest} Font copied to`, chalk.yellow('./app/assets/fonts'), 'folder')
}
})
}
function copyMaterialIconsFonts() {
// Material Icons Font
const fontFamilies = [
'MaterialIcons-Regular.ttf',
'MaterialIconsOutlined-Regular.otf',
'MaterialIconsRound-Regular.otf',
'MaterialIconsSharp-Regular.otf',
'MaterialIconsTwoTone-Regular.otf'
]
_.each(fontFamilies, familyName => {
copyFile(`${srcFonts_Folder}/${familyName}`, familyName)
})
logger.warn(' - Material Icons')
}
function copyMaterialSymbolsFonts() {
// Material Symbols Icons Font
const fontFamilies = [
'MaterialSymbolsOutlined-Regular.ttf',
'MaterialSymbolsRounded-Regular.ttf',
'MaterialSymbolsSharp-Regular.ttf'
]
_.each(fontFamilies, familyName => {
copyFile(`${srcFonts_Folder}/${familyName}`, familyName)
})
logger.warn(' - Material Symbols')
}
function copyFramework7IconsFonts() {
// Framework7 Font
copyFile(srcFonts_Folder + '/Framework7-Icons.ttf', 'Framework7-Icons.ttf')
logger.warn(' - Framework 7')
}
function processFontsCSS(data, prefix) {
const rules = getRules(data)
const fontsPrefix = findPrefix(rules)
let processedRules = ''
_.each(rules, rule => {
if (rule) {
if (prefix) {
processedRules += `'${rule.selector.replace(fontsPrefix, prefix)}': { text: '\\u${rule.property}', title: '\\u${rule.property}' }\n`
} else {
processedRules += `'${rule.selector}': { text: '\\u${rule.property}', title: '\\u${rule.property}' }\n`
}
}
})
return processedRules
}
function processFontsJS(data, fontFamily = '', prefix) {
const rules = getRules(data)
let exportIcons = '\n'
let fontsPrefix = findPrefix(rules)
_.each(rules, rule => {
if (rule) {
exportIcons += `\t\t'${prettifyFontName(rule.selector.replace('.', ''), fontsPrefix)}': '\\u${rule.property}',\n`
}
})
if (prefix) {
fontsPrefix = prefix
} else if (fontsPrefix === undefined) {
fontsPrefix = fontFamily
}
return `${fontFamily}\n\t'${_.camelCase(fontsPrefix)}': {${exportIcons}\t},`
}
function processFontFamilyNamesJS(data, fontFamily = '', prefix) {
const rules = getRules(data)
let fontsPrefix = prefix ?? findPrefix(rules)
if (fontsPrefix === undefined) {
fontsPrefix = fontFamily
}
return `${fontFamily}\n\t'${_.camelCase(fontsPrefix)}': '${getFontFamily(data)}',`
}
function processFontMeta(fontMeta) {
let fontMetaString = `\n/**\n * ${fontMeta.fullName}`
fontMetaString += `\n * ${fontMeta.version}`
if (fontMeta.urlVendor) {
fontMetaString += `\n * ${fontMeta.urlVendor}`
}
// if (fontMeta.description) {
// fontMetaString += `\n * Description: ${fontMeta.description}`;
// }
if (fontMeta.designer) {
fontMetaString += `\n * ${fontMeta.designer}`
if (fontMeta.urlDesigner) {
fontMetaString += ` - ${fontMeta.urlDesigner}`
}
}
if (fontMeta.copyright) {
fontMetaString += `\n * ${fontMeta.copyright}`
}
if (fontMeta.trademark) {
fontMetaString += `\n * ${fontMeta.trademark}`
}
if (fontMeta.licence) {
fontMetaString += `\n * ${fontMeta.licence.split('\n')[0]}`
if (fontMeta.licenceURL) {
fontMetaString += ` - ${fontMeta.licenceURL}`
}
}
fontMetaString += '\n */'
return fontMetaString
}
function getFiles(dir) {
return fs.readdirSync(dir).flatMap((item) => {
const thePath = `${dir}/${item}`
if (fs.statSync(thePath).isDirectory()) {
return getFiles(thePath)
}
return thePath
})
}
function getFileName(file) {
return file.split('/').pop().split('.').shift().replace(/ /g, '-').toLowerCase()
}
function getRules(data) {
return _.map(data.stylesheet.rules, rule => {
if (rule.type === 'rule' && rule.declarations[0].property === 'content' && rule.selectors[0].includes('before')) {
return {
selector: '.' + rule.selectors[0].replace('.', '').replace('::before', '').replace(':before', ''),
property: ('0000' + rule.declarations[0].value.replace('\"\\', '').replace('\"', '').replace('\'\\', '').replace('\'', '')).slice(-4)
}
}
}).filter(rule => rule)
}
function getFontFamily(data) {
return _.map(data.stylesheet.rules, rule => {
if (rule.type === 'font-face') {
let something = rule.declarations.filter(declaration => declaration.property === 'font-family').map(declaration => declaration.value)[0]
something = something.replace(/['"](.*?)['"]/g, (_match, p1) => p1)
return something
}
}).filter(rule => rule)
}
function findPrefix(rules) {
const arrayOfRules = rules.map(function (item) {
return item.selector.replace('.', '').split('-')
})
let firstPrefix = ''
let firstCounter = 0
let secondPrefix = ''
let secondCounter = 0
let thirdPrefix = ''
let thirdCounter = 0
_.each(arrayOfRules, item => {
if (item[0] !== firstPrefix) {
firstPrefix = item[0]
firstCounter++
}
if (item.length > 1 && secondPrefix !== item[1]) {
secondPrefix = item[1]
secondCounter++
}
if (item.length > 2 && thirdPrefix !== item[2]) {
thirdPrefix = item[2]
thirdCounter++
}
})
if (firstCounter === 1 && secondCounter === 1 && thirdCounter === 1) {
return `${firstPrefix}-${secondPrefix}-${thirdPrefix}`
} else if (firstCounter === 1 && secondCounter === 1) {
return `${firstPrefix}-${secondPrefix}`
} else if (firstCounter === 1) {
return `${firstPrefix}`
}
}
// ! Purge Fonts
function purgeFonts(uniqueClasses, cleanUniqueClasses) {
if (fs.existsSync(`${cwd}/purgetss/styles/fonts.tss`)) {
let purgedClasses = '\n// Font Styles\n'
purgedClasses += purgeFontIcons(`${cwd}/purgetss/styles/fonts.tss`, uniqueClasses, 'Purging Font styles...', cleanUniqueClasses, [])
return (purgedClasses === '\n// Font Styles\n') ? '' : purgedClasses
}
return ''
}
function prettifyFontName(str, prefix) {
const temp = str.replace(/_/g, '-').replace(prefix, '').replace(/\s/g, '').split('-')
const withoutPrefix = []
let i = 1
if (prefix === undefined) {
i = 0
}
for (i; i < temp.length; i++) {
withoutPrefix.push(temp[i].charAt(0).toUpperCase() + temp[i].slice(1))
}
const pretty = withoutPrefix.join('').replace(':', '')
return pretty.replace(/^.{1}/g, pretty[0].toLowerCase())
}
// ! Helper Functions
function findMissingClasses(tempPurged) {
// ! Get Styles from App - Minus `app.tss`
_.each(getFiles(`${cwd}/app/styles`).filter(file => file.endsWith('.tss') && !file.endsWith('app.tss') && !file.endsWith('_app.tss')), file => {
tempPurged += '\n' + fs.readFileSync(file, 'utf8')
})
// ! Get Styles from Widgets
if (configOptions.widgets && fs.existsSync(`${cwd}/app/widgets/`)) {
_.each(getFiles(`${cwd}/app/widgets`).filter(file => file.endsWith('.tss')), file => {
tempPurged += '\n' + fs.readFileSync(file, 'utf8')
})
}
// ! Get Views from Themes
if (fs.existsSync(`${cwd}/app/themes/`)) {
_.each(getFiles(`${cwd}/app/themes`).filter(file => file.endsWith('.tss')), file => {
tempPurged += '\n' + fs.readFileSync(file, 'utf8')
})
}
if (configOptions.safelist) {
_.each(configOptions.safelist, safe => {
tempPurged += safe + '\n'
})
}
const classesFromXmlFiles = getClassesOnlyFromXMLFiles().filter(item => !tempPurged.includes(item))
let classesFromJsFiles = []
const controllerPaths = getControllerPaths()
_.each(controllerPaths, controllerPath => {
const data = fs.readFileSync(controllerPath, 'utf8')
if (data) classesFromJsFiles.push(processControllers(data))
})
const reservedWords = 'Alloy.isTablet Alloy.isHandheld ? ,'
classesFromJsFiles = [...new Set([...classesFromJsFiles.flat().filter(item => !reservedWords.includes(item))])]
classesFromJsFiles = classesFromJsFiles.filter(item => !reservedWords.includes(item))
return [...new Set([...classesFromJsFiles.filter(item => !tempPurged.includes(item)), ...classesFromXmlFiles])]
}
function addHook() {
logger.warn(chalk.green('Adding Auto-Purging hook!'))
const originalJMKFile = fs.readFileSync(projectsAlloyJMKFile, 'utf8')
if (originalJMKFile.includes('pre:compile')) {
const updatedJMKFile = []
originalJMKFile.split(/\r?\n/).forEach((line) => {
if (line.includes('pre:compile')) {
line += `\n${methodCommand}`
}
updatedJMKFile.push(line)
})
saveFile(projectsAlloyJMKFile, updatedJMKFile.join('\n'))
} else {
const alloyJMKTemplate = fs.readFileSync(srcJMKFile, 'utf8')
const updatedJMKFile = []
alloyJMKTemplate.split(/\r?\n/).forEach((line) => {
if (line.includes('pre:compile')) {
line += `\n${methodCommand}`
}
updatedJMKFile.push(line)
})
fs.appendFileSync(projectsAlloyJMKFile, '\n' + updatedJMKFile.join('\n'))
}
}
function disableHook() {
const updatedJMKFile = []
const originalJMKFile = fs.readFileSync(projectsAlloyJMKFile, 'utf8')
const purgeCmdPresent = (originalJMKFile.includes('::PurgeTSS::'))
if (purgeCmdPresent) {
originalJMKFile.split(/\r?\n/).forEach((line) => {
if (!line.includes('::PurgeTSS::')) {
updatedJMKFile.push(line)
} else if (!line.includes('//')) {
updatedJMKFile.push(`\t//${line}`)
logger.warn(chalk.yellow('Auto-Purging hook disabled!'))
} else {
updatedJMKFile.push(line)
logger.warn(chalk.red('Auto-Purging hook already disabled!'))
}
})
saveFile(projectsAlloyJMKFile, updatedJMKFile.join('\n'))
}
}
function deleteHook() {
const updatedJMKFile = []
const originalJMKFile = fs.readFileSync(projectsAlloyJMKFile, 'utf8')
const purgeCmdPresent = (originalJMKFile.includes('::PurgeTSS::'))
if (purgeCmdPresent) {
originalJMKFile.split(/\r?\n/).forEach((line) => {
if (!line.includes('::PurgeTSS::')) {
updatedJMKFile.push(line)
} else {
logger.warn(chalk.red('Auto-Purging hook deleted!'))
}
})
saveFile(projectsAlloyJMKFile, updatedJMKFile.join('\n'))
}
}
function enableHook() {
const updatedJMKFile = []
const originalJMKFile = fs.readFileSync(projectsAlloyJMKFile, 'utf8')
originalJMKFile.split(/\r?\n/).forEach((line) => {
if (line.includes('::PurgeTSS::')) {
logger.warn(chalk.green('Auto-Purging hook enabled!'))
line = line.replace(/\/\/\t/g, '')
}
updatedJMKFile.push(line)
saveFile(projectsAlloyJMKFile, updatedJMKFile.join('\n'))
})
}
function initIfNotConfig() {
if (!fs.existsSync(projectsConfigJS)) {
createConfigFile()
}
}
function makeSureFileExists(file) {
if (!fs.existsSync(file)) {
fs.writeFileSync(file, '')
return true
}
}
function makeSureFolderExists(folder) {
if (!fs.existsSync(folder)) {
fs.mkdirSync(folder)
}
}
function copyFile(src, dest) {
if (fs.existsSync(src)) {
fs.copyFile(src, `${projectsFontsFolder}/${dest}`, callback)
return true
}
}
function getViewPaths() {
const viewPaths = []
// ! Parse Views from App
viewPaths.push(...glob.sync(`${cwd}/app/views/**/*.xml`))
// ! Parse Views from Widgets
if (configOptions.widgets && fs.existsSync(`${cwd}/app/widgets/`)) {
viewPaths.push(...glob.sync(`${cwd}/app/widgets/**/views/*.xml`))
}
// ! Parse Views from Themes
if (fs.existsSync(`${cwd}/app/themes/`)) {
viewPaths.push(...glob.sync(`${cwd}/app/themes/**/views/*.xml`))
}
return viewPaths
}
function getControllerPaths() {
const controllerPaths = []
// ! Parse Controllers from App
controllerPaths.push(...glob.sync(`${cwd}/app/controllers/**/*.js`))
// ! Parse Controllers from Widgets
if (configOptions.widgets && fs.existsSync(`${cwd}/app/widgets/`)) {
controllerPaths.push(...glob.sync(`${cwd}/app/widgets/**/controllers/*.js`))
}
// ! Parse Controllers from Themes
if (fs.existsSync(`${cwd}/app/themes/`)) {
controllerPaths.push(...glob.sync(`${cwd}/app/themes/**/controllers/*.js`))
}
return controllerPaths
}
function getClassesOnlyFromXMLFiles() {
const allClasses = []
const viewPaths = getViewPaths()
_.each(viewPaths, viewPath => allClasses.push(extractClassesOnly(fs.readFileSync(viewPath, 'utf8'), viewPath)))
const uniqueClasses = []
_.each(_.uniq(_.flattenDeep(allClasses)).sort(), uniqueClass => {
if (filterCharacters(uniqueClass)) uniqueClasses.push(uniqueClass)
})
return uniqueClasses.sort()
}
function getUniqueClasses() {
localStart()
const allClasses = []
const viewPaths = getViewPaths()
_.each(viewPaths, viewPath => {
const file = fs.readFileSync(viewPath, 'utf8')
if (file) allClasses.push((configFile.purge.mode === 'all') ? file.match(/[^<>"'`\s]*[^<>"'`\s:]/g) : extractClasses(file, viewPath))
})
const controllerPaths = getControllerPaths()
_.each(controllerPaths, controllerPath => {
const data = fs.readFileSync(controllerPath, 'utf8')
if (data) allClasses.push(processControllers(data))
})
if (configOptions.safelist) _.each(configOptions.safelist, safe => allClasses.push(safe))
const uniqueClasses = []
_.each(_.uniq(_.flattenDeep(allClasses)).sort(), uniqueClass => {
if (filterCharacters(uniqueClass)) uniqueClasses.push(uniqueClass)
})
localFinish('Get Unique Classes')
return uniqueClasses.sort()
}
function extractWordsFromLine(line) {
const patterns = [
{
// apply: 'classes'
regex: /apply:\s*'([^']+)'/,
process: match => match[1].split(/\s+/)
},
{
// classes: ['class1', 'class2'] o classes: ['class1 class2']
regex: /classes:\s*\[([^\]]+)\]/,
process: match => match[1].split(',').map(item => item.trim().replace(/['"]/g, ''))
},
{
// classes: 'class1 class2'
regex: /classes:\s*'([^']+)'/,
process: match => match[1].split(/\s+/)
}
]
// Process simple patterns
const words = patterns.reduce((acc, { regex, process }) => {
const match = regex.exec(line)
return match ? [...acc, ...process(match)] : acc
}, [])
// Process addClass, removeClass, resetClass
const classFunctionRegex = /(?:\.\w+Class|resetClass)\([^,]+,\s*(?:'([^']+)'|\[([^\]]+)\])/g
let classFunctionMatch
while ((classFunctionMatch = classFunctionRegex.exec(line)) !== null) {
const content = classFunctionMatch[1] || classFunctionMatch[2]
if (content) {
const classes = content.includes(',')
? content.split(',').map(item => item.trim().replace(/['"]/g, ''))
: content.replace(/['"]/g, '').split(/\s+/)
words.push(...classes)
}
}
// Matching generic arrays
// const genericArrayRegex = /(\w+:\s*\[([^\]]+)\])/g
// let genericArrayMatch
// while ((genericArrayMatch = genericArrayRegex.exec(line)) !== null) {
// const genericArrayContent = genericArrayMatch[2]
// const genericArray = genericArrayContent.split(',').map(item => item.trim().replace(/['"]/g, ''))
// words = words.concat(genericArray)
// }
return words
}
function processControllers(data) {
const allWords = []
const lines = data.split(/\r?\n/)
lines.forEach(line => {
const words = extractWordsFromLine(line)
if (words.length > 0) {
allWords.push(...words)
}
})
return allWords
}
function filterCharacters(uniqueClass) {
if (uniqueClass.length === 1 && !/^[a-zA-Z_]/.test(uniqueClass)) {
return false
}
return uniqueClass !== '(' && isNaN(uniqueClass.charAt(0)) &&
!uniqueClass.startsWith('--') &&
!uniqueClass.startsWith(',') &&
!uniqueClass.startsWith('!') &&
!uniqueClass.startsWith('(') &&
!uniqueClass.startsWith('[') &&
!uniqueClass.startsWith('{') &&
!uniqueClass.startsWith('/') &&
!uniqueClass.startsWith('\\') &&
!uniqueClass.startsWith('#') &&
!uniqueClass.startsWith('$') &&
!uniqueClass.startsWith('Ti.') &&
!uniqueClass.includes('\\n') &&
!uniqueClass.includes('=') &&
!uniqueClass.includes('http') &&
!uniqueClass.includes('L(') &&
!uniqueClass.includes('www') &&
// !uniqueClass.includes(')') &&
!uniqueClass.endsWith(',') &&
!uniqueClass.endsWith('.') &&
!uniqueClass.endsWith('/')
}
function getBaseValues(defaultTheme) {
const defaultColors = require('tailwindcss/colors')
const defaultThemeWidth = defaultTheme.width({ theme: () => (defaultTheme.spacing) })
const defaultThemeHeight = defaultTheme.height({ theme: () => (defaultTheme.spacing) })
removeDeprecatedColors(defaultColors)
// !Prepare values
const tiResets = { full: '100%' }
const allWidthsCombined = (configFile.theme.spacing) ? { ...configFile.theme.spacing, ...tiResets } : { ...defaultThemeWidth }
const allHeightsCombined = (configFile.theme.spacing) ? { ...configFile.theme.spacing, ...tiResets } : { ...defaultThemeHeight }
const allSpacingCombined = (configFile.theme.spacing) ? { ...configFile.theme.spacing, ...tiResets } : { ...defaultThemeWidth, ...defaultThemeHeight }
const themeOrDefaultValues = {
width: configFile.theme.width ?? allWidthsCombined,
height: configFile.theme.height ?? allHeightsCombined,
spacing: configFile.theme.spacing ?? allSpacingCombined,
fontSize: configFile.theme.spacing ?? defaultTheme.fontSize,
colors: configFile.theme.colors ?? { transparent: 'transparent', ...defaultColors }
}
// ! Remove unnecessary values
removeFitMaxMin(themeOrDefaultValues)
// ! Merge with extend values
const base = {
width: { ...themeOrDefaultValues.spacing, ...configFile.theme.extend.spacing, ...themeOrDefaultValues.width, ...configFile.theme.extend.width },
height: { ...themeOrDefaultValues.spacing, ...configFile.theme.extend.spacing, ...themeOrDefaultValues.height, ...configFile.theme.extend.height },
colors: { ...themeOrDefaultValues.colors, ...configFile.theme.extend.colors },
spacing: { ...themeOrDefaultValues.spacing, ...configFile.theme.extend.spacing },
fontSize: { ...themeOrDefaultValues.fontSize, ...configFile.theme.extend.spacing, ...configFile.theme.extend.fontSize },
columns: { 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 12: 12 },
delay: { 0: '0ms', 25: '25ms', 50: '50ms', 250: '250ms', 350: '350ms', 400: '400ms', 450: '450ms', 600: '600ms', 800: '800ms', 900: '900ms', 2000: '2000ms', 3000: '3000ms', 4000: '