@adamlui/scss-to-css 2.2.1 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,158 @@
1
+ const fs = require('fs'),
2
+ path = require('path')
3
+
4
+ ;(globalThis.cli ??= {}).config = {}
5
+
6
+ module.exports = {
7
+ configFilename: 'scss-to-css.config.mjs',
8
+
9
+ controls: {
10
+ dryRun: {
11
+ type: 'flag', regex: /^--?(?:n|dry[-_]?run)$/ },
12
+ includeDotFolders: {
13
+ type: 'flag', regex: /^--?(?:dd?|(?:include[-_]?)?dot[-_]?(?:folder|dir(?:ector(?:y|ie))?)s?=?(?:true|1)?)$/ },
14
+ noSourceMaps: {
15
+ type: 'flag', regex: /^--?(?:S|(?:exclude|disable|no)[-_]?so?u?rce?[-_]?maps?|so?u?rce?[-_]?maps?=(?:false|0))$/ },
16
+ noRecursion: {
17
+ type: 'flag', regex: /^--?(?:R|(?:disable|no)[-_]?recursi(?:on|ve)|recursi(?:on|ve)=(?:false|0))$/ },
18
+ noMinify: {
19
+ type: 'flag', regex: /^--?(?:M|(?:disable|no)[-_]?minif(?:y|ication)|minif(?:y|ication)=(?:false|0))$/ },
20
+ relativeOutput: {
21
+ type: 'flag', regex: /^--?(?:r|relative[-_]?output?=?(?:true|1)?)$/ },
22
+ copy: {
23
+ type: 'flag', regex: /^--?c(?:opy)?$/ },
24
+ quietMode: {
25
+ type: 'flag', regex: /^--?q(?:uiet)?(?:[-_]?mode)?$/ },
26
+ ignores: {
27
+ type: 'param', parser: val => val.split(',').map(val => val.trim()),
28
+ regex: /^--?(?:ignores?|(?:ignore|skip|exclude)(?:d?[-_]?files?)?)(?:[=\s].*|$)/
29
+ },
30
+ comment: {
31
+ type: 'param', parser: val => val.replace(/\\n/g, '\n'), regex: /^--?comments?(?:[=\s].*)?$/ },
32
+ uiLang: {
33
+ type: 'param', valType: 'langCode', regex: /^--?ui[-_]?lang(?:[=\s].*|$)/ },
34
+ config: {
35
+ type: 'param', valType: 'filepath', regex: /^--?config(?:[=\s].*|$)/ },
36
+ init: {
37
+ type: 'cmd', regex: /^-{0,2}i(?:nit)?$/ },
38
+ help: {
39
+ type: 'cmd', regex: /^--?h(?:elp)?$/ },
40
+ version: {
41
+ type: 'cmd', regex: /^--?ve?r?s?i?o?n?$/ },
42
+ stats: {
43
+ type: 'cmd', regex: /^--?stats?$/ }
44
+ },
45
+
46
+ load(ctrlKeys = Object.keys(this.controls)) {
47
+ const inputCtrlKeys = [].concat(ctrlKeys) // force array
48
+
49
+ if (!cli.defaultsSet && !arguments.length) { // init all defaults on arg-less load()
50
+ inputCtrlKeys.forEach(key => {
51
+ const ctrl = this.controls[key] ; if (ctrl.mode || ctrl.type == 'legacy') return
52
+ cli.config[key] ??= ctrl.defaultVal ?? ( ctrl.type == 'param' ? '' : false )
53
+ })
54
+ cli.defaultsSet = true
55
+ log.debug('All cli.config default vals set!')
56
+ }
57
+
58
+ if (!cli.configPathTried) { // init config file path
59
+ const configArg = env.args.find(arg => this.controls.config.regex.test(arg))
60
+
61
+ if (configArg) { // resolve input path, then validate
62
+ if (!/=/.test(configArg))
63
+ log.errorAndExit(`[${configArg}] ${cli.msgs.error_mustIncludePath}`)
64
+ const inputPath = configArg.split('=')[1]
65
+ cli.configPath = path.isAbsolute(inputPath) ? inputPath : path.resolve(process.cwd(), inputPath)
66
+ if (!fs.existsSync(cli.configPath))
67
+ log.configURLandExit(`${cli.msgs.error_configFileNotFound}:`, cli.configPath)
68
+
69
+ } else // auto-discover .config.[mc]?js file
70
+ for (const configExt of ['.mjs', '.cjs', '.js']) {
71
+ const autoPath = path.resolve(process.cwd(), this.configFilename.replace(/\.[^.]+$/, configExt))
72
+ if (fs.existsSync(autoPath)) { cli.configPath = autoPath ; break }
73
+ }
74
+
75
+ cli.configPathTried = true
76
+ }
77
+
78
+ if (cli.configPath) // load from config file
79
+ try {
80
+ const mod = require(cli.configPath), fileConfig = mod?.default ?? mod
81
+ if (!fileConfig || typeof fileConfig != 'object')
82
+ log.configURLandExit(`${cli.msgs.error_invalidConfigFile}.`)
83
+ ;(arguments.length ? inputCtrlKeys : Object.keys(fileConfig)).forEach(key => {
84
+ if (!(key in fileConfig)) return
85
+ const val = fileConfig[key], ctrl = this.controls[key]
86
+ if (!ctrl) {
87
+ if (this.configFileKeyWhitelist && !this.configFileKeyWhitelist.includes(key))
88
+ log.invalidConfigKey(key)
89
+ return
90
+ } else if (ctrl.type == 'legacy' && ctrl.replacedBy) {
91
+ if (key.toLowerCase().includes('no') != ctrl.replacedBy.toLowerCase().includes('no'))
92
+ cli.config[ctrl.replacedBy] = !val // assign opposite val to current key
93
+ else // assign direct val to current key
94
+ cli.config[ctrl.replacedBy] = val
95
+ return log.configKeyReplacedBy(key, ctrl.replacedBy, val)
96
+ }
97
+ cli.config[key] = val
98
+ })
99
+ if (!arguments.length) log.debug('Config file loaded!')
100
+ } catch (err) {
101
+ log.configURLandExit(`${cli.msgs.error_failedToLoadConfigFile}:`, cli.configPath, `\n${err.message}`) }
102
+
103
+ for (let i = 0 ; i < env.args.length ; i++) { // load from CLI arg (overriding config file loads)
104
+ const arg = env.args[i]
105
+ if (/^[^-]|--?(?:config|debug)/.test(arg) && arg != 'init') continue
106
+ const ctrlKey = Object.keys(this.controls).find(key => this.controls[key]?.regex?.test(arg))
107
+ if (!ctrlKey && cli.msgs) log.errorAndExit(`[${arg}] ${cli.msgs.error_notRecognized}.`)
108
+ if (!inputCtrlKeys.includes(ctrlKey)) return // don't process env.args when load() specific keys
109
+ const ctrl = this.controls[ctrlKey]
110
+ if (ctrl.type == 'legacy') { log.argDoesNothing(arg) ; continue }
111
+ if (ctrl.mode) // set cli.config.mode to mode name
112
+ cli.config.mode = ctrlKey.replace(/mode$/i, '').toLowerCase()
113
+ else { // init flag/param/cmd cli.config[ctrlKey] val
114
+ if (ctrl.type == 'param')
115
+ cli.config[ctrlKey] =
116
+ arg.includes('=') ? arg.split('=')[1]?.trim() || '' // =val
117
+ : (i +1 < env.args.length && !env.args[i +1].startsWith('-')) ? env.args[++i] // dashless val
118
+ : '' // val-less --param passed
119
+ else // flag/cmd
120
+ cli.config[ctrlKey] = true
121
+ }
122
+ }
123
+
124
+ if (!arguments.length) log.debug('Args parsed!')
125
+
126
+ this.parseValidateConfig(inputCtrlKeys)
127
+ if (!arguments.length) log.debug('All cli.config vals parsed/validated!')
128
+
129
+ return inputCtrlKeys.length == 1 ? cli.config[inputCtrlKeys[0]] : cli.config
130
+ },
131
+
132
+ parseValidateConfig(ctrlKeys = Object.keys(this.controls)) {
133
+ const language = require('./language')
134
+ for (const key of [].concat(ctrlKeys)) {
135
+ const ctrl = this.controls[key], configVal = cli.config[key]
136
+
137
+ if (ctrl.parser && !ctrl.parsed) {
138
+ cli.config[key] = ctrl.parser(configVal) ; ctrl.parsed = true }
139
+
140
+ if (ctrl.valType) ({
141
+ positiveInt() {
142
+ const numVal = parseInt(configVal, 10)
143
+ if (isNaN(numVal) || numVal < 1)
144
+ log.errorAndExit(`[${key}] ${cli.msgs.error_nonPositiveNum}: ${configVal}`)
145
+ cli.config[key] = numVal
146
+ },
147
+ filepath() {
148
+ if (configVal && !fs.existsSync(configVal))
149
+ log.errorAndExit(`[${key}] ${cli.msgs.error_invalidFilepath}: ${configVal}`)
150
+ },
151
+ langCode() {
152
+ if (configVal && !language.validateLangCode(configVal))
153
+ log.errorAndExit(`[${key}] ${cli.msgs.error_invalidLangCode}: ${configVal}`)
154
+ }
155
+ })[ctrl.valType]()
156
+ }
157
+ }
158
+ }
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ toTitleCase(str) { return str.toLowerCase().replace(/\b\w/g, char => char.toUpperCase()) }
3
+ }
@@ -1,6 +1,5 @@
1
1
  {
2
- "appName": { "message": "scss-to-css" },
3
- "appCopyright": { "message": "© 2024–2026 Adam Lui & contributors under the MIT license" },
2
+ "pkg_copyright": { "message": "© 2024–2026 Adam Lui & contributors under the MIT license" },
4
3
  "prefix_globalVer": { "message": "Global version" },
5
4
  "prefix_localVer": { "message": "Local version" },
6
5
  "prefix_source": { "message": "Source" },
@@ -11,6 +10,8 @@
11
10
  "error_invalidLangCode": { "message": "is an invalid language code" },
12
11
  "error_invalidURL": { "message": "Invalid URL" },
13
12
  "error_invalidConfigFile": { "message": "Config file must export an object" },
13
+ "error_invalidKey": { "message": "Invalid key" },
14
+ "error_foundIn": { "message": "found in" },
14
15
  "error_configFileNotFound": { "message": "Config file not found" },
15
16
  "error_failedToLoadConfigFile": { "message": "Failed to load config file" },
16
17
  "error_failedToFetchGlobalVer": { "message": "Failed to fetch global version" },
@@ -19,9 +20,15 @@
19
20
  "error_firstArgNotExist": { "message": "First argument can only be an existing file or directory" },
20
21
  "error_doesNotExist": { "message": "does not exist" },
21
22
  "warn_configFileExists": { "message": "Config file already exists" },
23
+ "warn_docLocalesFetchFailed": { "message": "Failed to fetch doc locales" },
22
24
  "warn_remoteConfigNotFound": { "message": "Remote config file not found" },
23
25
  "warn_remoteConfigFailed": { "message": "Failed to fetch remote config file" },
24
- "warn_docLocalesFetchFailed": { "message": "Failed to fetch doc locales" },
26
+ "warn_option": { "message": "Option" },
27
+ "warn_noLongerHasAnyEffect": { "message": "no longer has any effect" },
28
+ "warn_hasBeenReplacedBy": { "message": "has been replaced by" },
29
+ "warn_andWillBeRemoved": { "message": "and will be removed" },
30
+ "warn_notFound": { "message": "Not found" },
31
+ "info_configFile": { "message": "Config file" },
25
32
  "info_exampleValidConfigFile": { "message": "Example valid config file" },
26
33
  "info_exampleValidCmd": { "message": "Example valid command" },
27
34
  "info_scssFilesToBeCompiled": { "message": "SCSS files to be compiled" },
@@ -38,6 +45,7 @@
38
45
  "info_type": { "message": "type" },
39
46
  "info_or": { "message": "or" },
40
47
  "info_visit": { "message": "visit" },
48
+ "info_toCreateDefaultConfig": { "message": "to create default config file" },
41
49
  "info_configFileCreated": { "message": "Config file created" },
42
50
  "info_fetchingRemoteConfigFrom": { "message": "Fetching remote config file from" },
43
51
  "tip_editToSetDefaults": { "message": "Edit this file to customize defaults" },
@@ -66,5 +74,7 @@
66
74
  "optionDesc_config": { "message": "Load custom config file" },
67
75
  "optionDesc_init": { "message": "Create config file (in project root)" },
68
76
  "optionDesc_help": { "message": "Display help screen" },
69
- "optionDesc_version": { "message": "Show version number" }
77
+ "optionDesc_version": { "message": "Show version number" },
78
+ "optionDesc_stats": { "message": "Show npm stats" },
79
+ "optionDesc_debug": { "message": "Show debug logs" }
70
80
  }
@@ -13,6 +13,6 @@
13
13
  "src": "https://github.com/adamlui/scss-to-css/tree/main/src"
14
14
  },
15
15
  "commitHashes": {
16
- "locales": "83a673c"
16
+ "locales": "0da06d6"
17
17
  }
18
18
  }
@@ -0,0 +1,235 @@
1
+ // © 2024–2026 Adam Lui & contributors under the MIT license.
2
+ // Source: https://github.com/adamlui/scss-to-css/tree/main/src
3
+ // Documentation: https://github.com/adamlui/scss-to-css/tree/main/docs
4
+
5
+ const fs = require('fs'),
6
+ path = require('path'),
7
+ sass = require('sass')
8
+
9
+ Object.assign(globalThis.api ??= {},
10
+ require(`${ /[\\/]src(?:[\\/]|$)/i.test(__dirname) ? '../' : './data/' }package-data.json`))
11
+ api.regex = {
12
+ compile: /^(?:build|comp(?:ile|ress)|minify)$/i,
13
+ findSCSS: /^(?:find|search)(?:scss)?$/i
14
+ }
15
+
16
+ function findSCSS(searchDir, options = {}) {
17
+
18
+ const docURL = 'https://github.com/adamlui/scss-to-css/tree/main/docs/#findscsssearchdir-options',
19
+ exampleCall = `findSCSS('assets/scss', { verbose: false, dotFolders: true })`
20
+
21
+ const defaultOptions = {
22
+ recursive: true, // recursively search for nested files in searchDir passed
23
+ verbose: true, // show logging in console/terminal
24
+ dotFolders: false, // include dotfolders in file search
25
+ ignores: [] // files/dirs to exclude from search results
26
+ }
27
+
28
+ _log.prefix = 'findSCSS()'
29
+
30
+ // Validate searchDir
31
+ if (typeof searchDir != 'string')
32
+ _log.errHelpURLandThrow({ errMsg: '1st arg <searchDir> must be a string.', helpURL: docURL })
33
+ else { // verify searchDir path existence
34
+ const searchPath = path.resolve(process.cwd(), searchDir)
35
+ if (!fs.existsSync(searchPath)) {
36
+ _log.error('1st arg <searchDir> must be an existing directory.')
37
+ _log.error(`${searchPath} does not exist.`)
38
+ return _log.helpURL(docURL)
39
+ }
40
+ }
41
+
42
+ // Validate/init options
43
+ if (!_validateOptions({ options, defaultOptions, helpURL: docURL, exampleCall })) return
44
+ options = { ...defaultOptions, ...options } // merge validated options w/ missing default ones
45
+ if (options.ignoreFiles) options.ignores = [...options.ignores, ...options.ignoreFiles] // for bw compat
46
+
47
+ // Search for files
48
+ const dirFiles = fs.readdirSync(searchDir), scssFiles = []
49
+ if (options.verbose && !options.isRecursing)
50
+ _log.info('Searching for files...')
51
+ dirFiles.forEach(file => {
52
+ const filePath = path.resolve(searchDir, file)
53
+ const shouldIgnore = options.ignores?.length && options.ignores.filter(Boolean).some(ignore => {
54
+ ignore = ignore.replace(/\/$/, '')
55
+ return file == ignore || filePath.split(path.sep).includes(ignore)
56
+ })
57
+ if (shouldIgnore) {
58
+ if (options.verbose) _log.info(`** ${file} ignored`)
59
+ } else if (fs.statSync(filePath).isDirectory() && file != 'node_modules' // folder found
60
+ && options.recursive // only proceed if recursion enabled
61
+ && (options.dotFolders || !file.startsWith('.')) // exclude dotfolders if prohibited
62
+ ) scssFiles.push( // recursively find files in eligible dir
63
+ ...findSCSS(filePath, { ...options, isRecursing: true }))
64
+ else if (/s[ac]ss$/.test(file)) // .s[ac]ss file found
65
+ scssFiles.push(filePath) // store for returning
66
+ })
67
+
68
+ // Log/return final results
69
+ if (options.verbose && !options.isRecursing) {
70
+ _log.info('Search complete!',
71
+ `${ scssFiles.length || 'No' } file${ scssFiles.length == 1 ? '' : 's' } found.`)
72
+ if (findSCSS.caller?.name != 'compile' && typeof window != 'undefined')
73
+ _log.info('Check returned array.')
74
+ }
75
+ return options.isRecursing || scssFiles.length ? scssFiles : []
76
+ }
77
+
78
+ function compile(input, options = {}) {
79
+
80
+ const docURL = 'https://github.com/adamlui/scss-to-css/tree/main/docs/#compileinput-options',
81
+ exampleCall = `compile('assets/scss', { recursive: false, minify: false })`
82
+
83
+ const defaultOptions = {
84
+ recursive: true, // recursively search for nested files if dir path passed
85
+ verbose: true, // show logging in console/terminal
86
+ dotFolders: false, // include dotfolders in file search
87
+ minify: true, // minify output CSS
88
+ sourceMaps: true, // generate CSS source maps
89
+ relativeOutput: false, // output files relative to each source file instead of to input root
90
+ ignores: [], // files/dirs to exclude from compilation
91
+ comment: '' // header comment to prepend to compiled CSS
92
+ }
93
+
94
+ _log.prefix = 'compile()'
95
+
96
+ // Validate input
97
+ if (typeof input != 'string')
98
+ _log.errHelpURLandThrow({ errMsg: '1st arg <input> must be a string.', helpURL: docURL })
99
+
100
+ // Validate/init options
101
+ if (!_validateOptions({ options, defaultOptions, helpURL: docURL, exampleCall })) return
102
+ options = { ...defaultOptions, ...options } // merge validated options w/ missing default ones
103
+ if (options.ignoreFiles) options.ignores = [...options.ignores, ...options.ignoreFiles] // for bw compat
104
+
105
+ // Compile SCSS based on input
106
+ const compileOptions = {
107
+ style: options.minify ? 'compressed' : 'expanded',
108
+ sourceMap: options.sourceMaps,
109
+ charset: false // prevent UTF-8 BOM in output
110
+ }
111
+ if (fs.existsSync(input)) { // compile based on path arg
112
+ if (/s[ac]ss$/.test(input) && fs.statSync(input).isFile()) { // file path passed
113
+ if (options.verbose) _log.info(`** Compiling ${input}...`)
114
+ try { // to compile file passed
115
+ const compileResult = sass.compile(input, compileOptions)
116
+ if (options.comment)
117
+ compileResult.css = _prependComment(compileResult.css, options.comment)
118
+ if (options.verbose && typeof window != 'undefined')
119
+ _log.info('Compilation complete! Check returned object.')
120
+ return {
121
+ code: compileResult.css, srcMap: compileResult.sourceMap,
122
+ srcPath: path.resolve(process.cwd(), input), error: undefined
123
+ }
124
+ } catch (err) {
125
+ _log.error(err.message)
126
+ return { code: undefined, srcMap: undefined, srcPath: undefined, error: err }
127
+ }
128
+ } else { // dir path passed
129
+ const compileResult = findSCSS(input, options)?.map(scssPath => { // compile found SCSS files
130
+ if (options.verbose) _log.info(`** Compiling ${scssPath}...`)
131
+ try { // to compile found file
132
+ const compileResult = sass.compile(scssPath, compileOptions),
133
+ relPath = options.relativeOutput ? undefined
134
+ : path.relative(path.resolve(process.cwd(), input), scssPath)
135
+ if (options.comment)
136
+ compileResult.css = _prependComment(compileResult.css, options.comment)
137
+ return {
138
+ code: compileResult.css, srcMap: compileResult.sourceMap, srcPath: scssPath, relPath,
139
+ error: undefined
140
+ }
141
+ } catch (err) {
142
+ _log.error(err.message)
143
+ return { code: undefined, srcMap: undefined, srcPath: undefined, error: err }
144
+ }
145
+ }).filter(data => !data.error ) // filter out failed compilations
146
+ if (options.verbose) {
147
+ if (compileResult.length && typeof window != 'undefined')
148
+ _log.info('Compilation complete! Check returned object.')
149
+ else
150
+ _log.info('No SCSS files processed.')
151
+ }
152
+ return compileResult
153
+ }
154
+ } else { // compile based on src code arg
155
+ if (options.verbose)
156
+ _log.info('** Compiling passed source code...')
157
+ try { // to compile passed src code
158
+ const compileResult = sass.compileString(input, compileOptions)
159
+ if (options.comment)
160
+ compileResult.css = _prependComment(compileResult.css, options.comment)
161
+ return { code: compileResult.css, srcMap: compileResult.sourceMap, srcPath: undefined, error: undefined }
162
+ } catch (err) {
163
+ _log.error(err.message)
164
+ return { code: undefined, srcMap: undefined, srcPath: undefined, error: err }
165
+ }
166
+ }
167
+ }
168
+
169
+ function _prependComment(code, comment) {
170
+ let shebang = '' ; const shebangMatch = code.match(/^#!.*\n/)
171
+ if (shebangMatch) { // slice shebang from code to memory
172
+ shebang = shebangMatch[0] ; code = code.slice(shebang.length) }
173
+ return `${shebang}/**\n${comment.split('\n').map(line => ` * ${line}`).join('\n')}\n */\n${code}`
174
+ }
175
+
176
+ function _validateOptions({ options, defaultOptions, helpURL, exampleCall }) {
177
+
178
+ // Init option strings/types
179
+ const booleanOptions = Object.keys(defaultOptions).filter(key => typeof defaultOptions[key] == 'boolean'),
180
+ integerOptions = Object.keys(defaultOptions).filter(key => Number.isInteger(defaultOptions[key]))
181
+
182
+ // Validate options
183
+ if (typeof options != 'object') { // validate as obj
184
+ let optionsPos = exampleCall.split(',').findIndex(arg => arg.trim().startsWith('{')) +1
185
+ optionsPos += ['st','nd','rd'][optionsPos -1] || 'th' // append ordinal suffix
186
+ _log.error(`${ optionsPos == '0th' ? '[O' : optionsPos + ' arg [o' }ptions] can only be an object of key/vals.`)
187
+ _log.info('Example valid call:', exampleCall)
188
+ _log.validOptions(defaultOptions) ; _log.helpURL(helpURL)
189
+ return false
190
+ }
191
+ for (const key in options) { // validate each key
192
+ if (key == 'isRecursing' || !Object.prototype.hasOwnProperty.call(defaultOptions, key))
193
+ continue // to next key
194
+ else if (booleanOptions.includes(key) && typeof options[key] != 'boolean') {
195
+ _log.error(`[${key}] option can only be \`true\` or \`false\`.`)
196
+ _log.helpURL(helpURL)
197
+ return false
198
+ } else if (integerOptions.includes(key)) {
199
+ options[key] = parseInt(options[key], 10)
200
+ if (isNaN(options[key]) || options[key] < 1) {
201
+ _log.error(`[${key}] option can only be an integer > 0.`)
202
+ _log.helpURL(helpURL)
203
+ return false
204
+ }
205
+ }
206
+ }
207
+
208
+ return true
209
+ }
210
+
211
+ const _log = {
212
+ prefix: api.name,
213
+
214
+ errHelpURLandThrow({ errMsg, helpURL }) { this.error(errMsg) ; this.helpURL(helpURL) ; throw new Error(errMsg) },
215
+ error(...args) { console.error(`${this.prefix} » ERROR:`, ...args) },
216
+ helpURL(url = api.urls?.docs) { this.info('For more help, please visit', url) },
217
+ info(...args) { console.info(`${this.prefix} »`, ...args) },
218
+
219
+ validOptions(options) {
220
+ const strValidOptions = Object.keys(options).join(', ')
221
+ const strDefaultOptions = JSON.stringify(options, null, 2)
222
+ .replace(/"([^"]+)":/g, '$1:') // strip quotes from keys
223
+ .replace(/"/g, '\'') // replace double quotes w/ single quotes
224
+ .replace(/\n\s*/g, ' ') // condense to single line
225
+ this.info(`Valid options: [${strValidOptions}]`)
226
+ this.info(`If omitted, default settings are: ${strDefaultOptions}`)
227
+ }
228
+ }
229
+
230
+ module.exports = new Proxy({ compile, findSCSS }, {
231
+ get(target, requestedMethod) {
232
+ for (const [methodName, methodRegex] of Object.entries(api.regex))
233
+ if (methodRegex.test(requestedMethod)) return target[methodName]
234
+ }
235
+ })
package/docs/README.md CHANGED
@@ -33,8 +33,8 @@
33
33
  <img height=31 src="https://img.shields.io/npm/dm/%40adamlui%2Fscss-to-css?logo=npm&color=af68ff&logoColor=white&labelColor=464646&style=for-the-badge"></a>
34
34
  <a href="#%EF%B8%8F-mit-license">
35
35
  <img height=31 src="https://img.shields.io/badge/License-MIT-orange.svg?logo=internetarchive&logoColor=white&labelColor=464646&style=for-the-badge"></a>
36
- <a href="https://github.com/adamlui/js-utils/releases/tag/scss-to-css-2.2.1">
37
- <img height=31 src="https://img.shields.io/badge/Latest_Build-2.2.1-44cc11.svg?logo=icinga&logoColor=white&labelColor=464646&style=for-the-badge"></a>
36
+ <a href="https://github.com/adamlui/js-utils/releases/tag/scss-to-css-2.3.1">
37
+ <img height=31 src="https://img.shields.io/badge/Latest_Build-2.3.1-44cc11.svg?logo=icinga&logoColor=white&labelColor=464646&style=for-the-badge"></a>
38
38
  <a href="https://www.npmjs.com/package/@adamlui/scss-to-css?activeTab=code">
39
39
  <img height=31 src="https://img.shields.io/npm/unpacked-size/%40adamlui%2Fscss-to-css?style=for-the-badge&logo=ebox&logoColor=white&color=blue&labelColor=464646"></a>
40
40
  <a href="https://sonarcloud.io/component_measures?metric=new_vulnerabilities&id=adamlui_scss-to-css:src/scss-to-css.js">
@@ -170,6 +170,8 @@ Commands:
170
170
  -i, --init Create config file (in project root).
171
171
  -h, --help Display help screen.
172
172
  -v, --version Show version number.
173
+ --stats Show npm stats.
174
+ --debug [targetKey] Show debug logs.
173
175
  ```
174
176
 
175
177
  #
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adamlui/scss-to-css",
3
- "version": "2.2.1",
3
+ "version": "2.3.1",
4
4
  "description": "Recursively compile all SCSS files into minified CSS",
5
5
  "author": {
6
6
  "name": "Adam Lui",
@@ -34,8 +34,8 @@
34
34
  "!docs/*/"
35
35
  ],
36
36
  "bin": {
37
- "scsstocss": "dist/cli/index.min.js",
38
- "scss-to-css": "dist/cli/index.min.js"
37
+ "scsstocss": "dist/cli/index.js",
38
+ "scss-to-css": "dist/cli/index.js"
39
39
  },
40
40
  "directories": {
41
41
  "lib": "./dist",
@@ -48,6 +48,7 @@
48
48
  "lint:fix": "eslint . --fix --cache",
49
49
  "lint:fix-all": "eslint . --fix",
50
50
  "translate": "translate-messages",
51
+ "dev": "npm run build && npm i -g && scss-to-css --help",
51
52
  "build": "node utils/build",
52
53
  "build:js": "node utils/build --js",
53
54
  "build:data": "node utils/build --data",
@@ -55,6 +56,7 @@
55
56
  "debug": "node src/cli --debug",
56
57
  "bump:patch": "bash utils/bump.sh patch",
57
58
  "bump:minor": "bash utils/bump.sh minor",
59
+ "bump:feat": "npm run bump:minor",
58
60
  "bump:major": "bash utils/bump.sh major"
59
61
  },
60
62
  "repository": {
@@ -81,10 +83,11 @@
81
83
  "sass": "^1.97.3"
82
84
  },
83
85
  "devDependencies": {
84
- "@adamlui/minify.js": "^2.3.0",
85
86
  "@eslint/json": "^1.0.1",
86
87
  "@eslint/markdown": "^7.5.1",
87
88
  "@stylistic/eslint-plugin": "^5.9.0",
89
+ "console-table-printer": "^2.15.0",
90
+ "copyfiles": "^2.4.1",
88
91
  "eslint": "^9.39.3",
89
92
  "eslint-plugin-import": "^2.32.0",
90
93
  "eslint-plugin-regexp": "^3.0.0",
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * © 2024–2026 Adam Lui & contributors under the MIT license.
4
- * Source: https://github.com/adamlui/scss-to-css/tree/main/src
5
- * Documentation: https://github.com/adamlui/scss-to-css/tree/main/docs
6
- */
7
- (async()=>{globalThis.env={args:process.argv.slice(2),devMode:/[\\/]src(?:[\\/]|$)/i.test(__dirname)},env.debugMode=env.args.some(e=>/^--?debug(?:-?mode)?$/.test(e)),env.modExt=`${env.devMode?"":".min"}.js`;let e=require("./lib/compile"+env.modExt),s=require("../scss-to-css"+env.modExt).findSCSS,i=require("fs"),r=require("./lib/init"+env.modExt),o=require("./lib/log"+env.modExt),c=require("path");if(await r.cli(),cli.config.init)return r.configFile();if(cli.config.help)return o.help();if(cli.config.version)return o.version();var[n="",t=""]=env.args.filter(e=>!e.startsWith("-")).map(e=>e.replace(/^\/*/,""));let l=c.resolve(process.cwd(),n);n&&!i.existsSync(l)&&(d=l+".scss",i.existsSync(d)?l=d:(o.error(`${cli.msgs.error_firstArgNotExist}.\n${l} ${cli.msgs.error_doesNotExist}.`),o.success(cli.msgs.info_exampleValidCmd+`:
8
- » scss-to-css . output.min.css`),o.helpCmdAndDocURL(),process.exit(1)));var d=/s[ac]ss$/.test(l)&&!i.statSync(l).isDirectory()?[l]:s(l,{recursive:!cli.config.noRecursion,verbose:!cli.config.quietMode,ignores:cli.config.ignores});env.debugMode||cli.config.dryRun?d.length?(o.info(cli.msgs.info_scssFilesToBeCompiled+":"),d.forEach(e=>o.dim(e))):o.info(`
9
- ${cli.msgs.info_noSCSSfilesWillBeCompiled}.`):e.scss({srcFiles:d,inputPath:l,inputArg:n,outputArg:t})})();
@@ -1,8 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/scss-to-css/tree/main/src
4
- * Documentation: https://github.com/adamlui/scss-to-css/tree/main/docs
5
- */
6
- let log=require("./log"+env.modExt);module.exports={scss({srcFiles:i,inputPath:e,inputArg:r,outputArg:t}){let o=require("../../scss-to-css"+env.modExt).compile,g=require("fs"),f=require("path"),c=[],s=[];var n;!cli.config.relativeOutput&&g.statSync(e).isDirectory()?(n=o(e,{verbose:!1,minify:!cli.config.noMinify,comment:cli.config.comment,relativeOutput:!1,recursive:!cli.config.noRecursion,dotFolders:cli.config.includeDotFolders,sourceMaps:!cli.config.noSourceMaps,ignores:cli.config.ignores}))&&(n.error?c.push(e):s=[].concat(n)):s=i.map(i=>{var e=o(i,{verbose:!cli.config.quietMode,minify:!cli.config.noMinify,sourceMaps:!cli.config.noSourceMaps,comment:cli.config.comment});return e.error&&c.push(i),e}).filter(i=>!i.error),cli.config.quietMode||(n=1==(e=s.length)?"":"s",e?(log.success(cli.msgs.info_compilationComplete+"!"),log.data(`${e} CSS ${cli.msgs.info_file}${n}${cli.config.noSourceMaps?"":` + ${e} `+cli.msgs.info_srcMap+n} ${cli.msgs.info_generated}.`)):console.info(cli.msgs.info_noSCSSfilesProcessed+"."),c.length&&(log.error(c.length+" "+cli.msgs.info_file+(1==c.length?"":"s"),cli.msgs.info_failedToCompile+":"),c.forEach(i=>log.ifNotQuiet(i)))),s?.length&&(cli.config.copy&&1==s?.length?(log.data(s[0].code),log.ifNotQuiet(`
7
- ${cli.msgs.info_copyingToClip}...`),require("node-clipboardy").writeSync(s[0].code)):(log.ifNotQuiet(`
8
- ${cli.msgs.info_writing}${1<s?.length?"s":""}...`),s?.forEach(({code:i,srcMap:e,srcPath:o,relPath:c})=>{let s,n;if(!cli.config.relativeOutput&&c){let i=f.resolve(process.cwd(),t||"css"),e=f.dirname(c);s="."!=e?f.join(i,e):i,n=`${f.basename(o,f.extname(o))}${cli.config.noMinify?"":".min"}.css`}else s=f.join(f.dirname(o),t.endsWith(".css")?f.dirname(t):t||"css"),n=`${t.endsWith(".css")&&/s[ac]ss$/.test(r)?f.basename(t).replace(/(\.min)?\.css$/,""):f.basename(o,f.extname(o))}.min.css`;let l=f.join(s,n);g.mkdirSync(s,{recursive:!0}),g.writeFileSync(l,i,"utf8"),log.ifNotQuiet(` ${log.colors.bg}✓${log.colors.nc} `+f.relative(process.cwd(),l)),cli.config.noSourceMaps||g.writeFileSync(l+".map",JSON.stringify(e),"utf8"),log.ifNotQuiet(` ${log.colors.bg}✓${log.colors.nc} ${f.relative(process.cwd(),l)}.map`)})))}};
@@ -1,6 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/scss-to-css/tree/main/src
4
- * Documentation: https://github.com/adamlui/scss-to-css/tree/main/docs
5
- */
6
- module.exports={atomicWrite(e,r,t="utf8"){var n=require("fs"),a=require("path"),a=a.join(a.dirname(e),`.${a.basename(e)}.tmp`);n.writeFileSync(a,r,t),n.renameSync(a,e)},fetch(n){return"undefined"==typeof fetch?new Promise((t,e)=>{var r=n.match(/^([^:]+):\/\//)[1];/^https?$/.test(r)||e(new Error(cli.msgs.error_invalidURL+".")),require(r).get(n,e=>{let r="";e.on("data",e=>r+=e),e.on("end",()=>t({json:()=>JSON.parse(r)}))}).on("error",e)}):fetch(n)},flatten(e,{key:r="message"}={}){var t,n={};for(t in e)n[t]="object"==typeof e[t]&&r in e[t]?e[t][r]:e[t];return n}};
@@ -1,6 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/scss-to-css/tree/main/src
4
- * Documentation: https://github.com/adamlui/scss-to-css/tree/main/docs
5
- */
6
- let language=require("./language"+env.modExt),log=require("./log"+env.modExt),settings=require("./settings"+env.modExt),dataPath="../../"+(env.devMode?"../":"data/");module.exports={async cli(){Object.assign(globalThis.cli??={},require(dataPath+"package-data.json")),cli.lang=settings.load("uiLang")||(env.debugMode?language.generateRandomLang({excludes:["en"]}):language.getSysLang()),cli.msgs=await language.getMsgs(cli.lang),cli.urls.cliDocs=cli.urls.docs+"/#-command-line-usage",cli.lang.startsWith("en")||(cli.docLocale=cli.lang.replace("_","-").toLowerCase(),cli.docLocales??=await language.getDocLocales(),cli.docLocales?.includes(cli.docLocale)&&log.debug(cli.urls.cliDocs=cli.urls.docs+`/${cli.docLocale}#readme`)),settings.load()},async configFile(e=settings.configFilename){var a=require("fs"),i=require("path"),t={target:i.resolve(process.cwd(),e)};if(a.existsSync(t.target))return log.warn(cli.msgs.warn_configFileExists+":",t.target);if(a.existsSync(t.src=i.resolve(__dirname,""+dataPath+e)))a.copyFileSync(t.src,t.target);else{i=require("./jsdelivr"+env.modExt).pkgVerURL+`/${e}/`;log.data(cli.msgs.info_fetchingRemoteConfigFrom+` ${i}...`);try{var l=require("./data"+env.modExt),s=await l.fetch(i);if(!s.ok)return log.warn(`${cli.msgs.warn_remoteConfigNotFound}: ${i} (${s.status})`);l.atomicWrite(t.target,await s.text())}catch(e){return log.warn(cli.msgs.warn_remoteConfigFailed+`: ${i} `+e.message)}}log.success(`${cli.msgs.info_configFileCreated}: ${t.target}\n`),log.tip(cli.msgs.tip_editToSetDefaults+"."),log.tip(cli.msgs.tip_cliArgsPrioritized+".")}};
@@ -1,6 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/scss-to-css/tree/main/src
4
- * Documentation: https://github.com/adamlui/scss-to-css/tree/main/docs
5
- */
6
- module.exports={pkgVerURL(e){e||=cli.version||=require("./pkg"+env.modExt).getVer("local")||"none";e=/^\d+\.\d+\.\d+$/.test(e)?"v"+e:"latest";return cli.urls.jsdelivr+"@"+e},commitURL(e="latest"){return cli.urls.jsdelivr+"@"+e}};
@@ -1,7 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/scss-to-css/tree/main/src
4
- * Documentation: https://github.com/adamlui/scss-to-css/tree/main/docs
5
- */
6
- let data=require("./data"+env.modExt),log=require("./log"+env.modExt);module.exports={formatCode(e){return e.replace(/([a-z]{2,8})[-_]([a-z]{2})/i,(e,r,a)=>r.toLowerCase()+"_"+a.toUpperCase())},generateRandomLang({includes:e=[],excludes:r=[]}={}){let t=require("fs"),s=require("path"),a=e.length?e:(()=>{var e=s.join(__dirname,"..",".cache"),r=s.join(e,"locales.json");if(t.existsSync(r))try{return JSON.parse(t.readFileSync(r,"utf8"))}catch(e){}var a=s.resolve(process.cwd(),"_locales");return t.existsSync(a)?(a=t.readdirSync(a,{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name).filter(e=>/^\w{2}[-_]?\w{0,2}$/.test(e)),t.mkdirSync(e,{recursive:!0}),data.atomicWrite(r,JSON.stringify(a,null,2)),a):["en"]})(),n=new Set(r),o="en";return(a=a.filter(e=>!n.has(e))).length&&(o=a[Math.floor(Math.random()*a.length)]),log.debug(`Random language: ${o}
7
- `),o},async getDocLocales(){cli.version||=require("./pkg"+env.modExt).getVer("local")||"none";var e=require("./jsdelivr"+env.modExt).pkgVerURL()+"/docs/",r=[];try{for(var a,t=await(await data.fetch(e)).text(),s=/href=".*\/docs\/([^/]+)\/"/g;a=s.exec(t);)r.push(a[1])}catch(e){log.warn(cli.msgs.warn_docLocalesFetchFailed+":",e.message)}return r},async getMsgs(t="en"){if(t=module.exports.formatCode(t),env.msgs&&t==cli.lang)return env.msgs;let e=data.flatten(require(`../../${env.devMode?"../_locales/en/":"data/"}messages.json`));if(!t.startsWith("en")){var s=require("./jsdelivr"+env.modExt).commitURL(cli.commitHashes.locales)+"/_locales/";let r=s+t+"/messages.json",a=0;for(;a<3;)try{e=data.flatten(await(await data.fetch(r)).json());break}catch(e){if(3<=++a)break;log.debug(r=t.includes("-")&&1==a?r.replace(/([^_]*)_[^/]*(\/.*)/,"$1$2"):s+"en/messages.json")}}return e},getSysLang(){try{var e;return"win32"==process.platform?require("child_process").execSync("(Get-Culture).TwoLetterISOLanguageName",{shell:"powershell",encoding:"utf-8"}).trim():((e=process.env).LANG||e.LANGUAGE||e.LC_ALL||e.LC_MESSAGES||e.LC_NAME).split(".")[0]}catch(e){return log.error(cli.msgs.error_failedToFetchSysLang+":",e.message),"en"}},validateLangCode(e){return"string"==typeof e&&/^[a-z]{2,8}(?:[-_][a-z]{2,3})?$/i.test(e)}};
@@ -1,23 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/scss-to-css/tree/main/src
4
- * Documentation: https://github.com/adamlui/scss-to-css/tree/main/docs
5
- */
6
- module.exports={colors:{nc:"",br:"",by:"",bo:"",bg:"",bw:"",gry:"",blk:"",tlBG:""},configURL(){this.info(`
7
- ${cli.msgs.info_exampleValidConfigFile}: `+cli.urls.config)},configURLandExit(...s){this.error(...s),this.configURL(),process.exit(1)},data(s){console.log(`
8
- `+this.colors.bw+s+this.colors.nc)},debug(s){env.debugMode&&console.debug(`
9
- ${this.colors.bo}DEBUG:`,s,this.colors.nc,"\n")},dim(s){console.log(""+this.colors.gry+s+this.colors.nc)},error(...s){console.error(`
10
- ${this.colors.br}ERROR:`,...s,this.colors.nc)},errorAndExit(...s){this.error(...s),this.helpCmdAndDocURL(),process.exit(1)},ifNotQuiet(s){cli.config.quietMode||console.info(s)},info(s){console.info(`
11
- `+this.colors.by+s+this.colors.nc)},tip(s){console.info(this.colors.by+"TIP: "+s+this.colors.nc)},success(s){console.log(`
12
- `+this.colors.bg+s+this.colors.nc)},warn(...s){console.warn(`
13
- ${this.colors.bo}WARNING:`,...s,this.colors.nc)},help(s=["header","usage","pathArgs","flags","params","cmds"]){cli.prefix=""+this.colors.tlBG+this.colors.blk+` ${cli.name.replace(/^@[^/]+\//,"")} ${this.colors.nc} `;let o={header:[`
14
- ├ ${cli.prefix}${cli.msgs.appCopyright}.`,""+cli.prefix+cli.msgs.prefix_source+": "+cli.urls.src],usage:[`
15
- ${this.colors.bw}o ${cli.msgs.helpSection_usage}:`+this.colors.nc,` ${this.colors.bw}» `+this.colors.bg+cli.cmdFormat+this.colors.nc],pathArgs:[`
16
- ${this.colors.bw}o ${cli.msgs.helpSection_pathArgs}:`+this.colors.nc,` [inputPath] ${cli.msgs.inputPathDesc_main}, ${cli.msgs.inputPathDesc_extra}.`,` [outputPath] ${cli.msgs.outputPathDesc_main}, `+cli.msgs.outputPathDesc_extra],flags:[`
17
- ${this.colors.bw}o ${cli.msgs.helpSection_flags}:`+this.colors.nc,` -n, --dry-run ${cli.msgs.optionDesc_dryRun}.`,` -d, --include-dotfolders ${cli.msgs.optionDesc_dotfolders}.`,` -S, --no-source-maps ${cli.msgs.optionDesc_noSourceMaps}.`,` -M, --no-minify ${cli.msgs.optionDesc_noMinify}.`,` -R, --no-recursion ${cli.msgs.optionDesc_noRecursion}.`,` -r, --relative-output ${cli.msgs.optionDesc_relativeOutput}.`,` -c, --copy ${cli.msgs.optionDesc_copy}.`,` -q, --quiet ${cli.msgs.optionDesc_quiet}.`],params:[`
18
- ${this.colors.bw}o ${cli.msgs.helpSection_params}:`+this.colors.nc,`--ignores="dir/,file1.scss,file2.sass" ${cli.msgs.optionDesc_ignores}.`,`--comment="comment" ${cli.msgs.optionDesc_commentMain}. ${cli.msgs.optionDesc_commentExtra}.`,` --ui-lang="code" ${cli.msgs.optionDesc_uiLang}.`,` --config="path/to/file" ${cli.msgs.optionDesc_config}.`],cmds:[`
19
- ${this.colors.bw}o ${cli.msgs.helpSection_cmds}:`+this.colors.nc,` -i, --init ${cli.msgs.optionDesc_init}.`,` -h, --help ${cli.msgs.optionDesc_help}.`,` -v, --version ${cli.msgs.optionDesc_version}.`]};s.forEach(t=>o[t]?.forEach(o=>{{var e=/header|usage/.test(t)?1:41;let i=process.stdout.columns||80,s=o.match(/\S+|\s+/g),c=[],l="";s.forEach(s=>{var o=i-(c.length?e:0);l.length+"| ".length+s.length>o&&(c.push(c.length?l.trimStart():l),l=""),l+=s}),c.push(c.length?l.trimStart():l),c.forEach((s,o)=>console.info("| "+(0==o?s:" ".repeat(e)+s)))}})),console.info(`
20
- ${cli.msgs.info_moreHelp}, ${cli.msgs.info_visit}: `+this.colors.bw+cli.urls.cliDocs+this.colors.nc)},helpCmdAndDocURL(){console.info(`
21
- ${cli.msgs.info_moreHelp}, ${cli.msgs.info_type} ${cli.name.split("/")[1]} --help' ${cli.msgs.info_or} ${cli.msgs.info_visit}
22
- `+this.colors.bw+cli.urls.docs+this.colors.nc)},version(){var s=require("./pkg"+env.modExt).getVer;this.info(cli.name),this.data(`${cli.msgs.prefix_globalVer}: ${s("global")||"none"}
23
- ${cli.msgs.prefix_localVer}: `+(s("local")||"none"))}};
@@ -1,6 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/scss-to-css/tree/main/src
4
- * Documentation: https://github.com/adamlui/scss-to-css/tree/main/docs
5
- */
6
- let log=require("./log"+env.modExt);module.exports={getVer(e="any"){let r;if("global"!=e)try{var l=require("path").resolve(process.cwd(),"node_modules",cli.name,"package.json");r=require(l).version}catch(e){log.debug(cli.msgs.error_readingLocalPkgVer+": "+e.message)}if("global"==e||"all"==e&&!r)try{r=require("child_process").execSync(`npm view ${JSON.stringify(cli.name)} version`).toString().trim()}catch(e){log.debug(cli.msgs.error_failedToFetchGlobalVer+": "+e.message)}return r}};