From f08e8804c5faf478407353fb38072fe4c60f3122 Mon Sep 17 00:00:00 2001 From: Sarmaged <3858245+Sarmaged@users.noreply.github.com> Date: Fri, 16 May 2025 13:10:25 +0300 Subject: [PATCH] release: version 2025.5.16 --- .gitignore | 1 + .npmignore | 4 +- .prettierrc.json | 9 +++++ main.js | 88 ++++++++++++++++++++++------------------- package.json | 8 +++- utils/config.js | 42 ++++++++++++++++---- utils/convert.js | 79 +++++++++++++++++++++--------------- utils/fs.js | 20 +++++----- utils/getProjectRoot.js | 31 +++++++++++++++ utils/palette.js | 11 ++++-- utils/tools.js | 2 +- yarn.lock | 5 +++ 12 files changed, 203 insertions(+), 97 deletions(-) create mode 100644 .prettierrc.json create mode 100644 utils/getProjectRoot.js diff --git a/.gitignore b/.gitignore index efb7b78..66f8c5b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ node_modules/ .DS_Store +!.prettierrc.json !.editorconfig !CONTRIBUTING.md !LICENSE diff --git a/.npmignore b/.npmignore index fd23243..8eae855 100644 --- a/.npmignore +++ b/.npmignore @@ -1,10 +1,10 @@ * +!CONTRIBUTING.md +!LICENSE !main.js !package.json !README.md -!LICENSE -!CONTRIBUTING.md !generators/ !generators/** diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..3274f20 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,9 @@ +{ + "semi": false, + "singleQuote": true, + "printWidth": 120, + "trailingComma": "all", + "arrowParens": "avoid", + "bracketSpacing": true, + "htmlWhitespaceSensitivity": "ignore" +} diff --git a/main.js b/main.js index 1232e2c..5294c29 100755 --- a/main.js +++ b/main.js @@ -1,7 +1,7 @@ #!/usr/bin/env node -import fs from 'fs' -import path from 'path' +import { readdirSync, existsSync, readFileSync, writeFileSync } from 'fs' +import { resolve, join } from 'path' // packages import chalk from 'chalk' @@ -12,10 +12,17 @@ import { config } from './utils/config.js' import { colors } from './utils/colors.js' import { RGBToHSL, HSLToRGB } from './utils/convert.js' import { handleSingleColor, handlePaletteRanges } from './utils/palette.js' +import { getProjectRoot } from './utils/getProjectRoot.js' + +const projectRoot = getProjectRoot() async function main() { console.clear() - console.log('šŸŽØ Welcome to Style-Forge Generator CLI') + + console.log(` + ${chalk.hex('#00ffff').bold(' ⚔ STYLE-FORGE.COLORS ⚔ ')} + ${chalk.hex('#f0f').bold('āš›ļø Atomic HSL-based generator')} + `) const { mode } = await inquirer.prompt([ { @@ -38,7 +45,7 @@ async function main() { process.exit(0) } - let format = config.defaultFormat || 'HSL' + let format = config.modules.colors.defaultFormat || 'HSL' if (mode === 'šŸŽØ Generate CSS file by') { const formatAnswer = await inquirer.prompt([ @@ -47,7 +54,7 @@ async function main() { name: 'format', message: 'Select input format:', choices: ['HSL', 'RGB', 'HEX'], - default: config.defaultFormat || 'HSL', + default: config.modules.colors.defaultFormat || 'HSL', }, ]) format = formatAnswer.format @@ -84,7 +91,7 @@ async function main() { type: 'input', name: 'hex', message: 'HEX (e.g. #ff00aa):', - validate: v => /^#?[0-9A-Fa-f]{6}$/.test(v) + validate: v => /^#?[0-9A-Fa-f]{6}$/.test(v), }, ]) const hex = input.hex.replace('#', '') @@ -101,17 +108,23 @@ async function main() { await handleSingleColor(H, S, L) } if (mode === 'šŸ“ Generate from paletteRanges (from config)') { - if (config.paletteRanges && Array.isArray(config.paletteRanges)) { - await handlePaletteRanges(config.paletteRanges) + if (config.modules.colors.paletteRanges && Array.isArray(config.modules.colors.paletteRanges)) { + await handlePaletteRanges(config.modules.colors.paletteRanges) } else { - console.log('āš ļø No paletteRanges found in config.') + console.log('āš ļø No paletteRanges found in config.modules.colors.paletteRanges') } } if (mode === '🌈 Generate full palette for H / S range') { const { h, s, step } = await inquirer.prompt([ { type: 'input', name: 'h', message: 'Hue (0–360):', validate: v => !isNaN(v) && v >= 0 && v <= 360 }, { type: 'input', name: 's', message: 'Saturation (0–100):', validate: v => !isNaN(v) && v >= 0 && v <= 100 }, - { type: 'input', name: 'step', message: 'Step for Lightness (1–100):', default: 5, validate: v => !isNaN(v) && v > 0 && v <= 100 } + { + type: 'input', + name: 'step', + message: 'Step for Lightness (1–100):', + default: 5, + validate: v => !isNaN(v) && v > 0 && v <= 100, + }, ]) const H = parseInt(h) const S = parseInt(s) @@ -153,24 +166,23 @@ async function main() { } } if (mode === '🧩 Combine CSS files') { - const outputDir = path.resolve(config.outputDir) - const srcDir = path.resolve(config.outputDir, config.atomicSubDir) + const outputDir = resolve(projectRoot, config.output.dir, config.modules.colors.dir) + const srcDir = resolve(outputDir, config.modules.colors.atomicSubDir) - if (!fs.existsSync(srcDir)) { + if (!existsSync(srcDir)) { console.log(`āŒ Folder "${srcDir}" not found.`) return } const replace = x => x.replace('.css', '').split('.').map(Number) - const allFiles = fs - .readdirSync(srcDir) - .filter(name => name.endsWith('.css') && !name.startsWith('combined')) - .sort((a, b) => { - const [ha, sa, la] = replace(a) - const [hb, sb, lb] = replace(b) - return ha - hb || sa - sb || la - lb - }) + const allFiles = readdirSync(srcDir) + .filter(name => name.endsWith('.css') && !name.startsWith('combined')) + .sort((a, b) => { + const [ha, sa, la] = replace(a) + const [hb, sb, lb] = replace(b) + return ha - hb || sa - sb || la - lb + }) if (allFiles.length === 0) { console.log(`āŒ No .css files found in "${srcDir}" — nothing to combine.`) @@ -207,11 +219,10 @@ async function main() { validate: input => { if (!input.trim()) return true const indexes = input.split(',').map(i => parseInt(i.trim(), 10)) - const isValid = indexes.length === selected.length && - indexes.every(i => i > 0 && i <= selected.length) + const isValid = indexes.length === selected.length && indexes.every(i => i > 0 && i <= selected.length) return isValid || 'Enter valid sequence using all indexes (e.g. 2,1,3)' - } - } + }, + }, ]) let orderedFiles = selected @@ -232,31 +243,26 @@ async function main() { }, ]) - const targetPath = path.join(outputDir, `${name}.css`) + const targetPath = join(outputDir, `${name}.css`) let combined = '' for (const file of orderedFiles) { - const content = fs.readFileSync(path.join(srcDir, file), 'utf-8') + const content = readFileSync(join(srcDir, file), 'utf-8') combined += `/* ${file} */\n` + content + '\n\n' } - fs.writeFileSync(targetPath, combined) + writeFileSync(targetPath, combined) console.log(`āœ… Combined ${orderedFiles.length} files into ${name}.css`) console.log(`šŸ“¦ Size: ${(Buffer.byteLength(combined) / 1024).toFixed(2)} KB`) } } -(async function loop() { - while (true) { - try { - await main() - } catch (err) { - if (err?.isTtyError || err?.message?.includes('SIGINT') || err.name === 'ExitPromptError') { - console.log('\nšŸ‘‹ Bye!') - break - } - console.error('āŒ Unhandled error:', err) - break - } +main().catch(err => { + if (err?.isTtyError || err?.message?.includes('SIGINT') || err.name === 'ExitPromptError') { + console.log('\nšŸ‘‹ Bye!\n') + process.exit(0) } -})() + + console.error('\nāŒ Unhandled error:', err) + process.exit(1) +}) diff --git a/package.json b/package.json index d37b358..5a7b885 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "style-forge.colors", - "version": "2025.5.13", + "version": "2025.5.16", "description": "Style-Forge Colors: atomic HSL-based color palette generator and CSS module for scalable design systems.", "type": "module", "bin": { @@ -38,5 +38,11 @@ "homepage": "https://style-forge.github.io/colors/", "publishConfig": { "registry": "https://registry.npmjs.org/" + }, + "engines": { + "node": ">=18" + }, + "devDependencies": { + "prettier": "2.8.8" } } diff --git a/utils/config.js b/utils/config.js index f384beb..d056209 100644 --- a/utils/config.js +++ b/utils/config.js @@ -2,12 +2,13 @@ import { existsSync, writeFileSync, readFileSync } from 'fs' import { resolve } from 'path' import stringify from 'json-stringify-pretty-compact' -const configPath = resolve('style-forge.colors.config.json') +// Utils +import { getProjectRoot } from './getProjectRoot.js' -const defaultConfig = { +const defaultColorsConfig = { + atomicSubDir: 'single', defaultFormat: 'HSL', - outputDir: 'src/assets/styles/colors', - atomicSubDir: "single", + dir: 'colors', paletteRanges: [ [0, 0, [0]], [240, 100, [50]], @@ -17,12 +18,37 @@ const defaultConfig = { [197, 71, [73]], [300, 76, [72]], [60, 100, [50]], - ] + ], } +const configPath = resolve(getProjectRoot(), 'styleforgerc.json') + if (!existsSync(configPath)) { - writeFileSync(configPath, stringify(defaultConfig)) - console.log('šŸ†• Created style-forge.colors.config.json with default settings') + writeFileSync( + configPath, + stringify({ + output: { + dir: 'src/assets/styles', + name: 'style-forge', + }, + modules: { + colors: defaultColorsConfig, + }, + }), + ) } -export const config = JSON.parse(readFileSync(configPath, 'utf-8')) +const config = JSON.parse(readFileSync(configPath, 'utf-8')) + +config.modules = config.modules || {} +config.modules.colors = config.modules.colors || {} + +const target = config.modules.colors + +for (const [key, value] of Object.entries(defaultColorsConfig)) { + if (!(key in target)) target[key] = value +} + +writeFileSync(configPath, stringify(config, { maxLength: 100 })) + +export { config } diff --git a/utils/convert.js b/utils/convert.js index 98c6cdd..e7b102c 100644 --- a/utils/convert.js +++ b/utils/convert.js @@ -1,52 +1,61 @@ export function HSLToRGB([H, S, L]) { // Validate input if (isNaN(H) || isNaN(S) || isNaN(L) || H < 0 || H > 360 || S < 0 || S > 100 || L < 0 || L > 100) { - throw new Error('Invalid input. HSL values must be numbers within valid ranges.'); + throw new Error('Invalid input. HSL values must be numbers within valid ranges.') } // Normalize S and L to the range [0, 1] - S /= 100; - L /= 100; + S /= 100 + L /= 100 - const C = (1 - Math.abs(2 * L - 1)) * S; - const X = C * (1 - Math.abs((H / 60) % 2 - 1)); - const m = L - C / 2; + const C = (1 - Math.abs(2 * L - 1)) * S + const X = C * (1 - Math.abs(((H / 60) % 2) - 1)) + const m = L - C / 2 - let R = 0, G = 0, B = 0; + let R = 0, + G = 0, + B = 0 if (H < 60) { - R = C; - G = X; + R = C + G = X } else if (H < 120) { - R = X; - G = C; + R = X + G = C } else if (H < 180) { - G = C; - B = X; + G = C + B = X } else if (H < 240) { - G = X; - B = C; + G = X + B = C } else if (H < 300) { - R = X; - B = C; + R = X + B = C } else { - R = C; - B = X; + R = C + B = X } // Convert RGB values to the range [0, 255] and round them - R = Math.round((R + m) * 255); - G = Math.round((G + m) * 255); - B = Math.round((B + m) * 255); + R = Math.round((R + m) * 255) + G = Math.round((G + m) * 255) + B = Math.round((B + m) * 255) - return [R, G, B]; + return [R, G, B] } + export function RGBToHEX([r, g, b]) { - return ('#' + [r, g, b].map((x) => { - const hex = x.toString(16) - return hex.length === 1 ? '0' + hex : hex - }).join('')) + return ( + '#' + + [r, g, b] + .map(x => { + const hex = x.toString(16) + return hex.length === 1 ? '0' + hex : hex + }) + .join('') + ) } + export function RGBToHSL([r, g, b]) { r /= 255 g /= 255 @@ -54,7 +63,9 @@ export function RGBToHSL([r, g, b]) { const max = Math.max(r, g, b) const min = Math.min(r, g, b) - let h, s, l = (max + min) / 2 + let h, + s, + l = (max + min) / 2 if (max === min) { h = s = 0 // achromatic @@ -63,9 +74,15 @@ export function RGBToHSL([r, g, b]) { s = l > 0.5 ? d / (2 - max - min) : d / (max + min) switch (max) { - case r: h = (g - b) / d + (g < b ? 6 : 0); break - case g: h = (b - r) / d + 2; break - case b: h = (r - g) / d + 4; break + case r: + h = (g - b) / d + (g < b ? 6 : 0) + break + case g: + h = (b - r) / d + 2 + break + case b: + h = (r - g) / d + 4 + break } h *= 60 diff --git a/utils/fs.js b/utils/fs.js index 076e0d1..b393dba 100644 --- a/utils/fs.js +++ b/utils/fs.js @@ -1,15 +1,15 @@ -import { existsSync } from 'fs'; -import { dirname } from 'path'; -import { writeFile, mkdir } from 'fs/promises'; +import { existsSync } from 'fs' +import { dirname } from 'path' +import { writeFile, mkdir } from 'fs/promises' -export const ensureDirectoryExists = async (dirPath) => { +export const ensureDirectoryExists = async dirPath => { if (!existsSync(dirPath)) { - await mkdir(dirPath, { recursive: true }); + await mkdir(dirPath, { recursive: true }) } -}; +} export const writeFileWithDirs = async (filePath, data) => { - const dirPath = dirname(filePath); - await ensureDirectoryExists(dirPath); - await writeFile(filePath, data); -}; + const dirPath = dirname(filePath) + await ensureDirectoryExists(dirPath) + await writeFile(filePath, data) +} diff --git a/utils/getProjectRoot.js b/utils/getProjectRoot.js new file mode 100644 index 0000000..7cc175c --- /dev/null +++ b/utils/getProjectRoot.js @@ -0,0 +1,31 @@ +import { dirname, resolve } from 'path' +import { existsSync } from 'fs' +import { fileURLToPath } from 'url' + +const maxDepth = 10 +const __filename = fileURLToPath(import.meta.url) +const cliDir = dirname(__filename) + +export function getProjectRoot(start = process.cwd()) { + let dir = start + let depth = 0 + + while (dir !== dirname(dir) && depth < maxDepth) { + const pkgPath = resolve(dir, 'package.json') + + if (existsSync(pkgPath)) { + const isInsideNodeModules = cliDir.includes('/node_modules/') || cliDir.includes('\\node_modules\\') + + if (isInsideNodeModules && cliDir.startsWith(dir)) { + return null + } + + return dir + } + + dir = dirname(dir) + depth++ + } + + return null +} diff --git a/utils/palette.js b/utils/palette.js index b5483dd..04658fc 100644 --- a/utils/palette.js +++ b/utils/palette.js @@ -1,16 +1,21 @@ +import { resolve } from 'path' import { config } from './config.js' import { writeFileWithDirs } from './fs.js' import { generateSingleColorCSS } from '../generators/styleBlocks.js' +import { getProjectRoot } from './getProjectRoot.js' export async function handleSingleColor(H, S, L) { const css = generateSingleColorCSS(H, S, L) - const path = `${config.outputDir}/${config.atomicSubDir}/${H}.${S}.${L}.css` + + const filePath = [config.output.dir, config.modules.colors.dir, config.modules.colors.atomicSubDir, `${H}.${S}.${L}.css`] + const path = resolve(getProjectRoot(), ...filePath) + await writeFileWithDirs(path, css) - console.log(`āœ… Generated → ${path}`) + console.log(`āœ… Generated → ${filePath.join('/')}`) } export async function handlePaletteRanges(ranges) { - console.log('šŸŽØ Generating colors from config.paletteRanges...') + console.log('šŸŽØ Generating colors from config.modules.colors.paletteRanges...\n') for (const [H, S, lightnessList] of ranges) { for (const L of lightnessList) { await handleSingleColor(H, S, L) diff --git a/utils/tools.js b/utils/tools.js index 0fbaf53..98bddae 100644 --- a/utils/tools.js +++ b/utils/tools.js @@ -1,5 +1,5 @@ export function getLuminance([R, G, B]) { - [R, G, B] = [R, G, B].map(x => x / 255).map(x => x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4) + ;[R, G, B] = [R, G, B].map(x => x / 255).map(x => (x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4)) return R * 0.2126 + G * 0.7152 + B * 0.0722 } diff --git a/yarn.lock b/yarn.lock index 7667cbd..9e530c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -234,6 +234,11 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== +prettier@2.8.8: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + run-async@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-3.0.0.tgz#42a432f6d76c689522058984384df28be379daad"