@massimo-cassandro/minimo 0.2.0 → 0.2.2

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/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "@massimo-cassandro/minimo",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Light css framework",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/massimo-cassandro/minimo#readme",
7
7
  "bugs": {
8
8
  "url": "https://github.com/massimo-cassandro/minimo/issues"
9
9
  },
10
+ "bin": {
11
+ "buildTokens": "./src/design-tokens-utilities/build-tokens.mjs",
12
+ "checkUnresolvedProps": "./src/design-tokens-utilities/check-unresolved-custom-props.mjs"
13
+ },
10
14
  "repository": {
11
15
  "type": "git",
12
16
  "url": "git+https://github.com/massimo-cassandro/minimo.git"
@@ -25,7 +29,7 @@
25
29
  },
26
30
  "main": "./src/index.js",
27
31
  "files": [
28
- "src/**/*.{js,mjs,css,svg,tokens.json,tokens.jsonc}"
32
+ "src/**/*.{js,mjs,css,svg,tokens.json,tokens.jsonc,md}"
29
33
  ],
30
34
  "devDependencies": {
31
35
  "@massimo-cassandro/dev-updater": "^2.1.0",
@@ -0,0 +1 @@
1
+ Gli spinner hanno tutti la stessa classe, presumendo che se ne usi solo una per volta...
@@ -0,0 +1,2 @@
1
+ * conversione in js?
2
+ * automatizzare la creazione di proprietà ricorrenti (es il padding)
@@ -0,0 +1,501 @@
1
+ // StyleDictionary v.5
2
+ import StyleDictionary from 'style-dictionary';
3
+ import path from 'path';
4
+ import { pathToFileURL } from 'url';
5
+ import { styleText } from 'node:util';
6
+
7
+ import { homedir } from 'os';
8
+
9
+ import stylelint from 'stylelint';
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Risoluzione del config file tramite flag --config
13
+ // ---------------------------------------------------------------------------
14
+ const args = process.argv.slice(2);
15
+ const configFlagIndex = args.indexOf('--config');
16
+
17
+ if (configFlagIndex === -1 || !args[configFlagIndex + 1]) {
18
+ console.error('Errore: devi specificare il file di configurazione con --config ./path/to/config/file');
19
+ process.exit(1);
20
+ }
21
+
22
+ let customPropsCount;
23
+
24
+ // Il percorso passato è relativo alla CWD (root del progetto)
25
+ const configArgPath = args[configFlagIndex + 1];
26
+ const configAbsPath = path.resolve(process.cwd(), configArgPath);
27
+
28
+ // La directory del config file: usata come base per risolvere i percorsi interni
29
+ const configDir = path.dirname(configAbsPath);
30
+
31
+ // Importa il config dinamicamente (usa fileURL per compatibilità ESM su Windows)
32
+ const buildConfig = (await import(pathToFileURL(configAbsPath))).default;
33
+
34
+ // Helper: risolve un percorso relativo rispetto alla dir del config file.
35
+ // Se il percorso è già assoluto lo lascia intatto.
36
+ const resolveFromConfig = (p) => path.resolve(configDir, p);
37
+
38
+ // Risolve tutti i percorsi dichiarati nel config
39
+ const stylelintConfigPath = resolveFromConfig(buildConfig.stylelintConfigPath);
40
+ const buildPath = resolveFromConfig(buildConfig.buildPath);
41
+ const destFile = buildConfig.destFile; // nome file, non un percorso da risolvere
42
+
43
+ // source può contenere glob/percorsi: li risolviamo tutti rispetto alla configDir.
44
+ // path.resolve non gestisce i glob, quindi usiamo path.posix per mantenere le slash
45
+ // forward anche su Windows (fast-glob le richiede).
46
+ const source = buildConfig.source.map((s) => {
47
+ if (path.isAbsolute(s)) return s; // già assoluto, niente da fare
48
+
49
+ // Unisce configDir + s e normalizza, poi ri-converte in forward-slash per fast-glob
50
+ const joined = path.join(configDir, s);
51
+ // Su Windows path.join usa '\', fast-glob vuole '/': normalizziamo sempre
52
+ return joined.split(path.sep).join('/');
53
+ });
54
+
55
+ const stylelintConfig = await import(pathToFileURL(stylelintConfigPath)).then(m => m.default);
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // 1. Shadow Transform
59
+ // ---------------------------------------------------------------------------
60
+ StyleDictionary.registerTransform({
61
+ name: 'shadow/css',
62
+ type: 'value',
63
+ transitive: true,
64
+ filter: (token) => (token.$type === 'shadow' || token.type === 'shadow'),
65
+ transform: (token) => {
66
+ const original = token.original?.$value ?? token.original?.value;
67
+
68
+ const value = (typeof original === 'string' && original.startsWith('{'))
69
+ ? (token.$value ?? token.value)
70
+ : (original ?? token.$value ?? token.value);
71
+
72
+ const shadows = Array.isArray(value) ? value : [value];
73
+
74
+ if (shadows.length === 1 && typeof shadows[0] === 'string') {
75
+ return shadows[0];
76
+ }
77
+
78
+ const BASE_FONT_SIZE = 16;
79
+ const toRem = (val) => {
80
+ if (val === undefined || val === null) return '0';
81
+ const str = String(val).trim();
82
+ const num = parseFloat(str);
83
+ if (isNaN(num)) return str;
84
+ if (num === 0) return '0';
85
+ if (str.endsWith('px')) return `${num / BASE_FONT_SIZE}rem`;
86
+ return str;
87
+ };
88
+
89
+ return shadows
90
+ .map((s) => {
91
+ const offsetX = toRem(s.offsetX ?? s.x);
92
+ const offsetY = toRem(s.offsetY ?? s.y);
93
+ const blur = toRem(s.blur);
94
+ const spread = toRem(s.spread);
95
+ const color = s.color ?? 'transparent';
96
+ const inset = s.inset ? 'inset ' : '';
97
+ return `${inset}${offsetX} ${offsetY} ${blur} ${spread} ${color}`;
98
+ })
99
+ .join(', ');
100
+ }
101
+ });
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // 2. PX to REM Transform
105
+ // ---------------------------------------------------------------------------
106
+ StyleDictionary.registerTransform({
107
+ name: 'size/pxToRem-smart',
108
+ type: 'value',
109
+ filter: (token) => {
110
+ const value = token.$value || token.value;
111
+ return (token.$type === 'dimension' || token.type === 'dimension')
112
+ && typeof value === 'string'
113
+ && !value.endsWith('rem');
114
+ },
115
+ transform: (token) => {
116
+ const baseFontSize = 16;
117
+ const value = token.$value || token.value;
118
+ const floatValue = parseFloat(value);
119
+
120
+ if (isNaN(floatValue)) return value;
121
+
122
+ return `${floatValue / baseFontSize}rem`;
123
+ }
124
+ });
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // 3. Color passthrough
128
+ // ---------------------------------------------------------------------------
129
+ StyleDictionary.registerTransform({
130
+ name: 'color/css-modern',
131
+ type: 'value',
132
+ transitive: true,
133
+ filter: (token) => token.$type === 'color' || token.type === 'color',
134
+ transform: (token) => {
135
+ const value = token.$value ?? token.value;
136
+ if (typeof value === 'string') return value;
137
+ return value;
138
+ }
139
+ });
140
+
141
+ // ---------------------------------------------------------------------------
142
+ // 4. Gradient Transform
143
+ // ---------------------------------------------------------------------------
144
+ StyleDictionary.registerTransform({
145
+ name: 'gradient/css',
146
+ type: 'value',
147
+ transitive: true,
148
+ filter: (token) => token.$type === 'gradient' || token.type === 'gradient',
149
+ transform: (token) => {
150
+ const original = token.original?.$value ?? token.original?.value;
151
+
152
+ const value = (typeof original === 'string' && original.startsWith('{'))
153
+ ? (token.$value ?? token.value)
154
+ : (original ?? token.$value ?? token.value);
155
+
156
+ if (typeof value === 'string') return value;
157
+
158
+ const { type = 'linear', angle = 90, stops = [] } = value;
159
+
160
+ const stopsList = stops
161
+ .map((s) => {
162
+ let color = s.color ?? 'transparent';
163
+ if (s.alpha !== undefined) {
164
+ color = `color-mix(in srgb, ${color} ${s.alpha * 100}%, transparent)`;
165
+ }
166
+ const position = s.position !== undefined ? ` ${s.position * 100}%` : '';
167
+ return `${color}${position}`;
168
+ })
169
+ .join(', ');
170
+
171
+ if (type === 'linear') return `linear-gradient(${angle}deg, ${stopsList})`;
172
+ if (type === 'radial') return `radial-gradient(circle, ${stopsList})`;
173
+ if (type === 'conic') return `conic-gradient(from ${angle}deg, ${stopsList})`;
174
+
175
+ return `linear-gradient(${angle}${typeof angle === 'number'? 'deg' : ''}, ${stopsList})`;
176
+ }
177
+ });
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // 5. Composite Transform (border, outline, transition, animation)
181
+ // Tutti i tipi che hanno un shorthand CSS diretto.
182
+ // ---------------------------------------------------------------------------
183
+ StyleDictionary.registerTransform({
184
+ name: 'composite/css',
185
+ type: 'value',
186
+ transitive: true,
187
+ filter: (token) => {
188
+ const type = token.$type ?? token.type;
189
+ const value = token.$value ?? token.value;
190
+ return (
191
+ typeof value === 'object' &&
192
+ value !== null &&
193
+ !Array.isArray(value) &&
194
+ ['border', 'outline', 'transition', 'animation'].includes(type)
195
+ );
196
+ },
197
+ transform: (token) => {
198
+ const type = token.$type ?? token.type;
199
+ const v = token.$value ?? token.value;
200
+
201
+ if (type === 'border') {
202
+ const parts = [v.width ?? '1px'];
203
+ if (v.style !== undefined && v.style !== null && v.style !== '') parts.push(v.style);
204
+ parts.push(v.color ?? 'transparent');
205
+ return parts.join(' ');
206
+ }
207
+
208
+ if (type === 'outline') {
209
+ return `${v.width ?? '1px'} ${v.style ?? 'solid'} ${v.color ?? 'transparent'}`;
210
+ }
211
+
212
+ if (type === 'transition') {
213
+ // Ordine: duration | timing-function | delay | property
214
+ return [
215
+ v.duration ?? '0s',
216
+ v.timingFunction ?? 'ease',
217
+ v.delay ?? '0s',
218
+ v.property ?? 'all',
219
+ ].join(' ');
220
+ }
221
+
222
+ if (type === 'animation') {
223
+ // Ordine: duration | timing-function | delay | iteration-count |
224
+ // direction | fill-mode | play-state | name
225
+ return [
226
+ v.duration ?? '0s',
227
+ v.timingFunction ?? 'ease',
228
+ v.delay ?? '0s',
229
+ v.iterationCount ?? '1',
230
+ v.direction ?? 'normal',
231
+ v.fillMode ?? 'none',
232
+ v.playState ?? 'running',
233
+ v.name ?? 'none',
234
+ ].join(' ');
235
+ }
236
+ }
237
+ });
238
+
239
+ // ---------------------------------------------------------------------------
240
+ // 6. Typography Transform
241
+ // Restituisce l'oggetto così com'è: il format lo esploderà in più props.
242
+ // ---------------------------------------------------------------------------
243
+ StyleDictionary.registerTransform({
244
+ name: 'typography/css',
245
+ type: 'value',
246
+ transitive: true,
247
+ filter: (token) => token.$type === 'typography' || token.type === 'typography',
248
+ transform: (token) => token.$value ?? token.value,
249
+ });
250
+
251
+ // ---------------------------------------------------------------------------
252
+ // Format: css/variables-sorted
253
+ // ---------------------------------------------------------------------------
254
+ StyleDictionary.registerFormat({
255
+ name: 'css/variables-sorted',
256
+ format: ({ dictionary, options }) => {
257
+ const selector = options.selector ?? ':root';
258
+
259
+ const tokenByPath = {};
260
+ for (const token of dictionary.allTokens) {
261
+ tokenByPath[token.path.join('.')] = token;
262
+ }
263
+
264
+ const resolveRefs = (str) =>
265
+ String(str).replace(/\{([^}]+)\}/g, (_, p) => {
266
+ const ref = tokenByPath[p];
267
+ return ref ? `var(--${ref.name})` : `{${p}}`;
268
+ });
269
+
270
+ const BASE_FONT_SIZE = 16;
271
+ const toRem = (val) => {
272
+ if (val === undefined || val === null) return '0';
273
+ const str = String(val).trim();
274
+ const num = parseFloat(str);
275
+ if (isNaN(num) || num === 0) return num === 0 ? '0' : str;
276
+ if (str.endsWith('px')) return `${num / BASE_FONT_SIZE}rem`;
277
+ return str;
278
+ };
279
+
280
+ const buildShadow = (original) => {
281
+ const shadows = Array.isArray(original) ? original : [original];
282
+ return shadows.map((s) => {
283
+ const offsetX = toRem(s.offsetX ?? s.x);
284
+ const offsetY = toRem(s.offsetY ?? s.y);
285
+ const blur = toRem(s.blur);
286
+ const spread = toRem(s.spread);
287
+ const color = resolveRefs(s.color ?? 'transparent');
288
+ const inset = s.inset ? 'inset ' : '';
289
+ return `${inset}${offsetX} ${offsetY} ${blur} ${spread} ${color}`;
290
+ }).join(', ');
291
+ };
292
+
293
+ const buildGradient = (original) => {
294
+ const { type = 'linear', angle = 90, stops = [] } = original;
295
+ const stopsList = stops.map((s) => {
296
+ let color = resolveRefs(s.color ?? 'transparent');
297
+ if (s.alpha !== undefined) {
298
+ color = `color-mix(in srgb, ${color} ${s.alpha * 100}%, transparent)`;
299
+ }
300
+ const position = s.position !== undefined ? ` ${s.position * 100}%` : '';
301
+ return `${color}${position}`;
302
+ }).join(', ');
303
+
304
+ if (type === 'radial') return `radial-gradient(circle, ${stopsList})`;
305
+ if (type === 'conic') return `conic-gradient(from ${angle}deg, ${stopsList})`;
306
+ return `linear-gradient(${angle}deg, ${stopsList})`;
307
+ };
308
+
309
+ // border e outline: shorthand con resolveRefs su tutti i campi; style è opzionale
310
+ const buildBorderLike = (original) => {
311
+ const parts = [resolveRefs(String(original.width ?? '1px'))];
312
+ if (original.style !== undefined && original.style !== null && original.style !== '') {
313
+ parts.push(resolveRefs(String(original.style)));
314
+ }
315
+ parts.push(resolveRefs(String(original.color ?? 'transparent')));
316
+ return parts.join(' ');
317
+ };
318
+
319
+ // transition: resolveRefs su tutti i campi
320
+ const buildTransition = (original) => [
321
+ resolveRefs(String(original.duration ?? '0s')),
322
+ resolveRefs(String(original.timingFunction ?? 'ease')),
323
+ resolveRefs(String(original.delay ?? '0s')),
324
+ resolveRefs(String(original.property ?? 'all')),
325
+ ].join(' ');
326
+
327
+ // animation: resolveRefs su tutti i campi
328
+ const buildAnimation = (original) => [
329
+ resolveRefs(String(original.duration ?? '0s')),
330
+ resolveRefs(String(original.timingFunction ?? 'ease')),
331
+ resolveRefs(String(original.delay ?? '0s')),
332
+ resolveRefs(String(original.iterationCount ?? '1')),
333
+ resolveRefs(String(original.direction ?? 'normal')),
334
+ resolveRefs(String(original.fillMode ?? 'none')),
335
+ resolveRefs(String(original.playState ?? 'running')),
336
+ resolveRefs(String(original.name ?? 'none')),
337
+ ].join(' ');
338
+
339
+ const buildTypography = (tokenName, original) => {
340
+ const {
341
+ fontStyle,
342
+ fontVariant,
343
+ fontWeight,
344
+ fontStretch,
345
+ fontSize,
346
+ lineHeight,
347
+ fontFamily,
348
+ // queste non entrano nel shorthand font
349
+ letterSpacing,
350
+ textTransform,
351
+ textDecoration,
352
+ } = original;
353
+
354
+ const lines = [];
355
+
356
+ // shorthand font se ci sono size e family (requisiti minimi CSS)
357
+ if (fontSize && fontFamily) {
358
+ const parts = [];
359
+ if (fontStyle) parts.push(resolveRefs(String(fontStyle)));
360
+ if (fontVariant) parts.push(resolveRefs(String(fontVariant)));
361
+ if (fontWeight) parts.push(resolveRefs(String(fontWeight)));
362
+ if (fontStretch) parts.push(resolveRefs(String(fontStretch)));
363
+
364
+ const sizeSlash = lineHeight
365
+ ? `${resolveRefs(String(fontSize))}/${resolveRefs(String(lineHeight))}`
366
+ : resolveRefs(String(fontSize));
367
+
368
+ parts.push(sizeSlash);
369
+ parts.push(resolveRefs(String(fontFamily)));
370
+
371
+ lines.push(` --${tokenName}-font: ${parts.join(' ')};`);
372
+ }
373
+
374
+ // proprietà che non entrano nel shorthand font
375
+ const EXTRA_PROPS = {
376
+ letterSpacing: ['letter-spacing', letterSpacing],
377
+ textTransform: ['text-transform', textTransform],
378
+ textDecoration: ['text-decoration', textDecoration],
379
+ // fallback: se mancano size o family, le esponiamo comunque separatamente
380
+ ...(!fontSize ? { fontSize: ['font-size', original.fontSize] } : {}),
381
+ ...(!fontFamily ? { fontFamily: ['font-family', original.fontFamily] } : {}),
382
+ };
383
+
384
+ for (const [, [cssProp, val]] of Object.entries(EXTRA_PROPS)) {
385
+ if (val !== undefined) {
386
+ lines.push(` --${tokenName}-${cssProp}: ${resolveRefs(String(val))};`);
387
+ }
388
+ }
389
+
390
+ return lines.join('\n');
391
+ };
392
+
393
+ const lines = dictionary.allTokens
394
+ .slice()
395
+ .sort((a, b) => a.name.localeCompare(b.name))
396
+ .flatMap((token) => {
397
+ const type = token.$type ?? token.type;
398
+ const orig = token.original?.$value ?? token.original?.value;
399
+
400
+ // typography: genera più righe, una per proprietà
401
+ if (type === 'typography') {
402
+ return [buildTypography(token.name, orig ?? {})];
403
+ }
404
+
405
+ let value;
406
+
407
+ if (type === 'shadow' && options.outputReferences) {
408
+ value = (typeof orig === 'string' && orig.startsWith('{'))
409
+ ? resolveRefs(orig)
410
+ : buildShadow(orig);
411
+
412
+ } else if (type === 'gradient' && options.outputReferences) {
413
+ value = (typeof orig === 'string' && orig.startsWith('{'))
414
+ ? resolveRefs(orig)
415
+ : buildGradient(orig);
416
+
417
+ } else if ((type === 'border' || type === 'outline') && options.outputReferences) {
418
+ value = (typeof orig === 'string' && orig.startsWith('{'))
419
+ ? resolveRefs(orig)
420
+ : buildBorderLike(orig);
421
+
422
+ } else if (type === 'transition' && options.outputReferences) {
423
+ value = (typeof orig === 'string' && orig.startsWith('{'))
424
+ ? resolveRefs(orig)
425
+ : buildTransition(orig);
426
+
427
+ } else if (type === 'animation' && options.outputReferences) {
428
+ value = (typeof orig === 'string' && orig.startsWith('{'))
429
+ ? resolveRefs(orig)
430
+ : buildAnimation(orig);
431
+
432
+ } else if (options.outputReferences && typeof orig === 'string' && orig.includes('{')) {
433
+ value = resolveRefs(orig);
434
+
435
+ } else {
436
+ value = String(token.$value ?? token.value);
437
+ }
438
+
439
+ return [` --${token.name}: ${value};`];
440
+ });
441
+
442
+ customPropsCount = lines.length;
443
+ return `${selector} {\n${lines.join('\n')}\n}\n`;
444
+ }
445
+ });
446
+
447
+ // ---------------------------------------------------------------------------
448
+ // Style Dictionary config
449
+ // ---------------------------------------------------------------------------
450
+ const config = {
451
+ source,
452
+
453
+ log: {
454
+ verbosity: 'verbose'
455
+ },
456
+
457
+ platforms: {
458
+ css: {
459
+ buildPath: buildPath + '/',
460
+ transforms: [
461
+ 'attribute/cti',
462
+ 'name/kebab',
463
+ 'time/seconds',
464
+ 'asset/url',
465
+ 'size/pxToRem-smart',
466
+ 'color/css-modern',
467
+ 'shadow/css',
468
+ 'gradient/css',
469
+ 'composite/css', // border, outline, transition, animation
470
+ 'typography/css', // typography (esploso nel format)
471
+ ],
472
+ files: [
473
+ {
474
+ destination: destFile,
475
+ format: 'css/variables-sorted',
476
+ options: {
477
+ outputReferences: true,
478
+ showFileHeader: true,
479
+ }
480
+ }
481
+ ]
482
+ }
483
+ }
484
+ };
485
+
486
+ const sd = new StyleDictionary(config);
487
+ await sd.buildAllPlatforms();
488
+
489
+ await stylelint.lint({
490
+ config: stylelintConfig,
491
+ files: [ path.join(buildPath, destFile) ],
492
+ fix: true
493
+ });
494
+
495
+ console.log(styleText(['yellow'], `[build-tokens] config file : ${configAbsPath.replace(homedir(), '~')}` ));
496
+ console.log(styleText(['yellow'], `[build-tokens] source : ${source.map(i => i.replace(homedir(), '~'))}` ));
497
+ console.log(styleText(['yellow'], `[build-tokens] dest file : ${path.join(buildPath, destFile).replace(homedir(), '~')}` ));
498
+
499
+ console.log(styleText(['green'], `[build-tokens] ${customPropsCount} custom props elaborate`));
500
+
501
+ console.log(styleText(['green'], '**** FINE ****'));
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env node
2
+
3
+ /* eslint-disable no-console */
4
+
5
+ // rivela eventuale custom props css nei file della dir data non presenti nel file css di definizione
6
+ // uso: node check-unresolved-custom-props.mjs --config ./path/to-config.mjs
7
+
8
+ import path from 'path';
9
+ import fs from 'node:fs';
10
+ import { fileURLToPath, pathToFileURL } from 'url';
11
+ import { dirname } from 'path';
12
+ import { glob } from 'node:fs/promises';
13
+ import { styleText, parseArgs } from 'node:util';
14
+
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+
18
+ // 1. Configurazione e parsing dei flag
19
+ const options = {
20
+ config: {
21
+ type: 'string',
22
+ short: 'c',
23
+ },
24
+ // Qui in futuro potrai aggiungere altri flag, es:
25
+ // verbose: { type: 'boolean', short: 'v' }
26
+ };
27
+
28
+ const { values } = parseArgs({ options });
29
+
30
+ if (!values.config) {
31
+ console.error(styleText(['red'], 'Errore: Devi fornire il percorso del config con il flag --config o -c'));
32
+ console.log('Esempio: node check-unresolved-custom-props.mjs --config ./config.mjs');
33
+ process.exit(1);
34
+ }
35
+
36
+ const absoluteConfigPath = path.resolve(process.cwd(), values.config);
37
+ const configDir = path.dirname(absoluteConfigPath);
38
+
39
+ async function run() {
40
+ try {
41
+
42
+ const { default: config } = await import(pathToFileURL(absoluteConfigPath).href);
43
+ console.log(config);
44
+ const custom_prop_file_path = path.resolve(configDir, path.join(config.buildPath, config.destFile));
45
+ const custom_prop_filename = path.basename(custom_prop_file_path);
46
+ const checkdir = path.resolve(configDir, config.dir_to_check);
47
+ const exclude = config.exclude_pattern;
48
+
49
+ const files = [];
50
+ for await (const entry of glob(path.join(checkdir, '/**/*.css'))) {
51
+ if (!entry.endsWith(custom_prop_filename)) {
52
+ files.push(entry);
53
+ }
54
+ }
55
+
56
+ if (!fs.existsSync(custom_prop_file_path)) {
57
+ throw new Error(`File delle proprietà non trovato: ${custom_prop_file_path}`);
58
+ }
59
+
60
+ // lista custom props definiti
61
+ const cssContent = fs.readFileSync(custom_prop_file_path, 'utf-8');
62
+ const regex = /--([a-z0-9-]+)(?=\s*:)/g;
63
+ const propertyNamesList = [...cssContent.matchAll(regex)].map(match => `--${match[1]}`);
64
+ // extra_custom_props_files
65
+
66
+ (config.extra_custom_props_files??[]).forEach(extraCustomePropsFile => {
67
+
68
+ const extraCustomPropsContent = fs.readFileSync(
69
+ path.resolve(configDir, extraCustomePropsFile),
70
+ 'utf-8');
71
+
72
+ const extraMatches = [...extraCustomPropsContent.matchAll(regex)].map(match => `--${match[1]}`);
73
+ propertyNamesList.push(...extraMatches);
74
+ });
75
+
76
+
77
+
78
+ // rilevamento custom props nei vari file css
79
+ const regex2 = /var\(\s*(--[a-z0-9-]+)\s*(,.*?)?\)/g;
80
+ const unresolved_props = [];
81
+ const check = []; // per controllo aggiuntivo esecuzione
82
+
83
+ files.forEach(file => {
84
+ const fileContent = fs.readFileSync(file, 'utf-8');
85
+ // const filename = path.relative(checkdir, file);
86
+ const filename = path.relative(configDir, file);
87
+ const matches = [...fileContent.matchAll(regex2)];
88
+
89
+ matches.forEach(match => {
90
+ const cprop = match[1];
91
+ const isExcluded = exclude.some(exRegex => exRegex.test(cprop));
92
+ const isDefined = propertyNamesList.includes(cprop);
93
+
94
+ const lineNumber = fileContent.substring(0, match.index).split('\n').length;
95
+ if (!isExcluded && !isDefined) {
96
+ // unresolved_props.push(`${filename}:${lineNumber} -> var(${cprop})`);
97
+ unresolved_props.push({file: filename, line: lineNumber, prop: cprop});
98
+ }
99
+
100
+ check.push(`${filename}:${lineNumber} - ${cprop} - unresolved: ${(!isExcluded && !isDefined) ? 'Y' : 'N'}`);
101
+ });
102
+ });
103
+
104
+ unresolved_props.sort((a,b) => a.file.localeCompare(b.file));
105
+
106
+
107
+ const result_file = path.resolve(configDir, 'unresolved-props.md');
108
+
109
+ console.log(styleText(['green'],
110
+ `${unresolved_props.length} custom properties non impostate ` +
111
+ `-> ${path.relative(process.cwd(), result_file)}`)
112
+ );
113
+
114
+ // Output
115
+ fs.writeFileSync(result_file, unresolved_props
116
+ .map(item => `* [${path.basename(item.file)} :: ${item.line}](${item.file}#L${item.line}) -> var(${item.prop})`)
117
+ .join('\n'), 'utf-8');
118
+
119
+ // fs.writeFileSync(path.resolve(configDir, 'full-result.txt'), check.join('\n'), 'utf-8');
120
+
121
+
122
+ console.log(styleText(['green'], '**** FINE ****'));
123
+
124
+ } catch (err) {
125
+ console.log(styleText(['redBright'], err.stack));
126
+ // console.error(styleText(['red'], `Errore: ${err.message}`));
127
+ process.exit(1);
128
+ }
129
+ }
130
+
131
+ run();
@@ -0,0 +1,34 @@
1
+
2
+ import { homedir } from 'os';
3
+ import path from 'path';
4
+
5
+ // TODO spostare percorsi minimo su node modules
6
+ const minimo_path = path.resolve(homedir(), 'Sites/minimo/src');
7
+
8
+ // path relativi a questo file
9
+
10
+ const config = {
11
+ // build tokens
12
+ stylelintConfigPath: '../../stylelint.config.mjs',
13
+ buildPath: '../src/css', // dir in cui salvare il file generato da style dictionary
14
+ destFile: 'custom-properties.css', // nome del file generato da style dictionary
15
+ // tokens files
16
+ source: [
17
+ // '../../node_modules/open-props/open-props.style-dictionary-tokens.json',
18
+ path.join(minimo_path, 'design-tokens/*.jsonc'),
19
+ path.join(minimo_path, 'components/**/*.tokens.jsonc'),
20
+ path.join(minimo_path, 'web-components/**/*.tokens.jsonc'),
21
+ './figma-tokens-OK/*.jsonc',
22
+ ],
23
+
24
+ // eventuali file aggiuntivi con custim props utilizzate nel progetto
25
+ extra_custom_props_files: [
26
+ // '../../node_modules/open-props/open-props.min.css'
27
+ ],
28
+ dir_to_check: '../../src', // percorso della dir che include i css da analizzare
29
+ exclude_pattern: [ // patterns per l'esclusione dal controllo di alcune custom props
30
+ /^--_/,
31
+ ]
32
+ };
33
+
34
+ export default config;
@@ -0,0 +1,12 @@
1
+ ```json
2
+ {
3
+ "build tokens": "npx buildTokens --config ./path/to/tokens-config.mjs",
4
+ "check unresolved props": "npx checkUnresolvedProps --config ./path/to/tokens-config.mjs"
5
+ }
6
+ ```
7
+
8
+ ## TODO
9
+
10
+ * <https://github.com/georgeadamson/postcss-merge-selectors>
11
+ * <https://medium.com/@ashusingh584/chokidar-11290855e2cb>
12
+ * <https://github.com/xtivia/style-dictionary-watcher>