@ngcorex/cli 0.1.6 → 0.1.7

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/README.md CHANGED
@@ -63,25 +63,41 @@ Create a `tokens.json` file at your project root:
63
63
  ```json
64
64
  {
65
65
  "spacing": {
66
- "xs": "1rem",
67
- "sm": "1.25rem"
66
+ "xs": "0.25rem",
67
+ "sm": "0.5rem",
68
+ "md": "1rem",
69
+ "lg": "1.5rem",
70
+ "xl": "2rem"
68
71
  },
69
72
  "colors": {
70
- "gray": {
71
- "100": "#f3f4f6",
72
- "900": "#111827"
73
+ "neutral": {
74
+ "0": "#ffffff",
75
+ "100": "#f5f5f5",
76
+ "300": "#d4d4d4",
77
+ "500": "#737373",
78
+ "700": "#404040",
79
+ "900": "#171717"
80
+ },
81
+ "primary": {
82
+ "500": "#2563eb"
73
83
  }
74
84
  },
75
85
  "radius": {
76
- "sm": "4px",
77
- "md": "8px",
78
- "lg": "16px",
86
+ "sm": "0.25rem",
87
+ "md": "0.5rem",
88
+ "lg": "0.75rem",
89
+ "xl": "1rem",
79
90
  "full": "9999px"
80
91
  },
81
92
  "zIndex": {
93
+ "base": "0",
82
94
  "dropdown": "1000",
83
- "modal": "2000",
84
- "toast": "3000"
95
+ "sticky": "1020",
96
+ "fixed": "1030",
97
+ "modal-backdrop": "1040",
98
+ "modal": "1050",
99
+ "popover": "1060",
100
+ "tooltip": "1070"
85
101
  },
86
102
  "typography": {
87
103
  "fontSize": {
@@ -89,25 +105,35 @@ Create a `tokens.json` file at your project root:
89
105
  "sm": "0.875rem",
90
106
  "base": "1rem",
91
107
  "lg": "1.125rem",
92
- "xl": "1.25rem"
108
+ "xl": "1.25rem",
109
+ "2xl": "1.5rem",
110
+ "3xl": "1.875rem",
111
+ "4xl": "2.25rem"
93
112
  },
94
113
  "fontWeight": {
114
+ "light": "300",
95
115
  "normal": "400",
96
116
  "medium": "500",
97
117
  "semibold": "600",
98
- "bold": "700"
118
+ "bold": "700",
119
+ "extrabold": "800"
99
120
  },
100
121
  "lineHeight": {
122
+ "none": "1",
101
123
  "tight": "1.25",
124
+ "snug": "1.375",
102
125
  "normal": "1.5",
103
- "relaxed": "1.75"
126
+ "relaxed": "1.625",
127
+ "loose": "2"
104
128
  }
105
129
  },
106
130
  "shadows": {
107
- "sm": "0 1px 2px 0 rgba(0,0,0,0.05)",
108
- "md": "0 4px 6px -1px rgba(0,0,0,0.1)",
109
- "lg": "0 10px 15px -3px rgba(0,0,0,0.1)",
110
- "xl": "0 20px 25px -5px rgba(0,0,0,0.1)"
131
+ "sm": "0 1px 2px 0 rgb(0 0 0 / 0.05)",
132
+ "base": "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
133
+ "md": "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
134
+ "lg": "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
135
+ "xl": "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)",
136
+ "2xl": "0 25px 50px -12px rgb(0 0 0 / 0.25)"
111
137
  }
112
138
  }
113
139
  ```
@@ -200,55 +226,6 @@ export default defineNgCorexConfig({
200
226
  });
201
227
  ```
202
228
 
203
- ```ts
204
- import { defineNgCorexConfig } from '@ngcorex/css';
205
-
206
- export default defineNgCorexConfig({
207
- constraints: {
208
- spacing: {
209
- unit: 'warning', // Warn about unitless numbers
210
- format: 'error', // Error on invalid formats
211
- type: 'error' // Error on wrong types
212
- },
213
- colors: {
214
- format: 'error',
215
- shadeKey: 'error',
216
- type: 'error'
217
- },
218
- radius: {
219
- unit: 'warning',
220
- format: 'error',
221
- type: 'error'
222
- },
223
- zIndex: {
224
- format: 'error',
225
- type: 'error'
226
- },
227
- typography: {
228
- fontSize: {
229
- format: 'error',
230
- type: 'error'
231
- },
232
- fontWeight: {
233
- format: 'error',
234
- type: 'error'
235
- },
236
- lineHeight: {
237
- format: 'error',
238
- type: 'error'
239
- }
240
- },
241
- shadows: {
242
- format: 'error',
243
- type: 'error'
244
- }
245
- }
246
- });
247
- ```
248
-
249
- Available constraint levels: `'error'`, `'warning'`, `'off'`.
250
-
251
- If `tokens.json` is present, it is used automatically.
252
229
 
253
230
  ## Configuration File
254
231
 
@@ -374,17 +351,49 @@ Example output:
374
351
  ```css
375
352
  @layer tokens {
376
353
  :root {
377
- --nx-spacing-xs: 1rem;
378
- --nx-spacing-sm: 1.25rem;
379
- --nx-color-gray-100: #f3f4f6;
380
- --nx-color-gray-900: #111827;
381
- --nx-radius-sm: 4px;
382
- --nx-radius-md: 8px;
354
+ /* Spacing */
355
+ --nx-spacing-xs: 0.25rem;
356
+ --nx-spacing-sm: 0.5rem;
357
+ --nx-spacing-md: 1rem;
358
+ --nx-spacing-lg: 1.5rem;
359
+ --nx-spacing-xl: 2rem;
360
+
361
+ /* Colors */
362
+ --nx-color-neutral-0: #ffffff;
363
+ --nx-color-neutral-100: #f5f5f5;
364
+ --nx-color-neutral-500: #737373;
365
+ --nx-color-neutral-900: #171717;
366
+ --nx-color-primary-500: #2563eb;
367
+
368
+ /* Radius */
369
+ --nx-radius-sm: 0.25rem;
370
+ --nx-radius-md: 0.5rem;
371
+ --nx-radius-lg: 0.75rem;
372
+ --nx-radius-xl: 1rem;
373
+ --nx-radius-full: 9999px;
374
+
375
+ /* Z-Index */
376
+ --nx-zIndex-base: 0;
383
377
  --nx-zIndex-dropdown: 1000;
378
+ --nx-zIndex-modal: 1050;
379
+ --nx-zIndex-tooltip: 1070;
380
+
381
+ /* Typography */
384
382
  --nx-fontSize-xs: 0.75rem;
385
- --nx-fontWeight-medium: 500;
383
+ --nx-fontSize-base: 1rem;
384
+ --nx-fontSize-xl: 1.25rem;
385
+ --nx-fontSize-3xl: 1.875rem;
386
+ --nx-fontWeight-normal: 400;
387
+ --nx-fontWeight-bold: 700;
386
388
  --nx-lineHeight-normal: 1.5;
387
- --nx-shadows-sm: 0 1px 2px 0 rgba(0,0,0,0.05);
389
+ --nx-lineHeight-loose: 2;
390
+
391
+ /* Shadows */
392
+ --nx-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
393
+ --nx-shadow-base: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
394
+ --nx-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
395
+ --nx-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
396
+ --nx-shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
388
397
  }
389
398
  }
390
399
  ```
@@ -9,14 +9,77 @@ export async function initCommand() {
9
9
  if (!fs.existsSync(tokensPath)) {
10
10
  fs.writeFileSync(tokensPath, JSON.stringify({
11
11
  spacing: {
12
- "xs": "1rem",
13
- "sm": "1.25rem"
12
+ "xs": "0.25rem",
13
+ "sm": "0.5rem",
14
+ "md": "1rem",
15
+ "lg": "1.5rem",
16
+ "xl": "2rem"
14
17
  },
15
18
  colors: {
16
- gray: {
17
- "100": "#f3f4f6",
18
- "900": "#111827"
19
+ neutral: {
20
+ "0": "#ffffff",
21
+ "100": "#f5f5f5",
22
+ "300": "#d4d4d4",
23
+ "500": "#737373",
24
+ "700": "#404040",
25
+ "900": "#171717"
26
+ },
27
+ primary: {
28
+ "500": "#2563eb"
19
29
  }
30
+ },
31
+ radius: {
32
+ "sm": "0.25rem",
33
+ "md": "0.5rem",
34
+ "lg": "0.75rem",
35
+ "xl": "1rem",
36
+ "full": "9999px"
37
+ },
38
+ zIndex: {
39
+ "base": "0",
40
+ "dropdown": "1000",
41
+ "sticky": "1020",
42
+ "fixed": "1030",
43
+ "modal-backdrop": "1040",
44
+ "modal": "1050",
45
+ "popover": "1060",
46
+ "tooltip": "1070"
47
+ },
48
+ typography: {
49
+ fontSize: {
50
+ "xs": "0.75rem",
51
+ "sm": "0.875rem",
52
+ "base": "1rem",
53
+ "lg": "1.125rem",
54
+ "xl": "1.25rem",
55
+ "2xl": "1.5rem",
56
+ "3xl": "1.875rem",
57
+ "4xl": "2.25rem"
58
+ },
59
+ fontWeight: {
60
+ "light": "300",
61
+ "normal": "400",
62
+ "medium": "500",
63
+ "semibold": "600",
64
+ "bold": "700",
65
+ "extrabold": "800"
66
+ },
67
+ lineHeight: {
68
+ "none": "1",
69
+ "tight": "1.25",
70
+ "snug": "1.375",
71
+ "normal": "1.5",
72
+ "relaxed": "1.625",
73
+ "loose": "2"
74
+ }
75
+ },
76
+ shadows: {
77
+ "sm": "0 1px 2px 0 rgb(0 0 0 / 0.05)",
78
+ "base": "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
79
+ "md": "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
80
+ "lg": "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
81
+ "xl": "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)",
82
+ "2xl": "0 25px 50px -12px rgb(0 0 0 / 0.25)"
20
83
  }
21
84
  }, null, 2));
22
85
  console.info('✔ Created tokens.json');
@@ -3,13 +3,15 @@ import path from 'node:path';
3
3
  import { resolveConfigPath } from '../config/resolve-path.js';
4
4
  import { loadConfig } from '../config/load-config.js';
5
5
  import { writeCss } from '../output/write-css.js';
6
- import { buildCssFromConfig } from '@ngcorex/css';
6
+ import { buildCssFromConfig, runValidations, printValidationResults, hasValidationResults, hasValidationErrors } from '@ngcorex/css';
7
7
  import { resolve } from 'node:path';
8
+ import { BuildSummary, success, info, warning, error, section } from '../utils/logger.js';
8
9
  let hasShownInlineTokenNotice = false;
9
10
  export async function runBuild(options = {}) {
11
+ const buildSummary = new BuildSummary();
10
12
  const configPath = resolveConfigPath();
11
13
  const config = await loadConfig(configPath);
12
- console.log('✔ Loaded ngcorex.config.ts');
14
+ success('Loaded ngcorex.config.ts');
13
15
  const outputFile = config.output?.file ?? 'src/styles/ngcorex.css';
14
16
  const outputPath = resolve(process.cwd(), outputFile);
15
17
  const tokensPath = path.resolve(process.cwd(), 'tokens.json');
@@ -20,24 +22,19 @@ export async function runBuild(options = {}) {
20
22
  try {
21
23
  fileTokens = JSON.parse(raw);
22
24
  }
23
- catch (error) {
24
- console.error('');
25
- console.error('❌ Invalid tokens.json');
26
- console.error('Failed to parse JSON.');
27
- if (error instanceof SyntaxError) {
28
- console.error(error.message);
25
+ catch (err) {
26
+ error('Invalid tokens.json');
27
+ if (err instanceof SyntaxError) {
28
+ console.log(` Details: ${err.message}`);
29
29
  }
30
- console.error('');
31
30
  process.exit(1);
32
31
  }
33
32
  }
34
33
  // Validate top-level token shape
35
34
  if (fileTokens !== null) {
36
35
  if (typeof fileTokens !== 'object' || Array.isArray(fileTokens)) {
37
- console.error('');
38
- console.error('❌ Invalid tokens.json');
39
- console.error('The file must export a JSON object at the top level.');
40
- console.error('');
36
+ error('Invalid tokens.json');
37
+ console.log(' Details: The file must export a JSON object at the top level.');
41
38
  process.exit(1);
42
39
  }
43
40
  }
@@ -48,10 +45,8 @@ export async function runBuild(options = {}) {
48
45
  if (typeof tokens.spacing !== 'object' ||
49
46
  tokens.spacing === null ||
50
47
  Array.isArray(tokens.spacing)) {
51
- console.error('');
52
- console.error('❌ Invalid tokens.json');
53
- console.error('The "spacing" token must be an object.');
54
- console.error('');
48
+ error('Invalid tokens.json');
49
+ console.log(' Details: The "spacing" token must be an object.');
55
50
  process.exit(1);
56
51
  }
57
52
  }
@@ -59,10 +54,8 @@ export async function runBuild(options = {}) {
59
54
  if (typeof tokens.colors !== 'object' ||
60
55
  tokens.colors === null ||
61
56
  Array.isArray(tokens.colors)) {
62
- console.error('');
63
- console.error('❌ Invalid tokens.json');
64
- console.error('The "colors" token must be an object.');
65
- console.error('');
57
+ error('Invalid tokens.json');
58
+ console.log(' Details: The "colors" token must be an object.');
66
59
  process.exit(1);
67
60
  }
68
61
  }
@@ -70,10 +63,8 @@ export async function runBuild(options = {}) {
70
63
  if (typeof tokens.radius !== 'object' ||
71
64
  tokens.radius === null ||
72
65
  Array.isArray(tokens.radius)) {
73
- console.error('');
74
- console.error('❌ Invalid tokens.json');
75
- console.error('The "radius" token must be an object.');
76
- console.error('');
66
+ error('Invalid tokens.json');
67
+ console.log(' Details: The "radius" token must be an object.');
77
68
  process.exit(1);
78
69
  }
79
70
  }
@@ -81,10 +72,8 @@ export async function runBuild(options = {}) {
81
72
  if (typeof tokens.zIndex !== 'object' ||
82
73
  tokens.zIndex === null ||
83
74
  Array.isArray(tokens.zIndex)) {
84
- console.error('');
85
- console.error('❌ Invalid tokens.json');
86
- console.error('The "zIndex" token must be an object.');
87
- console.error('');
75
+ error('Invalid tokens.json');
76
+ console.log(' Details: The "zIndex" token must be an object.');
88
77
  process.exit(1);
89
78
  }
90
79
  }
@@ -92,10 +81,8 @@ export async function runBuild(options = {}) {
92
81
  if (typeof tokens.typography !== 'object' ||
93
82
  tokens.typography === null ||
94
83
  Array.isArray(tokens.typography)) {
95
- console.error('');
96
- console.error('❌ Invalid tokens.json');
97
- console.error('The "typography" token must be an object.');
98
- console.error('');
84
+ error('Invalid tokens.json');
85
+ console.log(' Details: The "typography" token must be an object.');
99
86
  process.exit(1);
100
87
  }
101
88
  }
@@ -103,10 +90,8 @@ export async function runBuild(options = {}) {
103
90
  if (typeof tokens.shadows !== 'object' ||
104
91
  tokens.shadows === null ||
105
92
  Array.isArray(tokens.shadows)) {
106
- console.error('');
107
- console.error('❌ Invalid tokens.json');
108
- console.error('The "shadows" token must be an object.');
109
- console.error('');
93
+ error('Invalid tokens.json');
94
+ console.log(' Details: The "shadows" token must be an object.');
110
95
  process.exit(1);
111
96
  }
112
97
  }
@@ -118,10 +103,8 @@ export async function runBuild(options = {}) {
118
103
  const spacing = tokens.spacing;
119
104
  for (const [key, value] of Object.entries(spacing)) {
120
105
  if (typeof value !== 'number' && typeof value !== 'string') {
121
- console.error('');
122
- console.error('❌ Invalid tokens.json');
123
- console.error(`Invalid spacing value for key "${key}". Expected number or string.`);
124
- console.error('');
106
+ error('Invalid tokens.json');
107
+ console.log(` Details: Invalid spacing value for key "${key}". Expected number or string.`);
125
108
  process.exit(1);
126
109
  }
127
110
  }
@@ -133,10 +116,8 @@ export async function runBuild(options = {}) {
133
116
  const radius = tokens.radius;
134
117
  for (const [key, value] of Object.entries(radius)) {
135
118
  if (typeof value !== 'number' && typeof value !== 'string') {
136
- console.error('');
137
- console.error('❌ Invalid tokens.json');
138
- console.error(`Invalid radius value for key "${key}". Expected number or string.`);
139
- console.error('');
119
+ error('Invalid tokens.json');
120
+ console.log(` Details: Invalid radius value for key "${key}". Expected number or string.`);
140
121
  process.exit(1);
141
122
  }
142
123
  }
@@ -149,10 +130,8 @@ export async function runBuild(options = {}) {
149
130
  const zIndex = tokens.zIndex;
150
131
  for (const [key, value] of Object.entries(zIndex)) {
151
132
  if (typeof value !== 'number' && typeof value !== 'string') {
152
- console.error('');
153
- console.error('❌ Invalid tokens.json');
154
- console.error(`Invalid zIndex value for key "${key}". Expected number or string.`);
155
- console.error('');
133
+ error('Invalid tokens.json');
134
+ console.log(` Details: Invalid zIndex value for key "${key}". Expected number or string.`);
156
135
  process.exit(1);
157
136
  }
158
137
  }
@@ -167,19 +146,15 @@ export async function runBuild(options = {}) {
167
146
  if (typeof typography.fontSize !== 'object' ||
168
147
  typography.fontSize === null ||
169
148
  Array.isArray(typography.fontSize)) {
170
- console.error('');
171
- console.error('❌ Invalid tokens.json');
172
- console.error('The "typography.fontSize" token must be an object.');
173
- console.error('');
149
+ error('Invalid tokens.json');
150
+ console.log(' Details: The "typography.fontSize" token must be an object.');
174
151
  process.exit(1);
175
152
  }
176
153
  const fontSize = typography.fontSize;
177
154
  for (const [key, value] of Object.entries(fontSize)) {
178
155
  if (typeof value !== 'number' && typeof value !== 'string') {
179
- console.error('');
180
- console.error('❌ Invalid tokens.json');
181
- console.error(`Invalid typography.fontSize value for key "${key}". Expected number or string.`);
182
- console.error('');
156
+ error('Invalid tokens.json');
157
+ console.log(` Details: Invalid typography.fontSize value for key "${key}". Expected number or string.`);
183
158
  process.exit(1);
184
159
  }
185
160
  }
@@ -188,19 +163,15 @@ export async function runBuild(options = {}) {
188
163
  if (typeof typography.fontWeight !== 'object' ||
189
164
  typography.fontWeight === null ||
190
165
  Array.isArray(typography.fontWeight)) {
191
- console.error('');
192
- console.error('❌ Invalid tokens.json');
193
- console.error('The "typography.fontWeight" token must be an object.');
194
- console.error('');
166
+ error('Invalid tokens.json');
167
+ console.log(' Details: The "typography.fontWeight" token must be an object.');
195
168
  process.exit(1);
196
169
  }
197
170
  const fontWeight = typography.fontWeight;
198
171
  for (const [key, value] of Object.entries(fontWeight)) {
199
172
  if (typeof value !== 'number' && typeof value !== 'string') {
200
- console.error('');
201
- console.error('❌ Invalid tokens.json');
202
- console.error(`Invalid typography.fontWeight value for key "${key}". Expected number or string.`);
203
- console.error('');
173
+ error('Invalid tokens.json');
174
+ console.log(` Details: Invalid typography.fontWeight value for key "${key}". Expected number or string.`);
204
175
  process.exit(1);
205
176
  }
206
177
  }
@@ -209,19 +180,15 @@ export async function runBuild(options = {}) {
209
180
  if (typeof typography.lineHeight !== 'object' ||
210
181
  typography.lineHeight === null ||
211
182
  Array.isArray(typography.lineHeight)) {
212
- console.error('');
213
- console.error('❌ Invalid tokens.json');
214
- console.error('The "typography.lineHeight" token must be an object.');
215
- console.error('');
183
+ error('Invalid tokens.json');
184
+ console.log(' Details: The "typography.lineHeight" token must be an object.');
216
185
  process.exit(1);
217
186
  }
218
187
  const lineHeight = typography.lineHeight;
219
188
  for (const [key, value] of Object.entries(lineHeight)) {
220
189
  if (typeof value !== 'number' && typeof value !== 'string') {
221
- console.error('');
222
- console.error('❌ Invalid tokens.json');
223
- console.error(`Invalid typography.lineHeight value for key "${key}". Expected number or string.`);
224
- console.error('');
190
+ error('Invalid tokens.json');
191
+ console.log(` Details: Invalid typography.lineHeight value for key "${key}". Expected number or string.`);
225
192
  process.exit(1);
226
193
  }
227
194
  }
@@ -235,10 +202,8 @@ export async function runBuild(options = {}) {
235
202
  const shadows = tokens.shadows;
236
203
  for (const [key, value] of Object.entries(shadows)) {
237
204
  if (typeof value !== 'string') {
238
- console.error('');
239
- console.error('❌ Invalid tokens.json');
240
- console.error(`Invalid shadows value for key "${key}". Expected string.`);
241
- console.error('');
205
+ error('Invalid tokens.json');
206
+ console.log(` Details: Invalid shadows value for key "${key}". Expected string.`);
242
207
  process.exit(1);
243
208
  }
244
209
  }
@@ -254,43 +219,56 @@ export async function runBuild(options = {}) {
254
219
  if (typeof shades !== 'object' ||
255
220
  shades === null ||
256
221
  Array.isArray(shades)) {
257
- console.error('');
258
- console.error('❌ Invalid tokens.json');
259
- console.error(`Color "${colorName}" must be an object of shade values.`);
260
- console.error('');
222
+ error('Invalid tokens.json');
223
+ console.log(` Details: Color "${colorName}" must be an object of shade values.`);
261
224
  process.exit(1);
262
225
  }
263
226
  for (const [shade, value] of Object.entries(shades)) {
264
227
  // shade keys must be numeric
265
228
  if (!/^\d+$/.test(shade)) {
266
- console.error('');
267
- console.error('❌ Invalid tokens.json');
268
- console.error(`Invalid shade key "${shade}" in color "${colorName}". Shade keys must be numeric.`);
269
- console.error('');
229
+ error('Invalid tokens.json');
230
+ console.log(` Details: Invalid shade key "${shade}" in color "${colorName}". Shade keys must be numeric.`);
270
231
  process.exit(1);
271
232
  }
272
233
  // value must be a string
273
234
  if (typeof value !== 'string') {
274
- console.error('');
275
- console.error('❌ Invalid tokens.json');
276
- console.error(`Invalid value for ${colorName}.${shade}. Expected a color string.`);
277
- console.error('');
235
+ error('Invalid tokens.json');
236
+ console.log(` Details: Invalid value for ${colorName}.${shade}. Expected a color string.`);
278
237
  process.exit(1);
279
238
  }
280
239
  // very light color format validation (delegate strictness to engine)
281
240
  if (!value.startsWith('#') &&
282
241
  !value.startsWith('rgb(') &&
283
242
  !value.startsWith('rgba(')) {
284
- console.error('');
285
- console.error('❌ Invalid tokens.json');
286
- console.error(`Invalid color format for ${colorName}.${shade}: "${value}".`);
287
- console.error('');
243
+ error('Invalid tokens.json');
244
+ console.log(` Details: Invalid color format for ${colorName}.${shade}: "${value}".`);
288
245
  process.exit(1);
289
246
  }
290
247
  }
291
248
  }
292
249
  }
293
250
  }
251
+ // Run non-blocking validations
252
+ if (fileTokens !== null) {
253
+ section('Token Validation');
254
+ const validationReport = runValidations(fileTokens);
255
+ if (hasValidationResults(validationReport)) {
256
+ if (hasValidationErrors(validationReport)) {
257
+ error('Blocking validation errors found');
258
+ console.log(' Details: Please fix the errors below before proceeding.');
259
+ printValidationResults(validationReport);
260
+ process.exit(1);
261
+ }
262
+ else {
263
+ warning('Non-blocking validation warnings found');
264
+ console.log(' Details: Review the warnings below for potential improvements.');
265
+ printValidationResults(validationReport);
266
+ }
267
+ }
268
+ else {
269
+ success('Token validation passed');
270
+ }
271
+ }
294
272
  const effectiveConfig = fileTokens
295
273
  ? {
296
274
  ...config,
@@ -300,16 +278,70 @@ export async function runBuild(options = {}) {
300
278
  if (!fileTokens &&
301
279
  config.tokens &&
302
280
  !hasShownInlineTokenNotice) {
303
- console.info('');
304
- console.info('i Inline tokens detected.');
305
- console.info('Using tokens.json is recommended for larger or shared projects.');
306
- console.info('');
281
+ info('Inline tokens detected');
282
+ console.log(' Details: Using tokens.json is recommended for larger or shared projects.');
307
283
  hasShownInlineTokenNotice = true;
308
284
  }
285
+ section('Building CSS');
309
286
  const css = buildCssFromConfig(effectiveConfig);
310
- console.log('✔ Generated CSS');
287
+ success('Generated CSS');
288
+ // Count tokens for build summary
289
+ if (effectiveConfig.tokens) {
290
+ if (effectiveConfig.tokens.spacing) {
291
+ buildSummary.addTokenCategory('spacing', Object.keys(effectiveConfig.tokens.spacing).length);
292
+ }
293
+ if (effectiveConfig.tokens.colors) {
294
+ const colorCount = countNestedTokens(effectiveConfig.tokens.colors);
295
+ buildSummary.addTokenCategory('colors', colorCount);
296
+ }
297
+ if (effectiveConfig.tokens.radius) {
298
+ buildSummary.addTokenCategory('radius', Object.keys(effectiveConfig.tokens.radius).length);
299
+ }
300
+ if (effectiveConfig.tokens.zIndex) {
301
+ buildSummary.addTokenCategory('zIndex', Object.keys(effectiveConfig.tokens.zIndex).length);
302
+ }
303
+ if (effectiveConfig.tokens.typography) {
304
+ const typography = effectiveConfig.tokens.typography;
305
+ if (typography.fontSize) {
306
+ buildSummary.addTokenCategory('typography.fontSize', Object.keys(typography.fontSize).length);
307
+ }
308
+ if (typography.fontWeight) {
309
+ buildSummary.addTokenCategory('typography.fontWeight', Object.keys(typography.fontWeight).length);
310
+ }
311
+ if (typography.lineHeight) {
312
+ buildSummary.addTokenCategory('typography.lineHeight', Object.keys(typography.lineHeight).length);
313
+ }
314
+ }
315
+ if (effectiveConfig.tokens.shadows) {
316
+ buildSummary.addTokenCategory('shadows', Object.keys(effectiveConfig.tokens.shadows).length);
317
+ }
318
+ }
319
+ buildSummary.setOutputFile(outputFile);
311
320
  writeCss(outputPath, css, { dryRun: options.dryRun });
312
321
  if (!options.dryRun) {
313
- console.log(`✔ Output written to ${outputFile}`);
322
+ success(`Output written to ${outputFile}`);
323
+ // Get output file size
324
+ const stats = fs.statSync(outputPath);
325
+ buildSummary.setOutputSize(stats.size);
326
+ }
327
+ // Print build summary
328
+ buildSummary.print();
329
+ }
330
+ /**
331
+ * Count tokens in a nested structure (like colors)
332
+ */
333
+ function countNestedTokens(obj) {
334
+ if (typeof obj !== 'object' || obj === null) {
335
+ return 0;
336
+ }
337
+ let count = 0;
338
+ for (const [key, value] of Object.entries(obj)) {
339
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
340
+ count += countNestedTokens(value);
341
+ }
342
+ else {
343
+ count++;
344
+ }
314
345
  }
346
+ return count;
315
347
  }
@@ -1,8 +1,252 @@
1
+ /**
2
+ * Enhanced logger for ngCorex CLI with improved developer experience
3
+ */
4
+ /**
5
+ * Build summary tracker
6
+ */
7
+ export class BuildSummary {
8
+ constructor(options = {}) {
9
+ this.tokensProcessed = new Map();
10
+ this.warnings = [];
11
+ this.errors = [];
12
+ this.startTime = Date.now();
13
+ this.options = {};
14
+ this.options = options;
15
+ }
16
+ addTokenCategory(category, count) {
17
+ this.tokensProcessed.set(category, count);
18
+ }
19
+ addWarning(message) {
20
+ this.warnings.push({
21
+ timestamp: Date.now(),
22
+ message
23
+ });
24
+ }
25
+ addError(message) {
26
+ this.errors.push({
27
+ timestamp: Date.now(),
28
+ message
29
+ });
30
+ }
31
+ setOutputFile(file) {
32
+ this.options.outputFile = file;
33
+ }
34
+ setOutputSize(size) {
35
+ this.options.outputSize = size;
36
+ }
37
+ getWarnings() {
38
+ return this.warnings;
39
+ }
40
+ getErrors() {
41
+ return this.errors;
42
+ }
43
+ getTokensProcessed() {
44
+ return this.tokensProcessed;
45
+ }
46
+ getDuration() {
47
+ return Date.now() - this.startTime;
48
+ }
49
+ hasWarnings() {
50
+ return this.warnings.length > 0;
51
+ }
52
+ hasErrors() {
53
+ return this.errors.length > 0;
54
+ }
55
+ /**
56
+ * Format file size in human-readable format
57
+ */
58
+ formatFileSize(bytes) {
59
+ if (bytes < 1024)
60
+ return `${bytes} B`;
61
+ if (bytes < 1024 * 1024)
62
+ return `${(bytes / 1024).toFixed(2)} KB`;
63
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
64
+ }
65
+ /**
66
+ * Format duration in human-readable format
67
+ */
68
+ formatDuration(ms) {
69
+ if (ms < 1000)
70
+ return `${ms}ms`;
71
+ return `${(ms / 1000).toFixed(2)}s`;
72
+ }
73
+ /**
74
+ * Print the build summary
75
+ */
76
+ print() {
77
+ const duration = this.getDuration();
78
+ const totalTokens = Array.from(this.tokensProcessed.values()).reduce((a, b) => a + b, 0);
79
+ console.log('');
80
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
81
+ console.log(' đŸ“Ļ Build Summary');
82
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
83
+ console.log('');
84
+ // Tokens processed
85
+ if (this.tokensProcessed.size > 0) {
86
+ console.log(' Tokens Processed:');
87
+ for (const [category, count] of this.tokensProcessed.entries()) {
88
+ console.log(` â€ĸ ${category.padEnd(12)} ${count.toString().padStart(4)} tokens`);
89
+ }
90
+ console.log(` ${'─'.repeat(20)}`);
91
+ console.log(` ${'Total'.padEnd(12)} ${totalTokens.toString().padStart(4)} tokens`);
92
+ console.log('');
93
+ }
94
+ // Output file
95
+ if (this.options.outputFile) {
96
+ console.log(' Output:');
97
+ console.log(` â€ĸ File: ${this.options.outputFile}`);
98
+ if (this.options.outputSize) {
99
+ console.log(` â€ĸ Size: ${this.formatFileSize(this.options.outputSize)}`);
100
+ }
101
+ console.log('');
102
+ }
103
+ // Duration
104
+ console.log(` Duration: ${this.formatDuration(duration)}`);
105
+ console.log('');
106
+ // Warnings and errors
107
+ if (this.hasWarnings() || this.hasErrors()) {
108
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
109
+ if (this.hasWarnings()) {
110
+ console.log(` âš ī¸ ${this.warnings.length} Warning${this.warnings.length > 1 ? 's' : ''}`);
111
+ }
112
+ if (this.hasErrors()) {
113
+ console.log(` ❌ ${this.errors.length} Error${this.errors.length > 1 ? 's' : ''}`);
114
+ }
115
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
116
+ console.log('');
117
+ }
118
+ else {
119
+ console.log(' ✅ Build completed successfully!');
120
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
121
+ console.log('');
122
+ }
123
+ }
124
+ /**
125
+ * Print detailed warnings
126
+ */
127
+ printWarnings() {
128
+ if (!this.hasWarnings())
129
+ return;
130
+ console.log('');
131
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
132
+ console.log(' âš ī¸ Warnings');
133
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
134
+ console.log('');
135
+ for (const entry of this.warnings) {
136
+ const { message } = entry;
137
+ console.log(` ${getIcon(message.type)} ${message.message}`);
138
+ if (message.location) {
139
+ console.log(` Location: ${message.location}`);
140
+ }
141
+ if (message.details) {
142
+ console.log(` Details: ${message.details}`);
143
+ }
144
+ if (message.suggestion) {
145
+ console.log(` 💡 ${message.suggestion}`);
146
+ }
147
+ console.log('');
148
+ }
149
+ }
150
+ /**
151
+ * Print detailed errors
152
+ */
153
+ printErrors() {
154
+ if (!this.hasErrors())
155
+ return;
156
+ console.log('');
157
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
158
+ console.log(' ❌ Errors');
159
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
160
+ console.log('');
161
+ for (const entry of this.errors) {
162
+ const { message } = entry;
163
+ console.log(` ${getIcon(message.type)} ${message.message}`);
164
+ if (message.location) {
165
+ console.log(` Location: ${message.location}`);
166
+ }
167
+ if (message.details) {
168
+ console.log(` Details: ${message.details}`);
169
+ }
170
+ if (message.suggestion) {
171
+ console.log(` 💡 ${message.suggestion}`);
172
+ }
173
+ console.log('');
174
+ }
175
+ }
176
+ }
177
+ /**
178
+ * Get icon for log level
179
+ */
180
+ function getIcon(level) {
181
+ const icons = {
182
+ success: '✅',
183
+ info: 'â„šī¸',
184
+ warning: 'âš ī¸',
185
+ error: '❌'
186
+ };
187
+ return icons[level] || 'â€ĸ';
188
+ }
189
+ /**
190
+ * Log a success message
191
+ */
192
+ export function success(message) {
193
+ console.log(`✅ ${message}`);
194
+ }
195
+ /**
196
+ * Log an info message
197
+ */
198
+ export function info(message) {
199
+ console.log(`â„šī¸ ${message}`);
200
+ }
201
+ /**
202
+ * Log a warning message
203
+ */
204
+ export function warning(message) {
205
+ console.log('');
206
+ console.log(`âš ī¸ ${message}`);
207
+ console.log('');
208
+ }
209
+ /**
210
+ * Log an error message
211
+ */
212
+ export function error(message) {
213
+ console.log('');
214
+ console.log(`❌ ${message}`);
215
+ console.log('');
216
+ }
217
+ /**
218
+ * Handle CLI errors with improved formatting
219
+ */
1
220
  export function handleCliError(error) {
221
+ console.log('');
222
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
223
+ console.log(' ❌ Error');
224
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
225
+ console.log('');
2
226
  if (error instanceof Error) {
3
- console.error(`✖ ${error.message}`);
227
+ console.log(` ${error.message}`);
4
228
  }
5
229
  else {
6
- console.error('✖ Unknown error');
230
+ console.log(' Unknown error occurred');
7
231
  }
232
+ console.log('');
233
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
234
+ console.log('');
235
+ }
236
+ /**
237
+ * Log a section header
238
+ */
239
+ export function section(title) {
240
+ console.log('');
241
+ console.log(`━━━ ${title} ━━━`);
242
+ console.log('');
243
+ }
244
+ /**
245
+ * Log a sub-section header
246
+ */
247
+ export function subsection(title) {
248
+ console.log('');
249
+ console.log(` ${title}`);
250
+ console.log(` ${'─'.repeat(title.length)}`);
251
+ console.log('');
8
252
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngcorex/cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "CLI for ngCorex - Angular-native design token engine",
5
5
  "keywords": [
6
6
  "design-tokens",
@@ -42,5 +42,5 @@
42
42
  "directory": "packages/cli"
43
43
  },
44
44
 
45
- "homepage": "https://github.com/arkdezin/ngCorex"
45
+ "homepage": "https://github.com/arkdezin/ngCorex/blob/main/README.md"
46
46
  }