chaincss 2.0.7 → 2.1.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.
- package/CHANGELOG.md +30 -0
- package/CODE_OF_CONDUCT.md +21 -0
- package/CONTRIBUTING.md +28 -0
- package/README.md +455 -226
- package/demo/demo/node_modules/caniuse-db/fulldata-json/data-2.0.json +1 -0
- package/demo/index.html +16 -0
- package/demo/package.json +20 -0
- package/demo/src/App.tsx +117 -0
- package/demo/src/chaincss-barrel.ts +9 -0
- package/demo/src/main.tsx +8 -0
- package/demo/src/styles.chain.ts +300 -0
- package/demo/vite.config.ts +46 -0
- package/dist/cli/commands/build.d.ts +0 -1
- package/dist/cli/commands/cache.d.ts +1 -0
- package/dist/cli/commands/init.d.ts +6 -3
- package/dist/cli/commands/timeline.d.ts +0 -1
- package/dist/cli/commands/watch.d.ts +0 -1
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +3213 -5296
- package/dist/cli/types.d.ts +51 -20
- package/dist/cli/utils/config-loader.d.ts +0 -1
- package/dist/cli/utils/file-utils.d.ts +27 -3
- package/dist/cli/utils/logger.d.ts +0 -1
- package/dist/compiler/Chain.d.ts +215 -0
- package/dist/compiler/animations.d.ts +76 -0
- package/dist/compiler/atomic-optimizer.d.ts +47 -12
- package/dist/compiler/breakpoints.d.ts +46 -0
- package/dist/compiler/btt.d.ts +36 -60
- package/dist/compiler/cache-manager.d.ts +58 -4
- package/dist/compiler/commonProps.d.ts +0 -1
- package/dist/compiler/content-addressable-cache.d.ts +78 -0
- package/dist/compiler/helpers.d.ts +54 -0
- package/dist/compiler/index.d.ts +16 -9
- package/dist/compiler/index.js +4450 -4316
- package/dist/compiler/prefixer.d.ts +17 -1
- package/dist/compiler/shorthands.d.ts +28 -0
- package/dist/compiler/suggestions.d.ts +43 -0
- package/dist/compiler/theme-contract.d.ts +16 -27
- package/dist/compiler/token-resolver.d.ts +69 -0
- package/dist/compiler/tokens.d.ts +33 -8
- package/dist/core/auto-detector.d.ts +34 -0
- package/dist/core/common-utils.d.ts +97 -0
- package/dist/core/compiler.d.ts +63 -23
- package/dist/core/constants.d.ts +137 -36
- package/dist/core/smart-chain.d.ts +3 -0
- package/dist/core/types.d.ts +122 -15
- package/dist/core/utils.d.ts +134 -17
- package/dist/index.d.ts +52 -8
- package/dist/index.js +7090 -5578
- package/dist/plugins/vite.d.ts +7 -5
- package/dist/plugins/vite.js +2964 -25641
- package/dist/plugins/webpack.d.ts +24 -1
- package/dist/plugins/webpack.js +209 -72
- package/dist/runtime/Chain.d.ts +32 -0
- package/dist/runtime/auto-hooks.d.ts +11 -0
- package/dist/runtime/hmr.d.ts +22 -2
- package/dist/runtime/index.d.ts +3 -2
- package/dist/runtime/index.js +3648 -301
- package/dist/runtime/injector.d.ts +39 -72
- package/dist/runtime/react.d.ts +17 -12
- package/dist/runtime/svelte.d.ts +15 -0
- package/dist/runtime/types.d.ts +126 -4
- package/dist/runtime/utils.d.ts +0 -1
- package/dist/runtime/vue.d.ts +34 -14
- package/package.json +59 -66
- package/src/cli/commands/build.ts +133 -0
- package/src/cli/commands/cache.ts +371 -0
- package/src/cli/commands/init.ts +230 -0
- package/src/cli/commands/timeline.ts +435 -0
- package/src/cli/commands/watch.ts +211 -0
- package/src/cli/index.ts +226 -0
- package/src/cli/types.ts +100 -0
- package/src/cli/utils/config-loader.ts +174 -0
- package/src/cli/utils/file-utils.ts +139 -0
- package/src/cli/utils/logger.ts +74 -0
- package/src/compiler/Chain.ts +831 -0
- package/src/compiler/animations.ts +517 -0
- package/src/compiler/atomic-optimizer.ts +786 -0
- package/src/compiler/breakpoints.ts +347 -0
- package/src/compiler/btt.ts +1147 -0
- package/src/compiler/cache-manager.ts +446 -0
- package/src/compiler/commonProps.ts +18 -0
- package/src/compiler/content-addressable-cache.ts +478 -0
- package/src/compiler/helpers.ts +407 -0
- package/src/compiler/index.ts +72 -0
- package/src/compiler/prefixer.ts +720 -0
- package/src/compiler/shorthands.ts +558 -0
- package/src/compiler/suggestions.ts +436 -0
- package/src/compiler/theme-contract.ts +197 -0
- package/src/compiler/token-resolver.ts +241 -0
- package/src/compiler/tokens.ts +612 -0
- package/src/core/auto-detector.ts +187 -0
- package/src/core/common-utils.ts +423 -0
- package/src/core/compiler.ts +835 -0
- package/src/core/constants.ts +424 -0
- package/src/core/index.ts +107 -0
- package/src/core/smart-chain.ts +163 -0
- package/src/core/types.ts +257 -0
- package/src/core/utils.ts +598 -0
- package/src/index.ts +208 -0
- package/src/plugins/vite.d.ts +316 -0
- package/src/plugins/vite.ts +424 -0
- package/src/plugins/webpack.d.ts +289 -0
- package/src/plugins/webpack.ts +416 -0
- package/src/runtime/Chain.ts +242 -0
- package/src/runtime/auto-hooks.tsx +127 -0
- package/src/runtime/auto-vue.ts +72 -0
- package/src/runtime/hmr.ts +212 -0
- package/src/runtime/index.ts +82 -0
- package/src/runtime/injector.ts +273 -0
- package/src/runtime/react.tsx +269 -0
- package/src/runtime/svelte.ts +15 -0
- package/src/runtime/types.ts +256 -0
- package/src/runtime/utils.ts +128 -0
- package/src/runtime/vite-env.d.ts +120 -0
- package/src/runtime/vue.ts +231 -0
- package/tsconfig.build.json +41 -0
- package/tsconfig.json +25 -0
- package/tsconfig.runtimes.json +18 -0
- package/dist/cli/cli.cjs +0 -7
- package/dist/cli/commands/build.d.ts.map +0 -1
- package/dist/cli/commands/compile.d.ts +0 -3
- package/dist/cli/commands/compile.d.ts.map +0 -1
- package/dist/cli/commands/init.d.ts.map +0 -1
- package/dist/cli/commands/timeline.d.ts.map +0 -1
- package/dist/cli/commands/watch.d.ts.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/types.d.ts.map +0 -1
- package/dist/cli/utils/config-loader.d.ts.map +0 -1
- package/dist/cli/utils/file-utils.d.ts.map +0 -1
- package/dist/cli/utils/logger.d.ts.map +0 -1
- package/dist/compiler/atomic-optimizer.d.ts.map +0 -1
- package/dist/compiler/btt.d.ts.map +0 -1
- package/dist/compiler/cache-manager.d.ts.map +0 -1
- package/dist/compiler/commonProps.d.ts.map +0 -1
- package/dist/compiler/index.d.ts.map +0 -1
- package/dist/compiler/prefixer.d.ts.map +0 -1
- package/dist/compiler/theme-contract.d.ts.map +0 -1
- package/dist/compiler/tokens.d.ts.map +0 -1
- package/dist/compiler/types.d.ts +0 -57
- package/dist/compiler/types.d.ts.map +0 -1
- package/dist/core/compiler.d.ts.map +0 -1
- package/dist/core/constants.d.ts.map +0 -1
- package/dist/core/index.d.ts +0 -4
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/utils.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/plugins/vite.d.ts.map +0 -1
- package/dist/plugins/webpack.d.ts.map +0 -1
- package/dist/runtime/hmr.d.ts.map +0 -1
- package/dist/runtime/index.d.ts.map +0 -1
- package/dist/runtime/injector.d.ts.map +0 -1
- package/dist/runtime/react.d.ts.map +0 -1
- package/dist/runtime/react.js +0 -324
- package/dist/runtime/types.d.ts.map +0 -1
- package/dist/runtime/utils.d.ts.map +0 -1
- package/dist/runtime/vue.d.ts.map +0 -1
- package/dist/runtime/vue.js +0 -286
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
// chaincss/src/core/utils.ts
|
|
2
|
+
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 1. RE-EXPORTS
|
|
9
|
+
* Re-exporting everything from common-utils ensures the Compiler
|
|
10
|
+
* can still find processStyleObject, resolveToken, and kebabCase
|
|
11
|
+
* without changing its import statements.
|
|
12
|
+
*/
|
|
13
|
+
export * from './common-utils.js';
|
|
14
|
+
|
|
15
|
+
// Import types
|
|
16
|
+
import type { StyleDefinition } from '../compiler/btt.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 2. HASHING & NAMING (Node/Compiler Only)
|
|
20
|
+
*/
|
|
21
|
+
export function hashString(str: string, length: number = 6): string {
|
|
22
|
+
return crypto.createHash('sha1').update(str).digest('hex').slice(0, length);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function generateClassName(styleId: string, naming: 'hash' | 'readable' = 'hash'): string {
|
|
26
|
+
if (naming === 'hash') {
|
|
27
|
+
return `c_${hashString(styleId)}`;
|
|
28
|
+
}
|
|
29
|
+
// Clean the styleId for readable names
|
|
30
|
+
const cleanId = styleId
|
|
31
|
+
.replace(/[^a-zA-Z0-9]/g, '-')
|
|
32
|
+
.replace(/-+/g, '-')
|
|
33
|
+
.replace(/^-|-$/g, '')
|
|
34
|
+
.toLowerCase();
|
|
35
|
+
return `chain-${cleanId}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Generate atomic class name
|
|
39
|
+
export function generateAtomicClassName(prop: string, value: string, type: 'atomic' | 'utility' = 'atomic'): string {
|
|
40
|
+
const hash = hashString(`${prop}:${value}`, 6);
|
|
41
|
+
const propKebab = kebabCase(prop);
|
|
42
|
+
|
|
43
|
+
if (type === 'utility') {
|
|
44
|
+
return `u-${propKebab}-${hash}`;
|
|
45
|
+
}
|
|
46
|
+
return `a-${propKebab}-${hash}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Generate component class name with hash for uniqueness
|
|
50
|
+
export function generateComponentClassName(componentName: string, hash?: string): string {
|
|
51
|
+
const cleanName = componentName
|
|
52
|
+
.replace(/[^a-zA-Z0-9]/g, '-')
|
|
53
|
+
.replace(/-+/g, '-')
|
|
54
|
+
.replace(/^-|-$/g, '')
|
|
55
|
+
.toLowerCase();
|
|
56
|
+
|
|
57
|
+
const suffix = hash || hashString(componentName, 4);
|
|
58
|
+
return `c-${cleanName}-${suffix}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 3. OBJECT MANIPULATION
|
|
63
|
+
*/
|
|
64
|
+
export function deepMerge<T extends Record<string, any>>(target: T, source: Partial<T>): T {
|
|
65
|
+
const result = { ...target } as any;
|
|
66
|
+
|
|
67
|
+
for (const key in source) {
|
|
68
|
+
const srcVal = source[key];
|
|
69
|
+
const targetVal = result[key];
|
|
70
|
+
|
|
71
|
+
if (srcVal && typeof srcVal === 'object' && !Array.isArray(srcVal)) {
|
|
72
|
+
result[key] = deepMerge(targetVal || {}, srcVal);
|
|
73
|
+
} else if (srcVal !== undefined) {
|
|
74
|
+
result[key] = srcVal;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Deep clone an object
|
|
82
|
+
export function deepClone<T>(obj: T): T {
|
|
83
|
+
if (obj === null || typeof obj !== 'object') return obj;
|
|
84
|
+
if (Array.isArray(obj)) return obj.map(item => deepClone(item)) as any;
|
|
85
|
+
|
|
86
|
+
const cloned: any = {};
|
|
87
|
+
for (const key in obj) {
|
|
88
|
+
if (obj.hasOwnProperty(key)) {
|
|
89
|
+
cloned[key] = deepClone(obj[key]);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return cloned;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check if two objects are deeply equal
|
|
96
|
+
export function deepEqual(obj1: any, obj2: any): boolean {
|
|
97
|
+
if (obj1 === obj2) return true;
|
|
98
|
+
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return false;
|
|
99
|
+
if (obj1 === null || obj2 === null) return false;
|
|
100
|
+
|
|
101
|
+
const keys1 = Object.keys(obj1);
|
|
102
|
+
const keys2 = Object.keys(obj2);
|
|
103
|
+
|
|
104
|
+
if (keys1.length !== keys2.length) return false;
|
|
105
|
+
|
|
106
|
+
for (const key of keys1) {
|
|
107
|
+
if (!keys2.includes(key)) return false;
|
|
108
|
+
if (!deepEqual(obj1[key], obj2[key])) return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Pick specific keys from an object
|
|
115
|
+
export function pick<T extends Record<string, any>, K extends keyof T>(
|
|
116
|
+
obj: T,
|
|
117
|
+
keys: K[]
|
|
118
|
+
): Pick<T, K> {
|
|
119
|
+
const result: Partial<T> = {};
|
|
120
|
+
for (const key of keys) {
|
|
121
|
+
if (key in obj) {
|
|
122
|
+
result[key] = obj[key];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return result as Pick<T, K>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Omit specific keys from an object
|
|
129
|
+
export function omit<T extends Record<string, any>, K extends keyof T>(
|
|
130
|
+
obj: T,
|
|
131
|
+
keys: K[]
|
|
132
|
+
): Omit<T, K> {
|
|
133
|
+
const result = { ...obj };
|
|
134
|
+
for (const key of keys) {
|
|
135
|
+
delete result[key];
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 4. FILE SYSTEM UTILS (Node Only)
|
|
142
|
+
*/
|
|
143
|
+
export function ensureDir(dir: string): void {
|
|
144
|
+
if (!fs.existsSync(dir)) {
|
|
145
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function writeFile(filePath: string, content: string): void {
|
|
150
|
+
const dir = path.dirname(filePath);
|
|
151
|
+
ensureDir(dir);
|
|
152
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function readFile(filePath: string): string {
|
|
156
|
+
if (!fs.existsSync(filePath)) {
|
|
157
|
+
throw new Error(`File not found: ${filePath}`);
|
|
158
|
+
}
|
|
159
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function fileExists(filePath: string): boolean {
|
|
163
|
+
return fs.existsSync(filePath);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function getFileExtension(filePath: string): string {
|
|
167
|
+
return path.extname(filePath);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function getBaseName(filePath: string): string {
|
|
171
|
+
return path.basename(filePath, path.extname(filePath));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function getDirName(filePath: string): string {
|
|
175
|
+
return path.dirname(filePath);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Resolve a relative path from current working directory
|
|
179
|
+
export function resolvePath(filePath: string): string {
|
|
180
|
+
return path.resolve(process.cwd(), filePath);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Check if a path is a directory
|
|
184
|
+
export function isDirectory(filePath: string): boolean {
|
|
185
|
+
try {
|
|
186
|
+
return fs.statSync(filePath).isDirectory();
|
|
187
|
+
} catch {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Get all files recursively in a directory
|
|
193
|
+
export function getAllFiles(dir: string, pattern?: RegExp): string[] {
|
|
194
|
+
const files: string[] = [];
|
|
195
|
+
|
|
196
|
+
const readDir = (currentDir: string) => {
|
|
197
|
+
const entries = fs.readdirSync(currentDir);
|
|
198
|
+
|
|
199
|
+
for (const entry of entries) {
|
|
200
|
+
const fullPath = path.join(currentDir, entry);
|
|
201
|
+
const stat = fs.statSync(fullPath);
|
|
202
|
+
|
|
203
|
+
if (stat.isDirectory()) {
|
|
204
|
+
readDir(fullPath);
|
|
205
|
+
} else if (!pattern || pattern.test(fullPath)) {
|
|
206
|
+
files.push(fullPath);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
readDir(dir);
|
|
212
|
+
return files;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 5. FORMATTING
|
|
217
|
+
*/
|
|
218
|
+
export function formatCSS(css: string, minify: boolean = false): string {
|
|
219
|
+
if (!css || css.trim().length === 0) return '';
|
|
220
|
+
|
|
221
|
+
if (!minify) {
|
|
222
|
+
// Format nicely with proper indentation
|
|
223
|
+
let formatted = css
|
|
224
|
+
.replace(/\s*{\s*/g, ' {\n ')
|
|
225
|
+
.replace(/;\s*/g, ';\n ')
|
|
226
|
+
.replace(/\s*}\s*/g, '\n}\n')
|
|
227
|
+
.replace(/\n\s*\n/g, '\n');
|
|
228
|
+
|
|
229
|
+
// Ensure proper spacing for media queries
|
|
230
|
+
formatted = formatted.replace(/@media[^{]+\{/g, match => {
|
|
231
|
+
return match.replace(/\{/, ' {\n');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
return formatted.trim();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Minify CSS
|
|
238
|
+
return css
|
|
239
|
+
.replace(/\/\*.*?\*\//g, '') // Remove comments
|
|
240
|
+
.replace(/\s+/g, ' ') // Collapse whitespace
|
|
241
|
+
.replace(/\s*{\s*/g, '{') // Remove spaces around braces
|
|
242
|
+
.replace(/\s*}\s*/g, '}') // Remove spaces around braces
|
|
243
|
+
.replace(/\s*;\s*/g, ';') // Remove spaces around semicolons
|
|
244
|
+
.replace(/\s*:\s*/g, ':') // Remove spaces around colons
|
|
245
|
+
.replace(/;}/g, '}') // Remove last semicolon
|
|
246
|
+
.trim();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Format JavaScript/TypeScript code
|
|
250
|
+
export function formatJS(code: string, minify: boolean = false): string {
|
|
251
|
+
if (!minify) {
|
|
252
|
+
return code;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return code
|
|
256
|
+
.replace(/\/\/.*$/gm, '') // Remove single-line comments
|
|
257
|
+
.replace(/\/\*.*?\*\//g, '') // Remove multi-line comments
|
|
258
|
+
.replace(/\s+/g, ' ') // Collapse whitespace
|
|
259
|
+
.trim();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Convert string to kebab-case
|
|
263
|
+
export function kebabCase(str: string): string {
|
|
264
|
+
return str
|
|
265
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
266
|
+
.replace(/[\s_]+/g, '-')
|
|
267
|
+
.toLowerCase();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Convert string to camelCase
|
|
271
|
+
export function camelCase(str: string): string {
|
|
272
|
+
return str
|
|
273
|
+
.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : '')
|
|
274
|
+
.replace(/^[A-Z]/, char => char.toLowerCase());
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Convert string to PascalCase
|
|
278
|
+
export function pascalCase(str: string): string {
|
|
279
|
+
const camel = camelCase(str);
|
|
280
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Convert string to snake_case
|
|
284
|
+
export function snakeCase(str: string): string {
|
|
285
|
+
return str
|
|
286
|
+
.replace(/([a-z])([A-Z])/g, '$1_$2')
|
|
287
|
+
.replace(/[\s-]+/g, '_')
|
|
288
|
+
.toLowerCase();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* 6. STRING UTILITIES
|
|
293
|
+
*/
|
|
294
|
+
export function truncate(str: string, length: number, suffix: string = '...'): string {
|
|
295
|
+
if (str.length <= length) return str;
|
|
296
|
+
return str.slice(0, length - suffix.length) + suffix;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function indent(str: string, level: number = 1, char: string = ' '): string {
|
|
300
|
+
const indentStr = char.repeat(level);
|
|
301
|
+
return str
|
|
302
|
+
.split('\n')
|
|
303
|
+
.map(line => indentStr + line)
|
|
304
|
+
.join('\n');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function stripIndent(str: string): string {
|
|
308
|
+
const lines = str.split('\n');
|
|
309
|
+
const minIndent = Math.min(
|
|
310
|
+
...lines
|
|
311
|
+
.filter(line => line.trim())
|
|
312
|
+
.map(line => line.match(/^\s*/)?.[0].length || 0)
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
return lines
|
|
316
|
+
.map(line => line.slice(minIndent))
|
|
317
|
+
.join('\n')
|
|
318
|
+
.trim();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* 7. ARRAY UTILITIES
|
|
323
|
+
*/
|
|
324
|
+
export function unique<T>(arr: T[], key?: keyof T): T[] {
|
|
325
|
+
if (!key) {
|
|
326
|
+
return [...new Set(arr)];
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const seen = new Set();
|
|
330
|
+
return arr.filter(item => {
|
|
331
|
+
const value = item[key];
|
|
332
|
+
if (seen.has(value)) return false;
|
|
333
|
+
seen.add(value);
|
|
334
|
+
return true;
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export function chunk<T>(arr: T[], size: number): T[][] {
|
|
339
|
+
const result: T[][] = [];
|
|
340
|
+
for (let i = 0; i < arr.length; i += size) {
|
|
341
|
+
result.push(arr.slice(i, i + size));
|
|
342
|
+
}
|
|
343
|
+
return result;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function groupBy<T>(arr: T[], key: keyof T): Record<string, T[]> {
|
|
347
|
+
const result: Record<string, T[]> = {};
|
|
348
|
+
|
|
349
|
+
for (const item of arr) {
|
|
350
|
+
const groupKey = String(item[key]);
|
|
351
|
+
if (!result[groupKey]) {
|
|
352
|
+
result[groupKey] = [];
|
|
353
|
+
}
|
|
354
|
+
result[groupKey].push(item);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return result;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* 8. PERFORMANCE UTILITIES
|
|
362
|
+
*/
|
|
363
|
+
export function debounce<T extends (...args: any[]) => any>(
|
|
364
|
+
fn: T,
|
|
365
|
+
delay: number
|
|
366
|
+
): (...args: Parameters<T>) => void {
|
|
367
|
+
let timeout: NodeJS.Timeout | null = null;
|
|
368
|
+
|
|
369
|
+
return (...args: Parameters<T>) => {
|
|
370
|
+
if (timeout) clearTimeout(timeout);
|
|
371
|
+
timeout = setTimeout(() => fn(...args), delay);
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function throttle<T extends (...args: any[]) => any>(
|
|
376
|
+
fn: T,
|
|
377
|
+
limit: number
|
|
378
|
+
): (...args: Parameters<T>) => void {
|
|
379
|
+
let inThrottle = false;
|
|
380
|
+
|
|
381
|
+
return (...args: Parameters<T>) => {
|
|
382
|
+
if (!inThrottle) {
|
|
383
|
+
fn(...args);
|
|
384
|
+
inThrottle = true;
|
|
385
|
+
setTimeout(() => (inThrottle = false), limit);
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* 9. ERROR HANDLING
|
|
392
|
+
*/
|
|
393
|
+
export class ChainCSSError extends Error {
|
|
394
|
+
public code: string;
|
|
395
|
+
public details?: any;
|
|
396
|
+
|
|
397
|
+
constructor(message: string, code: string = 'CHAINCSS_ERROR', details?: any) {
|
|
398
|
+
super(message);
|
|
399
|
+
this.name = 'ChainCSSError';
|
|
400
|
+
this.code = code;
|
|
401
|
+
this.details = details;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export function tryOrWarn<T>(fn: () => T, defaultValue: T, message?: string): T {
|
|
406
|
+
try {
|
|
407
|
+
return fn();
|
|
408
|
+
} catch (error) {
|
|
409
|
+
if (message) {
|
|
410
|
+
console.warn(message, error);
|
|
411
|
+
}
|
|
412
|
+
return defaultValue;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function tryOrThrow<T>(fn: () => T, errorMessage?: string): T {
|
|
417
|
+
try {
|
|
418
|
+
return fn();
|
|
419
|
+
} catch (error) {
|
|
420
|
+
throw new ChainCSSError(
|
|
421
|
+
errorMessage || (error as Error).message,
|
|
422
|
+
'EXECUTION_ERROR',
|
|
423
|
+
error
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* 10. LOGGING UTILITIES (Node Only)
|
|
430
|
+
*/
|
|
431
|
+
const LOG_LEVELS = {
|
|
432
|
+
debug: 0,
|
|
433
|
+
info: 1,
|
|
434
|
+
warn: 2,
|
|
435
|
+
error: 3,
|
|
436
|
+
silent: 4
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
let currentLogLevel = process.env.DEBUG ? 'debug' : 'info';
|
|
440
|
+
|
|
441
|
+
export function setLogLevel(level: keyof typeof LOG_LEVELS): void {
|
|
442
|
+
currentLogLevel = level;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function shouldLog(level: keyof typeof LOG_LEVELS): boolean {
|
|
446
|
+
return LOG_LEVELS[level as keyof typeof LOG_LEVELS] >= LOG_LEVELS[currentLogLevel as keyof typeof LOG_LEVELS];
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export function logDebug(message: string, ...args: any[]): void {
|
|
450
|
+
if (shouldLog('debug')) {
|
|
451
|
+
console.debug(`[ChainCSS Debug] ${message}`, ...args);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export function logInfo(message: string, ...args: any[]): void {
|
|
456
|
+
if (shouldLog('info')) {
|
|
457
|
+
console.log(`[ChainCSS] ${message}`, ...args);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export function logWarn(message: string, ...args: any[]): void {
|
|
462
|
+
if (shouldLog('warn')) {
|
|
463
|
+
console.warn(`[ChainCSS Warning] ${message}`, ...args);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export function logError(message: string, ...args: any[]): void {
|
|
468
|
+
if (shouldLog('error')) {
|
|
469
|
+
console.error(`[ChainCSS Error] ${message}`, ...args);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* 11. MEMORY MANAGEMENT
|
|
475
|
+
*/
|
|
476
|
+
export function getMemoryUsage(): { rss: number; heapTotal: number; heapUsed: number; external: number } {
|
|
477
|
+
const usage = process.memoryUsage();
|
|
478
|
+
return {
|
|
479
|
+
rss: usage.rss,
|
|
480
|
+
heapTotal: usage.heapTotal,
|
|
481
|
+
heapUsed: usage.heapUsed,
|
|
482
|
+
external: usage.external
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export function formatBytes(bytes: number): string {
|
|
487
|
+
if (bytes === 0) return '0 Bytes';
|
|
488
|
+
|
|
489
|
+
const k = 1024;
|
|
490
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
491
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
492
|
+
|
|
493
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* 12. VALIDATION UTILITIES
|
|
498
|
+
*/
|
|
499
|
+
export function isValidSelector(selector: string): boolean {
|
|
500
|
+
// Basic selector validation
|
|
501
|
+
if (!selector || typeof selector !== 'string') return false;
|
|
502
|
+
if (selector.length > 100) return false;
|
|
503
|
+
|
|
504
|
+
// Check for invalid characters
|
|
505
|
+
const invalidChars = /[<>`~]/;
|
|
506
|
+
if (invalidChars.test(selector)) return false;
|
|
507
|
+
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export function isValidClassName(className: string): boolean {
|
|
512
|
+
if (!className || typeof className !== 'string') return false;
|
|
513
|
+
if (className.length > 50) return false;
|
|
514
|
+
|
|
515
|
+
// CSS class name regex (letters, numbers, hyphens, underscores)
|
|
516
|
+
const classNameRegex = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;
|
|
517
|
+
return classNameRegex.test(className);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
export function isValidCSSProperty(prop: string): boolean {
|
|
521
|
+
if (!prop || typeof prop !== 'string') return false;
|
|
522
|
+
// CSS property regex
|
|
523
|
+
const cssPropRegex = /^[a-z][a-z-]*$/;
|
|
524
|
+
return cssPropRegex.test(prop);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* 13. EXPORTS
|
|
529
|
+
*/
|
|
530
|
+
export default {
|
|
531
|
+
// Hashing
|
|
532
|
+
hashString,
|
|
533
|
+
generateClassName,
|
|
534
|
+
generateAtomicClassName,
|
|
535
|
+
generateComponentClassName,
|
|
536
|
+
|
|
537
|
+
// Objects
|
|
538
|
+
deepMerge,
|
|
539
|
+
deepClone,
|
|
540
|
+
deepEqual,
|
|
541
|
+
pick,
|
|
542
|
+
omit,
|
|
543
|
+
|
|
544
|
+
// Filesystem
|
|
545
|
+
ensureDir,
|
|
546
|
+
writeFile,
|
|
547
|
+
readFile,
|
|
548
|
+
fileExists,
|
|
549
|
+
getFileExtension,
|
|
550
|
+
getBaseName,
|
|
551
|
+
getDirName,
|
|
552
|
+
resolvePath,
|
|
553
|
+
isDirectory,
|
|
554
|
+
getAllFiles,
|
|
555
|
+
|
|
556
|
+
// Formatting
|
|
557
|
+
formatCSS,
|
|
558
|
+
formatJS,
|
|
559
|
+
kebabCase,
|
|
560
|
+
camelCase,
|
|
561
|
+
pascalCase,
|
|
562
|
+
snakeCase,
|
|
563
|
+
|
|
564
|
+
// Strings
|
|
565
|
+
truncate,
|
|
566
|
+
indent,
|
|
567
|
+
stripIndent,
|
|
568
|
+
|
|
569
|
+
// Arrays
|
|
570
|
+
unique,
|
|
571
|
+
chunk,
|
|
572
|
+
groupBy,
|
|
573
|
+
|
|
574
|
+
// Performance
|
|
575
|
+
debounce,
|
|
576
|
+
throttle,
|
|
577
|
+
|
|
578
|
+
// Errors
|
|
579
|
+
ChainCSSError,
|
|
580
|
+
tryOrWarn,
|
|
581
|
+
tryOrThrow,
|
|
582
|
+
|
|
583
|
+
// Logging
|
|
584
|
+
setLogLevel,
|
|
585
|
+
logDebug,
|
|
586
|
+
logInfo,
|
|
587
|
+
logWarn,
|
|
588
|
+
logError,
|
|
589
|
+
|
|
590
|
+
// Memory
|
|
591
|
+
getMemoryUsage,
|
|
592
|
+
formatBytes,
|
|
593
|
+
|
|
594
|
+
// Validation
|
|
595
|
+
isValidSelector,
|
|
596
|
+
isValidClassName,
|
|
597
|
+
isValidCSSProperty
|
|
598
|
+
};
|