emily-css 1.0.28 → 1.0.29

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 CHANGED
@@ -4,6 +4,14 @@ All notable changes to `emily-css` are documented here.
4
4
 
5
5
  ---
6
6
 
7
+ ## v1.0.29 — May 2026
8
+
9
+ **added json manifest for future use**
10
+
11
+ ### Added
12
+ - added json manifest for future use
13
+
14
+ ---
7
15
  ## v1.0.28 — May 2026
8
16
 
9
17
  **added new utilities**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emily-css",
3
- "version": "1.0.28",
3
+ "version": "1.0.29",
4
4
  "description": "A config-driven utility CSS framework. Define your brand once, generate the CSS.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
+ const { generateManifest } = require('./manifest');
6
7
 
7
8
 
8
9
  // ============================================================================
@@ -1479,6 +1480,32 @@ function getProductionCssPath(config) {
1479
1480
  return path.join(process.cwd(), config.output?.css || 'dist/emily.min.css');
1480
1481
  }
1481
1482
 
1483
+ function getManifestSettings(config) {
1484
+ const manifestConfig = config.manifest;
1485
+
1486
+ if (manifestConfig === true) {
1487
+ return { enabled: true, output: 'dist/emily.manifest.json' };
1488
+ }
1489
+
1490
+ if (manifestConfig && typeof manifestConfig === 'object') {
1491
+ return {
1492
+ enabled: manifestConfig.enabled === true,
1493
+ output: manifestConfig.output || 'dist/emily.manifest.json',
1494
+ };
1495
+ }
1496
+
1497
+ return { enabled: false, output: 'dist/emily.manifest.json' };
1498
+ }
1499
+
1500
+ function getManifestOutputPath(config) {
1501
+ const manifestSettings = getManifestSettings(config);
1502
+ const outputPath = manifestSettings.output || 'dist/emily.manifest.json';
1503
+
1504
+ return path.isAbsolute(outputPath)
1505
+ ? outputPath
1506
+ : path.join(process.cwd(), outputPath);
1507
+ }
1508
+
1482
1509
  function ensureDirectoryForFile(filePath) {
1483
1510
  const dir = path.dirname(filePath);
1484
1511
 
@@ -1700,6 +1727,16 @@ ${bodyFont}`;
1700
1727
  ensureDirectoryForFile(fullCssPath);
1701
1728
  fs.writeFileSync(fullCssPath, css);
1702
1729
 
1730
+ const manifestSettings = getManifestSettings(config);
1731
+ if (manifestSettings.enabled) {
1732
+ const manifestPath = getManifestOutputPath(config);
1733
+ const manifest = generateManifest(css, config);
1734
+
1735
+ ensureDirectoryForFile(manifestPath);
1736
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
1737
+ console.log(`✓ Generated manifest: ${manifestPath}`);
1738
+ }
1739
+
1703
1740
  console.log(`✓ Generated CSS: ${fullCssPath}`);
1704
1741
  console.log(`✓ File size: ${(css.length / 1024).toFixed(2)} KB (unminified)`);
1705
1742
  console.log('Full framework build complete');
@@ -1808,6 +1845,7 @@ module.exports = {
1808
1845
  addStateVariants,
1809
1846
  addAriaDataVariants,
1810
1847
  addResponsiveVariants,
1848
+ generateManifest,
1811
1849
  generateFontCSS,
1812
1850
  codeUtilities,
1813
1851
  };
@@ -0,0 +1,307 @@
1
+ const MANIFEST_VERSION = '1.1.0';
2
+ const DEFAULT_RESPONSIVE_VARIANTS = ['sm', 'md', 'lg', 'xl', '2xl'];
3
+ const BASE_VARIANTS = [
4
+ 'hover',
5
+ 'focus',
6
+ 'focus-within',
7
+ 'focus-visible',
8
+ 'active',
9
+ 'disabled',
10
+ 'motion-reduce',
11
+ 'motion-safe',
12
+ 'aria-expanded',
13
+ 'aria-selected',
14
+ 'aria-current',
15
+ 'aria-disabled',
16
+ 'data-open',
17
+ 'data-closed',
18
+ 'dark',
19
+ 'forced-colors',
20
+ ];
21
+
22
+ function parseDeclarations(block) {
23
+ const declarations = {};
24
+ const declarationRegex = /([a-zA-Z-]+)\s*:\s*([^;]+)\s*;?/g;
25
+ let firstProperty = null;
26
+ let firstValue = null;
27
+ let match;
28
+
29
+ while ((match = declarationRegex.exec(block)) !== null) {
30
+ const property = match[1].trim();
31
+ const value = match[2].trim();
32
+ declarations[property] = value;
33
+
34
+ if (!firstProperty) {
35
+ firstProperty = property;
36
+ firstValue = value;
37
+ }
38
+ }
39
+
40
+ return { declarations, firstProperty, firstValue };
41
+ }
42
+
43
+ function getTokenFromDeclarations(declarations) {
44
+ const values = Object.values(declarations);
45
+
46
+ for (const value of values) {
47
+ const tokenMatch = value.match(/var\(\s*(--[a-zA-Z0-9-_]+)\s*(?:,[^)]+)?\)/);
48
+ if (tokenMatch) {
49
+ return tokenMatch[1];
50
+ }
51
+ }
52
+
53
+ return null;
54
+ }
55
+
56
+ function isSimpleBaseClassSelector(selector) {
57
+ if (!selector || !selector.startsWith('.')) return false;
58
+ if (selector.includes(' ')) return false;
59
+ if (selector.includes(',')) return false;
60
+ if (selector.includes('[')) return false;
61
+ if (selector.includes(':')) return false;
62
+ if (selector.includes('::')) return false;
63
+ if (selector.includes('>')) return false;
64
+ if (selector.includes('+')) return false;
65
+ if (selector.includes('~')) return false;
66
+
67
+ return true;
68
+ }
69
+
70
+ function inferCategory(className, property) {
71
+ if (
72
+ className === 'prose' ||
73
+ className === 'prose-emily' ||
74
+ className === 'center-screen' ||
75
+ className === 'center-absolute' ||
76
+ className === 'field-container' ||
77
+ className === 'form-hint' ||
78
+ className === 'form-error-message' ||
79
+ className === 'error-summary' ||
80
+ className === 'btn' ||
81
+ className === 'btn-primary' ||
82
+ className === 'btn-secondary' ||
83
+ className === 'btn-ghost' ||
84
+ className === 'btn-danger' ||
85
+ className === 'btn-sm' ||
86
+ className === 'btn-lg' ||
87
+ className === 'code-window' ||
88
+ className === 'code-title-bar' ||
89
+ className === 'code-dot' ||
90
+ className === 'code-dot-red' ||
91
+ className === 'code-dot-yellow' ||
92
+ className === 'code-dot-green' ||
93
+ className === 'code-filename' ||
94
+ className.startsWith('token-')
95
+ ) {
96
+ return 'component';
97
+ }
98
+
99
+ if (className.startsWith('text-')) {
100
+ return property === 'color' ? 'colour' : 'typography';
101
+ }
102
+
103
+ if (
104
+ className.startsWith('font-') ||
105
+ className.startsWith('leading-') ||
106
+ className.startsWith('tracking-') ||
107
+ className.startsWith('list-')
108
+ ) {
109
+ return 'typography';
110
+ }
111
+
112
+ if (className.startsWith('bg-')) return 'background';
113
+
114
+ if (
115
+ className === 'border' ||
116
+ className.startsWith('border-') ||
117
+ className.startsWith('divide-') ||
118
+ className === 'outline' ||
119
+ className.startsWith('outline-') ||
120
+ className === 'ring' ||
121
+ className.startsWith('ring-')
122
+ ) {
123
+ return 'border';
124
+ }
125
+
126
+ if (
127
+ className.startsWith('p-') ||
128
+ className.startsWith('px-') ||
129
+ className.startsWith('py-') ||
130
+ className.startsWith('pt-') ||
131
+ className.startsWith('pr-') ||
132
+ className.startsWith('pb-') ||
133
+ className.startsWith('pl-') ||
134
+ className.startsWith('m-') ||
135
+ className.startsWith('mx-') ||
136
+ className.startsWith('my-') ||
137
+ className.startsWith('mt-') ||
138
+ className.startsWith('mr-') ||
139
+ className.startsWith('mb-') ||
140
+ className.startsWith('ml-') ||
141
+ className.startsWith('gap-') ||
142
+ className.startsWith('space-') ||
143
+ className.startsWith('inset-') ||
144
+ className.startsWith('top-') ||
145
+ className.startsWith('right-') ||
146
+ className.startsWith('bottom-') ||
147
+ className.startsWith('left-')
148
+ ) {
149
+ return 'spacing';
150
+ }
151
+
152
+ if (
153
+ className.startsWith('w-') ||
154
+ className.startsWith('h-') ||
155
+ className.startsWith('min-w-') ||
156
+ className.startsWith('max-w-') ||
157
+ className.startsWith('min-h-') ||
158
+ className.startsWith('max-h-') ||
159
+ className.startsWith('size-')
160
+ ) {
161
+ return 'sizing';
162
+ }
163
+
164
+ if (
165
+ className === 'flex' ||
166
+ className === 'grid' ||
167
+ className === 'block' ||
168
+ className === 'inline' ||
169
+ className === 'inline-block' ||
170
+ className === 'inline-flex' ||
171
+ className === 'hidden' ||
172
+ className === 'container' ||
173
+ className === 'relative' ||
174
+ className === 'absolute' ||
175
+ className === 'fixed' ||
176
+ className === 'sticky' ||
177
+ className === 'static'
178
+ ) {
179
+ return 'layout';
180
+ }
181
+
182
+ if (
183
+ className.startsWith('items-') ||
184
+ className.startsWith('justify-') ||
185
+ className.startsWith('content-') ||
186
+ className.startsWith('self-') ||
187
+ className.startsWith('place-') ||
188
+ className.startsWith('order-') ||
189
+ className.startsWith('col-') ||
190
+ className.startsWith('row-')
191
+ ) {
192
+ return 'layout';
193
+ }
194
+
195
+ if (className === 'rounded' || className.startsWith('rounded-')) return 'radius';
196
+ if (className === 'shadow' || className.startsWith('shadow-')) return 'shadow';
197
+
198
+ if (
199
+ className.startsWith('opacity-') ||
200
+ className.startsWith('blur-') ||
201
+ className.startsWith('backdrop-') ||
202
+ className === 'filter' ||
203
+ className === 'grayscale' ||
204
+ className.startsWith('saturate-') ||
205
+ className.startsWith('brightness-') ||
206
+ className.startsWith('contrast-')
207
+ ) {
208
+ return 'effects';
209
+ }
210
+
211
+ if (
212
+ className === 'transition' ||
213
+ className.startsWith('transition-') ||
214
+ className.startsWith('duration-') ||
215
+ className.startsWith('ease-') ||
216
+ className.startsWith('delay-') ||
217
+ className.startsWith('animate-')
218
+ ) {
219
+ return 'motion';
220
+ }
221
+
222
+ if (
223
+ className === 'sr-only' ||
224
+ className === 'not-sr-only' ||
225
+ className === 'focus-ring' ||
226
+ className === 'skip-to-content' ||
227
+ className === 'js-hidden' ||
228
+ className === 'no-js'
229
+ ) {
230
+ return 'accessibility';
231
+ }
232
+
233
+ if (
234
+ className === 'input' ||
235
+ className === 'select' ||
236
+ className === 'textarea' ||
237
+ className === 'checkbox' ||
238
+ className === 'radio' ||
239
+ className === 'stack' ||
240
+ className === 'cluster' ||
241
+ className === 'width-container'
242
+ ) {
243
+ return 'component';
244
+ }
245
+
246
+ return 'utility';
247
+ }
248
+
249
+ function normalizeClassName(selector) {
250
+ return selector.slice(1).replace(/\\(.)/g, '$1');
251
+ }
252
+
253
+ function getVariants(config) {
254
+ const breakpoints =
255
+ config &&
256
+ config.breakpoints &&
257
+ typeof config.breakpoints === 'object' &&
258
+ Object.keys(config.breakpoints).length > 0
259
+ ? Object.keys(config.breakpoints)
260
+ : DEFAULT_RESPONSIVE_VARIANTS;
261
+
262
+ return [...BASE_VARIANTS, ...breakpoints];
263
+ }
264
+
265
+ function generateManifest(css, config = {}) {
266
+ const manifest = {
267
+ version: MANIFEST_VERSION,
268
+ generatedAt: new Date().toISOString(),
269
+ utilities: [],
270
+ };
271
+
272
+ if (typeof css !== 'string' || css.length === 0) {
273
+ return manifest;
274
+ }
275
+
276
+ const variants = getVariants(config);
277
+ const cleanedCss = css.replace(/\/\*[\s\S]*?\*\//g, '');
278
+ const ruleRegex = /([^{}]+)\{([^{}]*)\}/g;
279
+ let ruleMatch;
280
+
281
+ while ((ruleMatch = ruleRegex.exec(cleanedCss)) !== null) {
282
+ const selector = ruleMatch[1].trim();
283
+ const declarationBlock = ruleMatch[2].trim();
284
+
285
+ if (!isSimpleBaseClassSelector(selector)) continue;
286
+
287
+ const { declarations, firstProperty, firstValue } = parseDeclarations(declarationBlock);
288
+ if (!firstProperty) continue;
289
+
290
+ manifest.utilities.push({
291
+ class: normalizeClassName(selector),
292
+ category: inferCategory(normalizeClassName(selector), firstProperty),
293
+ property: firstProperty,
294
+ value: firstValue,
295
+ token: getTokenFromDeclarations(declarations),
296
+ declarations,
297
+ variants,
298
+ source: 'generated-css',
299
+ });
300
+ }
301
+
302
+ return manifest;
303
+ }
304
+
305
+ module.exports = {
306
+ generateManifest,
307
+ };