@ngcorex/cli 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +2 -2
- package/README.md +208 -14
- package/dist/commands/run-build.js +164 -2
- package/dist/output/write-css.js +1 -1
- package/package.json +7 -2
package/LICENSE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
MIT License
|
|
1
|
+
# MIT License
|
|
2
2
|
|
|
3
|
-
Copyright
|
|
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
|
-
   
|
|
3
|
+
    
|
|
4
4
|
|
|
5
5
|
Command-line interface for **ngCorex**.
|
|
6
6
|
|
|
@@ -63,18 +63,191 @@ Create a `tokens.json` file at your project root:
|
|
|
63
63
|
```json
|
|
64
64
|
{
|
|
65
65
|
"spacing": {
|
|
66
|
-
"
|
|
67
|
-
"
|
|
66
|
+
"xs": "1rem",
|
|
67
|
+
"sm": "1.25rem"
|
|
68
68
|
},
|
|
69
69
|
"colors": {
|
|
70
70
|
"gray": {
|
|
71
71
|
"100": "#f3f4f6",
|
|
72
72
|
"900": "#111827"
|
|
73
73
|
}
|
|
74
|
+
},
|
|
75
|
+
"radius": {
|
|
76
|
+
"sm": "4px",
|
|
77
|
+
"md": "8px",
|
|
78
|
+
"lg": "16px",
|
|
79
|
+
"full": "9999px"
|
|
80
|
+
},
|
|
81
|
+
"zIndex": {
|
|
82
|
+
"dropdown": "1000",
|
|
83
|
+
"modal": "2000",
|
|
84
|
+
"toast": "3000"
|
|
85
|
+
},
|
|
86
|
+
"typography": {
|
|
87
|
+
"fontSize": {
|
|
88
|
+
"xs": "0.75rem",
|
|
89
|
+
"sm": "0.875rem",
|
|
90
|
+
"base": "1rem",
|
|
91
|
+
"lg": "1.125rem",
|
|
92
|
+
"xl": "1.25rem"
|
|
93
|
+
},
|
|
94
|
+
"fontWeight": {
|
|
95
|
+
"normal": "400",
|
|
96
|
+
"medium": "500",
|
|
97
|
+
"semibold": "600",
|
|
98
|
+
"bold": "700"
|
|
99
|
+
},
|
|
100
|
+
"lineHeight": {
|
|
101
|
+
"tight": "1.25",
|
|
102
|
+
"normal": "1.5",
|
|
103
|
+
"relaxed": "1.75"
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"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)"
|
|
74
111
|
}
|
|
75
112
|
}
|
|
76
113
|
```
|
|
77
114
|
|
|
115
|
+
### Supported Token Categories
|
|
116
|
+
|
|
117
|
+
ngCorex supports the following design token categories:
|
|
118
|
+
|
|
119
|
+
| Category | Description | Example Values |
|
|
120
|
+
| ---------- | ------------- | ---------------- |
|
|
121
|
+
| `spacing` | Spacing scale for margins, padding, gaps | `"4px"`, `"1rem"`, `"0.5em"` |
|
|
122
|
+
| `colors` | Color palette with nested shades | `"#f3f4f6"`, `"rgb(37, 99, 235)"` |
|
|
123
|
+
| `radius` | Border radius values | `"4px"`, `"8px"`, `"16px"`, `"full"` |
|
|
124
|
+
| `zIndex` | Z-index layer values | `"1000"`, `"2000"`, `"3000"` |
|
|
125
|
+
| `typography` | Font properties (fontSize, fontWeight, lineHeight) | See below |
|
|
126
|
+
| `shadows` | Box shadow values | `"0 1px 2px 0 rgba(0,0,0,0.05)"` |
|
|
127
|
+
|
|
128
|
+
#### Typography Sub-categories
|
|
129
|
+
|
|
130
|
+
| Sub-category | Description | Example Values |
|
|
131
|
+
| ------------- | ------------- | ---------------- |
|
|
132
|
+
| `fontSize` | Font size values | `"0.75rem"`, `"16px"`, `"1.25em"` |
|
|
133
|
+
| `fontWeight` | Font weight values | `"400"`, `"500"`, `"bold"`, `"700"` |
|
|
134
|
+
| `lineHeight` | Line height values | `"1.25"`, `"1.5"`, `"1.75"` |
|
|
135
|
+
|
|
136
|
+
### Constraint Configuration
|
|
137
|
+
|
|
138
|
+
You can configure constraint levels in your `ngcorex.config.ts`:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { defineNgCorexConfig } from '@ngcorex/css';
|
|
142
|
+
|
|
143
|
+
export default defineNgCorexConfig({
|
|
144
|
+
constraints: {
|
|
145
|
+
spacing: {
|
|
146
|
+
unit: 'warning', // Warn about unitless numbers
|
|
147
|
+
format: 'error', // Error on invalid formats
|
|
148
|
+
type: 'error' // Error on wrong types
|
|
149
|
+
},
|
|
150
|
+
colors: {
|
|
151
|
+
format: 'error',
|
|
152
|
+
shadeKey: 'error',
|
|
153
|
+
type: 'error'
|
|
154
|
+
},
|
|
155
|
+
radius: {
|
|
156
|
+
unit: 'warning',
|
|
157
|
+
format: 'error',
|
|
158
|
+
type: 'error'
|
|
159
|
+
},
|
|
160
|
+
zIndex: {
|
|
161
|
+
format: 'error',
|
|
162
|
+
type: 'error'
|
|
163
|
+
},
|
|
164
|
+
typography: {
|
|
165
|
+
fontSize: {
|
|
166
|
+
format: 'error',
|
|
167
|
+
type: 'error'
|
|
168
|
+
},
|
|
169
|
+
fontWeight: {
|
|
170
|
+
format: 'error',
|
|
171
|
+
type: 'error'
|
|
172
|
+
},
|
|
173
|
+
lineHeight: {
|
|
174
|
+
format: 'error',
|
|
175
|
+
type: 'error'
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
shadows: {
|
|
179
|
+
format: 'error',
|
|
180
|
+
type: 'error'
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Available constraint levels: `'error'`, `'warning'`, `'off'`.
|
|
187
|
+
|
|
188
|
+
### Output Layer Configuration
|
|
189
|
+
|
|
190
|
+
You can optionally wrap generated CSS in a CSS layer for better organization and specificity control:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import { defineNgCorexConfig } from '@ngcorex/css';
|
|
194
|
+
|
|
195
|
+
export default defineNgCorexConfig({
|
|
196
|
+
output: {
|
|
197
|
+
layer: 'tokens', // Wraps CSS in @layer tokens { ... }
|
|
198
|
+
file: 'src/styles/ngcorex.css'
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
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
|
+
|
|
78
251
|
If `tokens.json` is present, it is used automatically.
|
|
79
252
|
|
|
80
253
|
## Configuration File
|
|
@@ -95,6 +268,18 @@ export default defineNgCorexConfig({
|
|
|
95
268
|
});
|
|
96
269
|
```
|
|
97
270
|
|
|
271
|
+
In case you want to output in layer
|
|
272
|
+
**add:**
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
export default defineNgCorexConfig({
|
|
276
|
+
output: {
|
|
277
|
+
layer: 'layer-name',
|
|
278
|
+
file: 'src/styles/ngcorex.css'
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
```
|
|
282
|
+
|
|
98
283
|
### Important Rules
|
|
99
284
|
|
|
100
285
|
- The config file **must import from npm packages only**
|
|
@@ -107,12 +292,12 @@ export default defineNgCorexConfig({
|
|
|
107
292
|
|
|
108
293
|
The ngCorex CLI supports the following commands:
|
|
109
294
|
|
|
110
|
-
- `ngcorex init`
|
|
111
|
-
- `ngcorex build`
|
|
112
|
-
- `ngcorex build --watch`
|
|
113
|
-
- `ngcorex build --dry-run`
|
|
114
|
-
- `ngcorex version` / `--version` / `-v`
|
|
115
|
-
- `ngcorex --help` / `-h`
|
|
295
|
+
- `ngcorex init` - create starter config and tokens
|
|
296
|
+
- `ngcorex build` - generate CSS from tokens
|
|
297
|
+
- `ngcorex build --watch` - rebuild on file changes
|
|
298
|
+
- `ngcorex build --dry-run` - validate without writing files
|
|
299
|
+
- `ngcorex version` / `--version` / `-v` - print CLI version
|
|
300
|
+
- `ngcorex --help` / `-h` - show help information
|
|
116
301
|
|
|
117
302
|
## Commands
|
|
118
303
|
|
|
@@ -187,11 +372,20 @@ The CLI generates CSS variables based on your tokens and constraints.
|
|
|
187
372
|
Example output:
|
|
188
373
|
|
|
189
374
|
```css
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
375
|
+
@layer tokens {
|
|
376
|
+
: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;
|
|
383
|
+
--nx-zIndex-dropdown: 1000;
|
|
384
|
+
--nx-fontSize-xs: 0.75rem;
|
|
385
|
+
--nx-fontWeight-medium: 500;
|
|
386
|
+
--nx-lineHeight-normal: 1.5;
|
|
387
|
+
--nx-shadows-sm: 0 1px 2px 0 rgba(0,0,0,0.05);
|
|
388
|
+
}
|
|
195
389
|
}
|
|
196
390
|
```
|
|
197
391
|
|
|
@@ -66,6 +66,50 @@ export async function runBuild(options = {}) {
|
|
|
66
66
|
process.exit(1);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
+
if ('radius' in tokens) {
|
|
70
|
+
if (typeof tokens.radius !== 'object' ||
|
|
71
|
+
tokens.radius === null ||
|
|
72
|
+
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('');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if ('zIndex' in tokens) {
|
|
81
|
+
if (typeof tokens.zIndex !== 'object' ||
|
|
82
|
+
tokens.zIndex === null ||
|
|
83
|
+
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('');
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if ('typography' in tokens) {
|
|
92
|
+
if (typeof tokens.typography !== 'object' ||
|
|
93
|
+
tokens.typography === null ||
|
|
94
|
+
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('');
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if ('shadows' in tokens) {
|
|
103
|
+
if (typeof tokens.shadows !== 'object' ||
|
|
104
|
+
tokens.shadows === null ||
|
|
105
|
+
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('');
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
69
113
|
}
|
|
70
114
|
// Validate spacing token values
|
|
71
115
|
if (fileTokens !== null) {
|
|
@@ -82,6 +126,124 @@ export async function runBuild(options = {}) {
|
|
|
82
126
|
}
|
|
83
127
|
}
|
|
84
128
|
}
|
|
129
|
+
// Validate radius token values
|
|
130
|
+
if (fileTokens !== null) {
|
|
131
|
+
const tokens = fileTokens;
|
|
132
|
+
if (tokens.radius) {
|
|
133
|
+
const radius = tokens.radius;
|
|
134
|
+
for (const [key, value] of Object.entries(radius)) {
|
|
135
|
+
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('');
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Validate z-index token values
|
|
146
|
+
if (fileTokens !== null) {
|
|
147
|
+
const tokens = fileTokens;
|
|
148
|
+
if (tokens.zIndex) {
|
|
149
|
+
const zIndex = tokens.zIndex;
|
|
150
|
+
for (const [key, value] of Object.entries(zIndex)) {
|
|
151
|
+
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('');
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Validate typography token structure
|
|
162
|
+
if (fileTokens !== null) {
|
|
163
|
+
const tokens = fileTokens;
|
|
164
|
+
if (tokens.typography) {
|
|
165
|
+
const typography = tokens.typography;
|
|
166
|
+
if (typography.fontSize) {
|
|
167
|
+
if (typeof typography.fontSize !== 'object' ||
|
|
168
|
+
typography.fontSize === null ||
|
|
169
|
+
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('');
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
const fontSize = typography.fontSize;
|
|
177
|
+
for (const [key, value] of Object.entries(fontSize)) {
|
|
178
|
+
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('');
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (typography.fontWeight) {
|
|
188
|
+
if (typeof typography.fontWeight !== 'object' ||
|
|
189
|
+
typography.fontWeight === null ||
|
|
190
|
+
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('');
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
const fontWeight = typography.fontWeight;
|
|
198
|
+
for (const [key, value] of Object.entries(fontWeight)) {
|
|
199
|
+
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('');
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (typography.lineHeight) {
|
|
209
|
+
if (typeof typography.lineHeight !== 'object' ||
|
|
210
|
+
typography.lineHeight === null ||
|
|
211
|
+
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('');
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
const lineHeight = typography.lineHeight;
|
|
219
|
+
for (const [key, value] of Object.entries(lineHeight)) {
|
|
220
|
+
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('');
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Validate shadows token values
|
|
232
|
+
if (fileTokens !== null) {
|
|
233
|
+
const tokens = fileTokens;
|
|
234
|
+
if (tokens.shadows) {
|
|
235
|
+
const shadows = tokens.shadows;
|
|
236
|
+
for (const [key, value] of Object.entries(shadows)) {
|
|
237
|
+
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('');
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
85
247
|
}
|
|
86
248
|
// Validate color token structure & values
|
|
87
249
|
if (fileTokens !== null) {
|
|
@@ -139,8 +301,8 @@ export async function runBuild(options = {}) {
|
|
|
139
301
|
config.tokens &&
|
|
140
302
|
!hasShownInlineTokenNotice) {
|
|
141
303
|
console.info('');
|
|
142
|
-
console.info('
|
|
143
|
-
console.info('
|
|
304
|
+
console.info('i Inline tokens detected.');
|
|
305
|
+
console.info('Using tokens.json is recommended for larger or shared projects.');
|
|
144
306
|
console.info('');
|
|
145
307
|
hasShownInlineTokenNotice = true;
|
|
146
308
|
}
|
package/dist/output/write-css.js
CHANGED
|
@@ -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(
|
|
5
|
+
console.log(`i Dry run - skipping write to ${filePath}`);
|
|
6
6
|
return;
|
|
7
7
|
}
|
|
8
8
|
const dir = dirname(filePath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ngcorex/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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",
|