styles-config 2.0.0-alpha.5 → 2.0.0-alpha.6

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.
package/src/loader.ts DELETED
@@ -1,112 +0,0 @@
1
- import { cosmiconfig, cosmiconfigSync, defaultLoadersSync } from 'cosmiconfig';
2
- import type { StylesConfig } from './types.js';
3
-
4
- export interface LoadedConfigMeta {
5
- config: StylesConfig;
6
- configFilePath?: string;
7
- }
8
-
9
- const explorer = cosmiconfig('styles', {
10
- searchPlaces: [
11
- 'styles.config.ts',
12
- 'styles.config.js',
13
- 'styles.config.mts',
14
- 'styles.config.mjs',
15
- 'styles.config.cjs',
16
- 'styles.config.cts'
17
- ],
18
- loaders: {
19
- // eslint-disable-next-line @typescript-eslint/naming-convention
20
- '.mts': defaultLoadersSync['.ts'],
21
- // eslint-disable-next-line @typescript-eslint/naming-convention
22
- '.cts': defaultLoadersSync['.ts'],
23
- // eslint-disable-next-line @typescript-eslint/naming-convention
24
- '.mjs': defaultLoadersSync['.js'],
25
- // eslint-disable-next-line @typescript-eslint/naming-convention
26
- '.cjs': defaultLoadersSync['.cjs']
27
- }
28
- });
29
-
30
- const explorerSync = cosmiconfigSync('styles', {
31
- searchPlaces: [
32
- 'styles.config.ts',
33
- 'styles.config.js',
34
- 'styles.config.mts',
35
- 'styles.config.mjs',
36
- 'styles.config.cjs',
37
- 'styles.config.cts'
38
- ],
39
- loaders: {
40
- // eslint-disable-next-line @typescript-eslint/naming-convention
41
- '.mts': defaultLoadersSync['.ts'],
42
- // eslint-disable-next-line @typescript-eslint/naming-convention
43
- '.cts': defaultLoadersSync['.ts'],
44
- // eslint-disable-next-line @typescript-eslint/naming-convention
45
- '.mjs': defaultLoadersSync['.js'],
46
- // eslint-disable-next-line @typescript-eslint/naming-convention
47
- '.cjs': defaultLoadersSync['.cjs']
48
- }
49
- });
50
-
51
- /**
52
- * Load styles configuration from the file system (async)
53
- * @param searchFrom - Directory to search from (defaults to process.cwd())
54
- * @returns Configuration object or null if not found
55
- */
56
- export async function loadConfig(searchFrom?: string): Promise<StylesConfig | null> {
57
- const result = await explorer.search(searchFrom);
58
- return result?.config ? normalizeConfig(result.config) : null;
59
- }
60
-
61
- /**
62
- * Load styles configuration from the file system (sync)
63
- * @param searchFrom - Directory to search from (defaults to process.cwd())
64
- * @returns Configuration object or empty object if not found
65
- */
66
- export function loadConfigSync(searchFrom?: string): StylesConfig {
67
- const result = explorerSync.search(searchFrom);
68
- return result?.config ? normalizeConfig(result.config) : {};
69
- }
70
-
71
- /**
72
- * Load styles configuration with metadata (sync).
73
- * Includes config file path when a config file is discovered.
74
- */
75
- export function loadConfigSyncWithMeta(searchFrom?: string): LoadedConfigMeta {
76
- const result = explorerSync.search(searchFrom);
77
- return {
78
- config: result?.config ? normalizeConfig(result.config) : {},
79
- configFilePath: result?.filepath
80
- };
81
- }
82
-
83
- /**
84
- * Load styles configuration from a specific file path (async)
85
- * @param filePath - Path to the config file
86
- * @returns Configuration object or null if not found
87
- */
88
- export async function loadConfigFromPath(filePath: string): Promise<StylesConfig | null> {
89
- const result = await explorer.load(filePath);
90
- return result?.config ? normalizeConfig(result.config) : null;
91
- }
92
-
93
- /**
94
- * Load styles configuration from a specific file path (sync)
95
- * @param filePath - Path to the config file
96
- * @returns Configuration object or empty object if not found
97
- */
98
- export function loadConfigFromPathSync(filePath: string): StylesConfig {
99
- const result = explorerSync.load(filePath);
100
- return result?.config ? normalizeConfig(result.config) : {};
101
- }
102
-
103
- /**
104
- * Normalize config object - handle default exports and ensure proper type
105
- */
106
- function normalizeConfig(config: any): StylesConfig {
107
- // Handle default export (common in ES modules)
108
- if (typeof config === 'object' && config !== null && 'default' in config) {
109
- return config.default as StylesConfig;
110
- }
111
- return config as StylesConfig;
112
- }
package/src/options.ts DELETED
@@ -1,172 +0,0 @@
1
- import type { StylesConfig, FileMatchOptions } from './types.js';
2
- import picomatch from 'picomatch';
3
- import path from 'path';
4
-
5
- /**
6
- * Options for retrieving merged configuration
7
- */
8
- export interface GetOptionsParams {
9
- /**
10
- * Language key to get options for (e.g., 'less', 'scss', 'jess').
11
- * If omitted but `input` is provided, language is inferred from the file extension.
12
- */
13
- language?: string;
14
- /**
15
- * Input file path to match against input options.
16
- * Also used to infer language if `language` is not specified.
17
- */
18
- input?: string;
19
- /**
20
- * Output file path to match against output options
21
- */
22
- output?: string;
23
- }
24
-
25
- /**
26
- * Map of file extensions to language keys
27
- */
28
- const extensionToLanguage = new Map<string, string>([
29
- ['.less', 'less'],
30
- ['.scss', 'scss'],
31
- ['.sass', 'scss'],
32
- ['.jess', 'jess'],
33
- ['.css', 'css']
34
- ]);
35
-
36
- /**
37
- * Infer language from a file path's extension
38
- */
39
- function inferLanguage(filePath: string | undefined): string | undefined {
40
- if (!filePath) {
41
- return undefined;
42
- }
43
- const ext = path.extname(filePath).toLowerCase();
44
- return extensionToLanguage.get(ext);
45
- }
46
-
47
- /**
48
- * Check if a file path matches a pattern (exact path, relative path, or glob)
49
- */
50
- function matchesFile(pattern: string | undefined, filePath: string | undefined): boolean {
51
- if (!pattern || !filePath) {
52
- return false;
53
- }
54
-
55
- // Normalize paths for comparison
56
- const normalizedPattern = path.normalize(pattern);
57
- const normalizedFile = path.normalize(filePath);
58
-
59
- // Try exact match first
60
- if (normalizedPattern === normalizedFile) {
61
- return true;
62
- }
63
-
64
- // Try basename match (e.g., pattern "styles.less" matches "/path/to/styles.less")
65
- if (path.basename(normalizedFile) === normalizedPattern) {
66
- return true;
67
- }
68
-
69
- // Try glob/pattern match using picomatch
70
- const isMatch = picomatch(pattern, { dot: true });
71
- return isMatch(filePath) || isMatch(normalizedFile);
72
- }
73
-
74
- /**
75
- * Get matching options from an array of file-based options.
76
- * Returns merged options from:
77
- * 1. All entries without a `file` property (defaults)
78
- * 2. All entries whose `file` pattern matches the given path
79
- *
80
- * Later entries override earlier ones.
81
- */
82
- function getMatchingOptions<T extends FileMatchOptions>(
83
- options: T | T[] | undefined,
84
- filePath?: string
85
- ): Partial<T> {
86
- if (!options) {
87
- return {};
88
- }
89
-
90
- const optionsArray = Array.isArray(options) ? options : [options];
91
- let result: Partial<T> = {};
92
-
93
- for (const opt of optionsArray) {
94
- // Include if: no file pattern (default), or file pattern matches
95
- if (!opt.file || (filePath && matchesFile(opt.file, filePath))) {
96
- // Merge this entry's options, excluding the 'file' property
97
- const rest = { ...opt } as Record<string, unknown>;
98
- delete rest.file;
99
- result = { ...result, ...rest };
100
- }
101
- }
102
-
103
- return result;
104
- }
105
- /**
106
- * Get merged options by combining compile, language, input, and output settings.
107
- *
108
- * Merge priority (later wins):
109
- * 1. compile options (base)
110
- * 2. language-specific options (inferred from input extension or explicitly specified)
111
- * 3. matched input options (if input path provided and matches)
112
- * 4. matched output options (if output path provided and matches)
113
- *
114
- * @param config - The styles configuration object
115
- * @param params - Options specifying language, input file, and output file
116
- * @returns Merged options object
117
- *
118
- * @example
119
- * // Get Less options for a specific input/output (language inferred from .less extension)
120
- * const options = getOptions(config, {
121
- * input: 'src/styles/main.less',
122
- * output: 'dist/main.css'
123
- * });
124
- *
125
- * @example
126
- * // Explicitly specify language
127
- * const options = getOptions(config, { language: 'less' });
128
- *
129
- * @example
130
- * // Get base options without language-specific settings
131
- * const options = getOptions(config);
132
- */
133
- export function getOptions(
134
- config: StylesConfig = {},
135
- params: GetOptionsParams = {}
136
- ): Record<string, any> {
137
- const { input: inputFile, output: outputFile } = params;
138
- const { compile = {}, input, output, language: languageConfig = {} } = config;
139
-
140
- // Determine language: explicit param > inferred from input extension
141
- const language = params.language ?? inferLanguage(inputFile);
142
-
143
- // Get language-specific options if language is determined
144
- const languageOptions = language ? (languageConfig[language] ?? {}) : {};
145
-
146
- // Get matched input and output options
147
- const matchedInput = getMatchingOptions(input, inputFile);
148
- const matchedOutput = getMatchingOptions(output, outputFile);
149
-
150
- // Build result with proper merge priority:
151
- // 1. compile (base)
152
- // 2. language-specific
153
- // 3. matched input
154
- // 4. matched output
155
- return {
156
- // Start with compile-level settings
157
- mathMode: compile.mathMode,
158
- unitMode: compile.unitMode,
159
- equalityMode: compile.equalityMode,
160
- paths: compile.searchPaths,
161
- javascriptEnabled: compile.enableJavaScript,
162
-
163
- // Override with language-specific settings
164
- ...languageOptions,
165
-
166
- // Override with matched input settings
167
- ...matchedInput,
168
-
169
- // Override with matched output settings
170
- ...matchedOutput
171
- };
172
- }
@@ -1,256 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { getOptions } from '../src/options.js';
3
- import type { StylesConfig } from '../src/types.js';
4
-
5
- describe('getOptions', () => {
6
- describe('language inference from input extension', () => {
7
- it('should infer language from .less extension', () => {
8
- const config: StylesConfig = {
9
- language: {
10
- less: { leakyRules: true }
11
- }
12
- };
13
- const options = getOptions(config, { input: 'src/styles.less' });
14
- expect(options.leakyRules).toBe(true);
15
- });
16
-
17
- it('should infer language from .scss extension', () => {
18
- const config: StylesConfig = {
19
- language: {
20
- scss: { precision: 10 }
21
- }
22
- };
23
- const options = getOptions(config, { input: 'src/styles.scss' });
24
- expect(options.precision).toBe(10);
25
- });
26
-
27
- it('should infer language from .sass extension', () => {
28
- const config: StylesConfig = {
29
- language: {
30
- scss: { indentedSyntax: true }
31
- }
32
- };
33
- const options = getOptions(config, { input: 'src/styles.sass' });
34
- expect(options.indentedSyntax).toBe(true);
35
- });
36
-
37
- it('should infer language from .jess extension', () => {
38
- const config: StylesConfig = {
39
- language: {
40
- jess: { customOption: 'value' }
41
- }
42
- };
43
- const options = getOptions(config, { input: 'src/styles.jess' });
44
- expect(options.customOption).toBe('value');
45
- });
46
-
47
- it('should allow explicit language to override inferred', () => {
48
- const config: StylesConfig = {
49
- language: {
50
- less: { leakyRules: true },
51
- scss: { precision: 10 }
52
- }
53
- };
54
- // File is .less but we explicitly request scss options
55
- const options = getOptions(config, { language: 'scss', input: 'src/styles.less' });
56
- expect(options.precision).toBe(10);
57
- expect(options.leakyRules).toBeUndefined();
58
- });
59
- });
60
-
61
- describe('merge priority', () => {
62
- it('should merge compile options as base', () => {
63
- const config: StylesConfig = {
64
- compile: {
65
- mathMode: 'parens-division',
66
- unitMode: 'loose',
67
- equalityMode: 'coerce'
68
- }
69
- };
70
- const options = getOptions(config);
71
- expect(options.mathMode).toBe('parens-division');
72
- expect(options.unitMode).toBe('loose');
73
- expect(options.equalityMode).toBe('coerce');
74
- });
75
-
76
- it('should override compile options with language options', () => {
77
- const config: StylesConfig = {
78
- compile: {
79
- mathMode: 'parens-division'
80
- },
81
- language: {
82
- less: { mathMode: 'strict' }
83
- }
84
- };
85
- const options = getOptions(config, { language: 'less' });
86
- expect(options.mathMode).toBe('strict');
87
- });
88
-
89
- it('should override language options with matched input options', () => {
90
- const config: StylesConfig = {
91
- language: {
92
- less: { mathMode: 'parens-division', leakyRules: true }
93
- },
94
- input: [
95
- { mathMode: 'strict' }
96
- ]
97
- };
98
- const options = getOptions(config, { input: 'src/styles.less' });
99
- expect(options.mathMode).toBe('strict');
100
- expect(options.leakyRules).toBe(true); // from language.less
101
- });
102
-
103
- it('should override input options with matched output options', () => {
104
- const config: StylesConfig = {
105
- input: [
106
- { compress: false }
107
- ],
108
- output: [
109
- { compress: true }
110
- ]
111
- };
112
- const options = getOptions(config, { input: 'src/styles.less', output: 'dist/styles.css' });
113
- expect(options.compress).toBe(true);
114
- });
115
-
116
- it('should apply full merge priority chain', () => {
117
- const config: StylesConfig = {
118
- compile: {
119
- mathMode: 'always',
120
- unitMode: 'loose',
121
- equalityMode: 'coerce'
122
- },
123
- language: {
124
- less: {
125
- mathMode: 'parens-division',
126
- leakyRules: true
127
- }
128
- },
129
- input: [
130
- { mathMode: 'strict', collapseNesting: false }
131
- ],
132
- output: [
133
- { compress: true, sourceMap: true }
134
- ]
135
- };
136
- const options = getOptions(config, { input: 'src/styles.less', output: 'dist/styles.css' });
137
- expect(options.unitMode).toBe('loose'); // from compile
138
- expect(options.equalityMode).toBe('coerce'); // from compile
139
- expect(options.leakyRules).toBe(true); // from language.less
140
- expect(options.mathMode).toBe('strict'); // from input (overrides language)
141
- expect(options.collapseNesting).toBe(false); // from input
142
- expect(options.compress).toBe(true); // from output
143
- expect(options.sourceMap).toBe(true); // from output
144
- });
145
- });
146
-
147
- describe('file matching', () => {
148
- it('should match entries without file property as defaults', () => {
149
- const config: StylesConfig = {
150
- input: [
151
- { leakyRules: true }
152
- ]
153
- };
154
- const options = getOptions(config, { input: 'any/file.less' });
155
- expect(options.leakyRules).toBe(true);
156
- });
157
-
158
- it('should match exact file paths', () => {
159
- const config: StylesConfig = {
160
- input: [
161
- { leakyRules: false },
162
- { file: 'src/legacy.less', leakyRules: true }
163
- ]
164
- };
165
- const options = getOptions(config, { input: 'src/legacy.less' });
166
- expect(options.leakyRules).toBe(true);
167
- });
168
-
169
- it('should match basename patterns', () => {
170
- const config: StylesConfig = {
171
- input: [
172
- { leakyRules: false },
173
- { file: 'legacy.less', leakyRules: true }
174
- ]
175
- };
176
- const options = getOptions(config, { input: '/path/to/legacy.less' });
177
- expect(options.leakyRules).toBe(true);
178
- });
179
-
180
- it('should match glob patterns', () => {
181
- const config: StylesConfig = {
182
- input: [
183
- { leakyRules: false },
184
- { file: 'legacy/**/*.less', leakyRules: true }
185
- ]
186
- };
187
- const options = getOptions(config, { input: 'legacy/old/styles.less' });
188
- expect(options.leakyRules).toBe(true);
189
- });
190
-
191
- it('should not match when glob does not match', () => {
192
- const config: StylesConfig = {
193
- input: [
194
- { leakyRules: false },
195
- { file: 'legacy/**/*.less', leakyRules: true }
196
- ]
197
- };
198
- const options = getOptions(config, { input: 'modern/styles.less' });
199
- expect(options.leakyRules).toBe(false);
200
- });
201
-
202
- it('should merge multiple matching entries in order', () => {
203
- const config: StylesConfig = {
204
- input: [
205
- { mathMode: 'always', leakyRules: false },
206
- { file: '**/*.less', mathMode: 'parens-division' },
207
- { file: 'legacy/**/*.less', leakyRules: true }
208
- ]
209
- };
210
- const options = getOptions(config, { input: 'legacy/old.less' });
211
- expect(options.mathMode).toBe('parens-division'); // from **/*.less
212
- expect(options.leakyRules).toBe(true); // from legacy/**/*.less
213
- });
214
-
215
- it('should match output files with glob patterns', () => {
216
- const config: StylesConfig = {
217
- output: [
218
- { compress: false },
219
- { file: '**/*.min.css', compress: true }
220
- ]
221
- };
222
- const options = getOptions(config, { output: 'dist/styles.min.css' });
223
- expect(options.compress).toBe(true);
224
- });
225
- });
226
-
227
- describe('single object vs array', () => {
228
- it('should handle single input object', () => {
229
- const config: StylesConfig = {
230
- input: { leakyRules: true }
231
- };
232
- const options = getOptions(config, { input: 'src/styles.less' });
233
- expect(options.leakyRules).toBe(true);
234
- });
235
-
236
- it('should handle single output object', () => {
237
- const config: StylesConfig = {
238
- output: { compress: true }
239
- };
240
- const options = getOptions(config, { output: 'dist/styles.css' });
241
- expect(options.compress).toBe(true);
242
- });
243
- });
244
-
245
- describe('no config', () => {
246
- it('should return empty-ish object with no config', () => {
247
- const options = getOptions();
248
- expect(options).toBeDefined();
249
- });
250
-
251
- it('should return empty-ish object with empty config', () => {
252
- const options = getOptions({});
253
- expect(options).toBeDefined();
254
- });
255
- });
256
- });
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "./lib",
5
- "rootDir": "./src",
6
- "noEmit": false
7
- },
8
- "include": ["src/**/*"]
9
- }
10
-