@ngcorex/cli 0.1.5 → 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/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
- MIT License
1
+ # MIT License
2
2
 
3
- Copyright (c) 2026 Ajay Kumar Sharma
3
+ Copyright © 2026 Ajay Kumar Sharma
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
6
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @ngcorex/cli
2
2
 
3
- ![NPM Version](https://img.shields.io/npm/v/%40ngcorex%2Fcli?style=flat-square&logo=npm&labelColor=%23D50100&color=%23000) ![NPM License](https://img.shields.io/npm/l/%40ngcorex%2Fcli?style=flat-square) ![Static Badge](https://img.shields.io/badge/Github-Repo-blue?style=flat-square&logo=github) ![NPM Downloads](https://img.shields.io/npm/dm/%40ngcorex%2Fcli?style=flat-square&logo=npm&logoColor=%23ffffff&labelColor=%23D50100&color=%23000)
3
+ ![NPM Version](https://img.shields.io/npm/v/%40ngcorex%2Fcli?style=flat-square&logo=npm&labelColor=%23D50100&color=%23000) ![NPM License](https://img.shields.io/npm/l/%40ngcorex%2Fcli?style=flat-square) ![Static Badge](https://img.shields.io/badge/Github-Repo-blue?style=flat-square&logo=github) ![NPM Downloads](https://img.shields.io/npm/dm/%40ngcorex%2Fcli?style=flat-square&logo=npm&logoColor=%23ffffff&labelColor=%23D50100&color=%23000) ![GitHub Issues or Pull Requests](https://img.shields.io/github/issues/arkdezin/ngcorex)
4
4
 
5
5
  Command-line interface for **ngCorex**.
6
6
 
@@ -63,19 +63,169 @@ Create a `tokens.json` file at your project root:
63
63
  ```json
64
64
  {
65
65
  "spacing": {
66
- "1": "1rem",
67
- "2": "2rem"
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
  }
84
+ },
85
+ "radius": {
86
+ "sm": "0.25rem",
87
+ "md": "0.5rem",
88
+ "lg": "0.75rem",
89
+ "xl": "1rem",
90
+ "full": "9999px"
91
+ },
92
+ "zIndex": {
93
+ "base": "0",
94
+ "dropdown": "1000",
95
+ "sticky": "1020",
96
+ "fixed": "1030",
97
+ "modal-backdrop": "1040",
98
+ "modal": "1050",
99
+ "popover": "1060",
100
+ "tooltip": "1070"
101
+ },
102
+ "typography": {
103
+ "fontSize": {
104
+ "xs": "0.75rem",
105
+ "sm": "0.875rem",
106
+ "base": "1rem",
107
+ "lg": "1.125rem",
108
+ "xl": "1.25rem",
109
+ "2xl": "1.5rem",
110
+ "3xl": "1.875rem",
111
+ "4xl": "2.25rem"
112
+ },
113
+ "fontWeight": {
114
+ "light": "300",
115
+ "normal": "400",
116
+ "medium": "500",
117
+ "semibold": "600",
118
+ "bold": "700",
119
+ "extrabold": "800"
120
+ },
121
+ "lineHeight": {
122
+ "none": "1",
123
+ "tight": "1.25",
124
+ "snug": "1.375",
125
+ "normal": "1.5",
126
+ "relaxed": "1.625",
127
+ "loose": "2"
128
+ }
129
+ },
130
+ "shadows": {
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)"
74
137
  }
75
138
  }
76
139
  ```
77
140
 
78
- If `tokens.json` is present, it is used automatically.
141
+ ### Supported Token Categories
142
+
143
+ ngCorex supports the following design token categories:
144
+
145
+ | Category | Description | Example Values |
146
+ | ---------- | ------------- | ---------------- |
147
+ | `spacing` | Spacing scale for margins, padding, gaps | `"4px"`, `"1rem"`, `"0.5em"` |
148
+ | `colors` | Color palette with nested shades | `"#f3f4f6"`, `"rgb(37, 99, 235)"` |
149
+ | `radius` | Border radius values | `"4px"`, `"8px"`, `"16px"`, `"full"` |
150
+ | `zIndex` | Z-index layer values | `"1000"`, `"2000"`, `"3000"` |
151
+ | `typography` | Font properties (fontSize, fontWeight, lineHeight) | See below |
152
+ | `shadows` | Box shadow values | `"0 1px 2px 0 rgba(0,0,0,0.05)"` |
153
+
154
+ #### Typography Sub-categories
155
+
156
+ | Sub-category | Description | Example Values |
157
+ | ------------- | ------------- | ---------------- |
158
+ | `fontSize` | Font size values | `"0.75rem"`, `"16px"`, `"1.25em"` |
159
+ | `fontWeight` | Font weight values | `"400"`, `"500"`, `"bold"`, `"700"` |
160
+ | `lineHeight` | Line height values | `"1.25"`, `"1.5"`, `"1.75"` |
161
+
162
+ ### Constraint Configuration
163
+
164
+ You can configure constraint levels in your `ngcorex.config.ts`:
165
+
166
+ ```ts
167
+ import { defineNgCorexConfig } from '@ngcorex/css';
168
+
169
+ export default defineNgCorexConfig({
170
+ constraints: {
171
+ spacing: {
172
+ unit: 'warning', // Warn about unitless numbers
173
+ format: 'error', // Error on invalid formats
174
+ type: 'error' // Error on wrong types
175
+ },
176
+ colors: {
177
+ format: 'error',
178
+ shadeKey: 'error',
179
+ type: 'error'
180
+ },
181
+ radius: {
182
+ unit: 'warning',
183
+ format: 'error',
184
+ type: 'error'
185
+ },
186
+ zIndex: {
187
+ format: 'error',
188
+ type: 'error'
189
+ },
190
+ typography: {
191
+ fontSize: {
192
+ format: 'error',
193
+ type: 'error'
194
+ },
195
+ fontWeight: {
196
+ format: 'error',
197
+ type: 'error'
198
+ },
199
+ lineHeight: {
200
+ format: 'error',
201
+ type: 'error'
202
+ }
203
+ },
204
+ shadows: {
205
+ format: 'error',
206
+ type: 'error'
207
+ }
208
+ }
209
+ });
210
+ ```
211
+
212
+ Available constraint levels: `'error'`, `'warning'`, `'off'`.
213
+
214
+ ### Output Layer Configuration
215
+
216
+ You can optionally wrap generated CSS in a CSS layer for better organization and specificity control:
217
+
218
+ ```ts
219
+ import { defineNgCorexConfig } from '@ngcorex/css';
220
+
221
+ export default defineNgCorexConfig({
222
+ output: {
223
+ layer: 'tokens', // Wraps CSS in @layer tokens { ... }
224
+ file: 'src/styles/ngcorex.css'
225
+ }
226
+ });
227
+ ```
228
+
79
229
 
80
230
  ## Configuration File
81
231
 
@@ -95,6 +245,18 @@ export default defineNgCorexConfig({
95
245
  });
96
246
  ```
97
247
 
248
+ In case you want to output in layer
249
+ **add:**
250
+
251
+ ```ts
252
+ export default defineNgCorexConfig({
253
+ output: {
254
+ layer: 'layer-name',
255
+ file: 'src/styles/ngcorex.css'
256
+ }
257
+ });
258
+ ```
259
+
98
260
  ### Important Rules
99
261
 
100
262
  - The config file **must import from npm packages only**
@@ -107,12 +269,12 @@ export default defineNgCorexConfig({
107
269
 
108
270
  The ngCorex CLI supports the following commands:
109
271
 
110
- - `ngcorex init` create starter config and tokens
111
- - `ngcorex build` generate CSS from tokens
112
- - `ngcorex build --watch` rebuild on file changes
113
- - `ngcorex build --dry-run` validate without writing files
114
- - `ngcorex version` / `--version` / `-v` print CLI version
115
- - `ngcorex --help` / `-h` show help information
272
+ - `ngcorex init` - create starter config and tokens
273
+ - `ngcorex build` - generate CSS from tokens
274
+ - `ngcorex build --watch` - rebuild on file changes
275
+ - `ngcorex build --dry-run` - validate without writing files
276
+ - `ngcorex version` / `--version` / `-v` - print CLI version
277
+ - `ngcorex --help` / `-h` - show help information
116
278
 
117
279
  ## Commands
118
280
 
@@ -187,11 +349,52 @@ The CLI generates CSS variables based on your tokens and constraints.
187
349
  Example output:
188
350
 
189
351
  ```css
190
- :root {
191
- --nx-spacing-1: 1rem;
192
- --nx-spacing-2: 2rem;
193
- --nx-color-gray-100: #f3f4f6;
194
- --nx-color-gray-900: #111827;
352
+ @layer tokens {
353
+ :root {
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;
377
+ --nx-zIndex-dropdown: 1000;
378
+ --nx-zIndex-modal: 1050;
379
+ --nx-zIndex-tooltip: 1070;
380
+
381
+ /* Typography */
382
+ --nx-fontSize-xs: 0.75rem;
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;
388
+ --nx-lineHeight-normal: 1.5;
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);
397
+ }
195
398
  }
196
399
  ```
197
400
 
@@ -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,44 @@ 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.');
59
+ process.exit(1);
60
+ }
61
+ }
62
+ if ('radius' in tokens) {
63
+ if (typeof tokens.radius !== 'object' ||
64
+ tokens.radius === null ||
65
+ Array.isArray(tokens.radius)) {
66
+ error('Invalid tokens.json');
67
+ console.log(' Details: The "radius" token must be an object.');
68
+ process.exit(1);
69
+ }
70
+ }
71
+ if ('zIndex' in tokens) {
72
+ if (typeof tokens.zIndex !== 'object' ||
73
+ tokens.zIndex === null ||
74
+ Array.isArray(tokens.zIndex)) {
75
+ error('Invalid tokens.json');
76
+ console.log(' Details: The "zIndex" token must be an object.');
77
+ process.exit(1);
78
+ }
79
+ }
80
+ if ('typography' in tokens) {
81
+ if (typeof tokens.typography !== 'object' ||
82
+ tokens.typography === null ||
83
+ Array.isArray(tokens.typography)) {
84
+ error('Invalid tokens.json');
85
+ console.log(' Details: The "typography" token must be an object.');
86
+ process.exit(1);
87
+ }
88
+ }
89
+ if ('shadows' in tokens) {
90
+ if (typeof tokens.shadows !== 'object' ||
91
+ tokens.shadows === null ||
92
+ Array.isArray(tokens.shadows)) {
93
+ error('Invalid tokens.json');
94
+ console.log(' Details: The "shadows" token must be an object.');
66
95
  process.exit(1);
67
96
  }
68
97
  }
@@ -74,14 +103,112 @@ export async function runBuild(options = {}) {
74
103
  const spacing = tokens.spacing;
75
104
  for (const [key, value] of Object.entries(spacing)) {
76
105
  if (typeof value !== 'number' && typeof value !== 'string') {
77
- console.error('');
78
- console.error('❌ Invalid tokens.json');
79
- console.error(`Invalid spacing value for key "${key}". Expected number or string.`);
80
- console.error('');
106
+ error('Invalid tokens.json');
107
+ console.log(` Details: Invalid spacing value for key "${key}". Expected number or string.`);
81
108
  process.exit(1);
82
109
  }
83
110
  }
84
111
  }
112
+ // Validate radius token values
113
+ if (fileTokens !== null) {
114
+ const tokens = fileTokens;
115
+ if (tokens.radius) {
116
+ const radius = tokens.radius;
117
+ for (const [key, value] of Object.entries(radius)) {
118
+ if (typeof value !== 'number' && typeof value !== 'string') {
119
+ error('Invalid tokens.json');
120
+ console.log(` Details: Invalid radius value for key "${key}". Expected number or string.`);
121
+ process.exit(1);
122
+ }
123
+ }
124
+ }
125
+ }
126
+ // Validate z-index token values
127
+ if (fileTokens !== null) {
128
+ const tokens = fileTokens;
129
+ if (tokens.zIndex) {
130
+ const zIndex = tokens.zIndex;
131
+ for (const [key, value] of Object.entries(zIndex)) {
132
+ if (typeof value !== 'number' && typeof value !== 'string') {
133
+ error('Invalid tokens.json');
134
+ console.log(` Details: Invalid zIndex value for key "${key}". Expected number or string.`);
135
+ process.exit(1);
136
+ }
137
+ }
138
+ }
139
+ }
140
+ // Validate typography token structure
141
+ if (fileTokens !== null) {
142
+ const tokens = fileTokens;
143
+ if (tokens.typography) {
144
+ const typography = tokens.typography;
145
+ if (typography.fontSize) {
146
+ if (typeof typography.fontSize !== 'object' ||
147
+ typography.fontSize === null ||
148
+ Array.isArray(typography.fontSize)) {
149
+ error('Invalid tokens.json');
150
+ console.log(' Details: The "typography.fontSize" token must be an object.');
151
+ process.exit(1);
152
+ }
153
+ const fontSize = typography.fontSize;
154
+ for (const [key, value] of Object.entries(fontSize)) {
155
+ if (typeof value !== 'number' && typeof value !== 'string') {
156
+ error('Invalid tokens.json');
157
+ console.log(` Details: Invalid typography.fontSize value for key "${key}". Expected number or string.`);
158
+ process.exit(1);
159
+ }
160
+ }
161
+ }
162
+ if (typography.fontWeight) {
163
+ if (typeof typography.fontWeight !== 'object' ||
164
+ typography.fontWeight === null ||
165
+ Array.isArray(typography.fontWeight)) {
166
+ error('Invalid tokens.json');
167
+ console.log(' Details: The "typography.fontWeight" token must be an object.');
168
+ process.exit(1);
169
+ }
170
+ const fontWeight = typography.fontWeight;
171
+ for (const [key, value] of Object.entries(fontWeight)) {
172
+ if (typeof value !== 'number' && typeof value !== 'string') {
173
+ error('Invalid tokens.json');
174
+ console.log(` Details: Invalid typography.fontWeight value for key "${key}". Expected number or string.`);
175
+ process.exit(1);
176
+ }
177
+ }
178
+ }
179
+ if (typography.lineHeight) {
180
+ if (typeof typography.lineHeight !== 'object' ||
181
+ typography.lineHeight === null ||
182
+ Array.isArray(typography.lineHeight)) {
183
+ error('Invalid tokens.json');
184
+ console.log(' Details: The "typography.lineHeight" token must be an object.');
185
+ process.exit(1);
186
+ }
187
+ const lineHeight = typography.lineHeight;
188
+ for (const [key, value] of Object.entries(lineHeight)) {
189
+ if (typeof value !== 'number' && typeof value !== 'string') {
190
+ error('Invalid tokens.json');
191
+ console.log(` Details: Invalid typography.lineHeight value for key "${key}". Expected number or string.`);
192
+ process.exit(1);
193
+ }
194
+ }
195
+ }
196
+ }
197
+ }
198
+ // Validate shadows token values
199
+ if (fileTokens !== null) {
200
+ const tokens = fileTokens;
201
+ if (tokens.shadows) {
202
+ const shadows = tokens.shadows;
203
+ for (const [key, value] of Object.entries(shadows)) {
204
+ if (typeof value !== 'string') {
205
+ error('Invalid tokens.json');
206
+ console.log(` Details: Invalid shadows value for key "${key}". Expected string.`);
207
+ process.exit(1);
208
+ }
209
+ }
210
+ }
211
+ }
85
212
  }
86
213
  // Validate color token structure & values
87
214
  if (fileTokens !== null) {
@@ -92,43 +219,56 @@ export async function runBuild(options = {}) {
92
219
  if (typeof shades !== 'object' ||
93
220
  shades === null ||
94
221
  Array.isArray(shades)) {
95
- console.error('');
96
- console.error('❌ Invalid tokens.json');
97
- console.error(`Color "${colorName}" must be an object of shade values.`);
98
- console.error('');
222
+ error('Invalid tokens.json');
223
+ console.log(` Details: Color "${colorName}" must be an object of shade values.`);
99
224
  process.exit(1);
100
225
  }
101
226
  for (const [shade, value] of Object.entries(shades)) {
102
227
  // shade keys must be numeric
103
228
  if (!/^\d+$/.test(shade)) {
104
- console.error('');
105
- console.error('❌ Invalid tokens.json');
106
- console.error(`Invalid shade key "${shade}" in color "${colorName}". Shade keys must be numeric.`);
107
- console.error('');
229
+ error('Invalid tokens.json');
230
+ console.log(` Details: Invalid shade key "${shade}" in color "${colorName}". Shade keys must be numeric.`);
108
231
  process.exit(1);
109
232
  }
110
233
  // value must be a string
111
234
  if (typeof value !== 'string') {
112
- console.error('');
113
- console.error('❌ Invalid tokens.json');
114
- console.error(`Invalid value for ${colorName}.${shade}. Expected a color string.`);
115
- console.error('');
235
+ error('Invalid tokens.json');
236
+ console.log(` Details: Invalid value for ${colorName}.${shade}. Expected a color string.`);
116
237
  process.exit(1);
117
238
  }
118
239
  // very light color format validation (delegate strictness to engine)
119
240
  if (!value.startsWith('#') &&
120
241
  !value.startsWith('rgb(') &&
121
242
  !value.startsWith('rgba(')) {
122
- console.error('');
123
- console.error('❌ Invalid tokens.json');
124
- console.error(`Invalid color format for ${colorName}.${shade}: "${value}".`);
125
- console.error('');
243
+ error('Invalid tokens.json');
244
+ console.log(` Details: Invalid color format for ${colorName}.${shade}: "${value}".`);
126
245
  process.exit(1);
127
246
  }
128
247
  }
129
248
  }
130
249
  }
131
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
+ }
132
272
  const effectiveConfig = fileTokens
133
273
  ? {
134
274
  ...config,
@@ -138,16 +278,70 @@ export async function runBuild(options = {}) {
138
278
  if (!fileTokens &&
139
279
  config.tokens &&
140
280
  !hasShownInlineTokenNotice) {
141
- console.info('');
142
- console.info('ℹ️ Inline tokens detected.');
143
- console.info(' Using tokens.json is recommended for larger or shared projects.');
144
- console.info('');
281
+ info('Inline tokens detected');
282
+ console.log(' Details: Using tokens.json is recommended for larger or shared projects.');
145
283
  hasShownInlineTokenNotice = true;
146
284
  }
285
+ section('Building CSS');
147
286
  const css = buildCssFromConfig(effectiveConfig);
148
- 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);
149
320
  writeCss(outputPath, css, { dryRun: options.dryRun });
150
321
  if (!options.dryRun) {
151
- 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
+ }
152
345
  }
346
+ return count;
153
347
  }
@@ -2,7 +2,7 @@ import { mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { dirname } from 'node:path';
3
3
  export function writeCss(filePath, css, options = {}) {
4
4
  if (options.dryRun) {
5
- console.log(`ℹ Dry run skipping write to ${filePath}`);
5
+ console.log(`i Dry run - skipping write to ${filePath}`);
6
6
  return;
7
7
  }
8
8
  const dir = dirname(filePath);
@@ -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.5",
3
+ "version": "0.1.7",
4
4
  "description": "CLI for ngCorex - Angular-native design token engine",
5
5
  "keywords": [
6
6
  "design-tokens",
@@ -12,7 +12,12 @@
12
12
  "frontend-tooling",
13
13
  "ngcorex",
14
14
  "typescript",
15
- "angular"
15
+ "angular",
16
+ "css-variables",
17
+ "utility-css",
18
+ "build-time",
19
+ "design-system",
20
+ "theming"
16
21
  ],
17
22
  "license": "MIT",
18
23
  "type": "module",
@@ -37,5 +42,5 @@
37
42
  "directory": "packages/cli"
38
43
  },
39
44
 
40
- "homepage": "https://github.com/arkdezin/ngCorex"
45
+ "homepage": "https://github.com/arkdezin/ngCorex/blob/main/README.md"
41
46
  }