@massimo-cassandro/minimo 0.3.6 → 0.4.1
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 +1 -1
- package/design-tokens/alerts.minimo.tokens.jsonc +28 -0
- package/design-tokens/badge.minimo.tokens.jsonc +42 -0
- package/design-tokens/basic.minimo.tokens.jsonc +58 -0
- package/design-tokens/btn-close.minimo.tokens.jsonc +96 -0
- package/design-tokens/btn-primary.minimo.tokens.jsonc +96 -0
- package/design-tokens/btn.minimo.tokens.jsonc +196 -0
- package/design-tokens/color-accent.minimo.tokens.jsonc +18 -0
- package/design-tokens/color-neutral.minimo.tokens.jsonc +18 -0
- package/design-tokens/color-primary.minimo.tokens.jsonc +18 -0
- package/design-tokens/color-secondary.minimo.tokens.jsonc +18 -0
- package/design-tokens/components/recaptcha.minimo.tokens.jsonc +28 -0
- package/design-tokens/components/sf-macro.minimo.tokens.jsonc +66 -0
- package/design-tokens/components/spinner.minimo.tokens.jsonc +24 -0
- package/design-tokens/components/toast.minimo.tokens.jsonc +32 -0
- package/design-tokens/flex-grid.minimo.tokens.jsonc +24 -0
- package/design-tokens/form.minimo.tokens.jsonc +314 -0
- package/design-tokens/headings.minimo.tokens.jsonc +62 -0
- package/design-tokens/link.minimo.tokens.jsonc +44 -0
- package/design-tokens/radius.minimo.tokens.jsonc +42 -0
- package/design-tokens/recaptcha.minimo.tokens.jsonc +28 -0
- package/design-tokens/sf-macro.minimo.tokens.jsonc +66 -0
- package/design-tokens/simple-datatable.minimo.tokens.jsonc +51 -0
- package/design-tokens/size.minimo.tokens.jsonc +46 -0
- package/design-tokens/spinner.minimo.tokens.jsonc +24 -0
- package/design-tokens/status.minimo.tokens.jsonc +98 -0
- package/design-tokens/table.minimo.tokens.jsonc +64 -0
- package/design-tokens/toast.minimo.tokens.jsonc +32 -0
- package/design-tokens/typography.minimo.tokens.jsonc +102 -0
- package/design-tokens/utility.minimo.tokens.jsonc +14 -0
- package/design-tokens/web-components/simple-datatable.minimo.tokens.jsonc +51 -0
- package/design-tokens/z-index.minimo.tokens.jsonc +14 -0
- package/design-tokens-src/_readme.md +27 -0
- package/design-tokens-src/alerts.minimo.tokens.mjs +25 -0
- package/{src/design-tokens → design-tokens-src}/form.minimo.tokens.jsonc +11 -1
- package/design-tokens-src/size.minimo.tokens.jsonc +105 -0
- package/design-tokens-utilities/build-tokens-src/config.mjs +108 -0
- package/design-tokens-utilities/build-tokens-src/formats/css.mjs +201 -0
- package/design-tokens-utilities/build-tokens-src/formats/penpot.mjs +262 -0
- package/design-tokens-utilities/build-tokens-src/platforms.mjs +80 -0
- package/design-tokens-utilities/build-tokens-src/transforms.mjs +200 -0
- package/design-tokens-utilities/build-tokens.mjs +114 -0
- package/design-tokens-utilities/check-unresolved-custom-props.mjs +137 -0
- package/design-tokens-utilities/readme.md +185 -0
- package/design-tokens-utilities/tokens-config-sample.mjs +98 -0
- package/package.json +10 -6
- package/src/components/{overlay.js → overlay/overlay.js} +1 -1
- package/src/{css/dialog-content.css → components/popup-page/popup-page.css} +1 -1
- package/src/components/{dialog-content.js → popup-page/popup-page.js} +6 -4
- package/src/css/forms/form-group.css +1 -1
- package/src/css/forms/forms.css +13 -7
- package/src/custom-properties-default.css +8 -6
- package/src/icons/x.svg +1 -0
- package/src/web-components/simple-datatable/simple-datatable.js +2 -2
- package/src/web-components/simple-datatable/simple-datatable.module.css +3 -3
- package/src/web-components/simple-datatable/src/parse-cols.js +2 -2
- package/src/design-tokens/TODO.md +0 -2
- package/src/design-tokens/alerts.minimo.tokens.jsonc +0 -25
- package/src/design-tokens/size.minimo.tokens.jsonc +0 -101
- package/src/design-tokens-utilities/build-tokens.mjs +0 -503
- package/src/design-tokens-utilities/check-unresolved-custom-props.mjs +0 -131
- package/src/design-tokens-utilities/config-templates/tokens-config.mjs +0 -34
- package/src/design-tokens-utilities/readme.md +0 -12
- /package/{src/design-tokens → design-tokens-src}/badge.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/basic.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/btn-close.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/btn-primary.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/btn-secondary.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/btn.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/color-accent.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/color-neutral.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/color-primary.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/color-secondary.minimo.tokens.jsonc +0 -0
- /package/{src/components/recaptcha → design-tokens-src/components}/recaptcha.minimo.tokens.jsonc +0 -0
- /package/{src/components/sf-macro → design-tokens-src/components}/sf-macro.minimo.tokens.jsonc +0 -0
- /package/{src/components/spinner → design-tokens-src/components}/spinner.minimo.tokens.jsonc +0 -0
- /package/{src/components/toast → design-tokens-src/components}/toast.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/flex-grid.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/gradients-shadowes.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/headings.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/link.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/radius.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/status.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/table.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/typography.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/utility.minimo.tokens.jsonc +0 -0
- /package/{src/web-components/simple-datatable → design-tokens-src/web-components}/simple-datatable.minimo.tokens.jsonc +0 -0
- /package/{src/design-tokens → design-tokens-src}/z-index.minimo.tokens.jsonc +0 -0
- /package/src/{css → components/overlay}/overlay.css +0 -0
- /package/src/{web-components/simple-datatable/icons → icons}/arrow-down.svg +0 -0
- /package/src/{web-components/simple-datatable/icons → icons}/arrow-up.svg +0 -0
- /package/src/{web-components/simple-datatable/icons → icons}/arrows-down-up.svg +0 -0
- /package/src/{web-components/simple-datatable/icons → icons}/caret-left.svg +0 -0
- /package/src/{web-components/simple-datatable/icons → icons}/caret-right.svg +0 -0
- /package/src/{web-components/simple-datatable/icons → icons}/check-bold.svg +0 -0
- /package/src/{components/modal-alert/svg → icons}/check-circle-duotone.svg +0 -0
- /package/src/{components/modal-alert/svg → icons}/info-duotone.svg +0 -0
- /package/src/{components/modal-alert/svg → icons}/question-duotone.svg +0 -0
- /package/src/{components/modal-alert/svg → icons}/warning-duotone.svg +0 -0
- /package/src/{web-components/simple-datatable/icons → icons}/x-bold.svg +0 -0
- /package/src/{components/modal-alert/svg → icons}/x-circle-duotone.svg +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// build-tokens-src/platforms.mjs
|
|
2
|
+
// Builds and returns the `platforms` object for the Style Dictionary config.
|
|
3
|
+
//
|
|
4
|
+
// The `penpot` platform is included only if penpotBuildPath is set.
|
|
5
|
+
// Output format (json/jsonc) and file splitting are delegated to buildPenpotFiles().
|
|
6
|
+
|
|
7
|
+
import { buildPenpotFiles } from './formats/penpot.mjs';
|
|
8
|
+
|
|
9
|
+
// Transforms applied to the CSS platform
|
|
10
|
+
const CSS_TRANSFORMS = [
|
|
11
|
+
'attribute/cti',
|
|
12
|
+
'name/kebab',
|
|
13
|
+
'time/seconds',
|
|
14
|
+
'asset/url',
|
|
15
|
+
'size/pxToRem-smart',
|
|
16
|
+
'color/css-modern',
|
|
17
|
+
'shadow/css',
|
|
18
|
+
'gradient/css',
|
|
19
|
+
'composite/css',
|
|
20
|
+
'typography/css',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
// Transforms applied to the Penpot platform.
|
|
24
|
+
// Kept minimal on purpose: Penpot expects original values (e.g. "16px" not "1rem")
|
|
25
|
+
// and alias references ({...}) must be preserved to maintain token links.
|
|
26
|
+
const PENPOT_TRANSFORMS = [
|
|
27
|
+
'attribute/cti',
|
|
28
|
+
'name/kebab',
|
|
29
|
+
'color/css-modern',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {object} opts
|
|
34
|
+
* @param {string} opts.buildPath Absolute path for CSS output directory
|
|
35
|
+
* @param {string} opts.destFile CSS output filename (e.g. "tokens.css")
|
|
36
|
+
* @param {string|null} opts.penpotBuildPath Absolute path for Penpot output (null = disabled)
|
|
37
|
+
* @param {string|null} opts.penpotDestFile Base name for aggregated file; null = one file per source
|
|
38
|
+
* @param {'json'|'jsonc'} opts.penpotFormat Output format for Penpot files
|
|
39
|
+
* @param {string[]} opts.concreteFilePaths Concrete (expanded) source file paths for multi-file mode.
|
|
40
|
+
* Collected from sd.allTokens after SD initialisation.
|
|
41
|
+
* Ignored when penpotDestFile is set.
|
|
42
|
+
* @param {'keep'|'calc'|'resolve'} opts.penpotExpressions How to handle math expressions in dimension tokens.
|
|
43
|
+
* @returns {object} platforms object ready for Style Dictionary config
|
|
44
|
+
*/
|
|
45
|
+
export const buildPlatforms = ({
|
|
46
|
+
buildPath,
|
|
47
|
+
destFile,
|
|
48
|
+
penpotBuildPath,
|
|
49
|
+
penpotDestFile,
|
|
50
|
+
penpotFormat,
|
|
51
|
+
penpotExpressions = 'keep',
|
|
52
|
+
concreteFilePaths = [],
|
|
53
|
+
}) => {
|
|
54
|
+
const platforms = {
|
|
55
|
+
css: {
|
|
56
|
+
buildPath: buildPath + '/',
|
|
57
|
+
transforms: CSS_TRANSFORMS,
|
|
58
|
+
files: [
|
|
59
|
+
{
|
|
60
|
+
destination: destFile,
|
|
61
|
+
format: 'css/variables-sorted',
|
|
62
|
+
options: {
|
|
63
|
+
outputReferences: true,
|
|
64
|
+
showFileHeader: true,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
if (penpotBuildPath) {
|
|
72
|
+
platforms.penpot = {
|
|
73
|
+
buildPath: penpotBuildPath + '/',
|
|
74
|
+
transforms: PENPOT_TRANSFORMS,
|
|
75
|
+
files: buildPenpotFiles(concreteFilePaths, penpotDestFile, penpotFormat, penpotExpressions),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return platforms;
|
|
80
|
+
};
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// build-tokens-src/transforms.mjs
|
|
2
|
+
// Registers all custom Style Dictionary transforms.
|
|
3
|
+
// Imported once by build-tokens.mjs before buildAllPlatforms() is called.
|
|
4
|
+
|
|
5
|
+
import StyleDictionary from 'style-dictionary';
|
|
6
|
+
|
|
7
|
+
const BASE_FONT_SIZE = 16;
|
|
8
|
+
|
|
9
|
+
// Converts a px value to rem. Returns the original string unchanged for
|
|
10
|
+
// non-px values, and '0' for zero values regardless of unit.
|
|
11
|
+
const toRem = (val) => {
|
|
12
|
+
if (val === undefined || val === null) return '0';
|
|
13
|
+
const str = String(val).trim();
|
|
14
|
+
const num = parseFloat(str);
|
|
15
|
+
if (isNaN(num)) return str;
|
|
16
|
+
if (num === 0) return '0';
|
|
17
|
+
if (str.endsWith('px')) return `${num / BASE_FONT_SIZE}rem`;
|
|
18
|
+
return str;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// 1. Shadow
|
|
23
|
+
// Converts shadow token objects (single or array) to a CSS box-shadow string.
|
|
24
|
+
// px values are converted to rem. Alias references are resolved transitively.
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
StyleDictionary.registerTransform({
|
|
27
|
+
name: 'shadow/css',
|
|
28
|
+
type: 'value',
|
|
29
|
+
transitive: true,
|
|
30
|
+
filter: (token) => token.$type === 'shadow' || token.type === 'shadow',
|
|
31
|
+
transform: (token) => {
|
|
32
|
+
const original = token.original?.$value ?? token.original?.value;
|
|
33
|
+
|
|
34
|
+
const value = (typeof original === 'string' && original.startsWith('{'))
|
|
35
|
+
? (token.$value ?? token.value)
|
|
36
|
+
: (original ?? token.$value ?? token.value);
|
|
37
|
+
|
|
38
|
+
const shadows = Array.isArray(value) ? value : [value];
|
|
39
|
+
|
|
40
|
+
// Already a resolved CSS string (e.g. from an alias)
|
|
41
|
+
if (shadows.length === 1 && typeof shadows[0] === 'string') return shadows[0];
|
|
42
|
+
|
|
43
|
+
return shadows
|
|
44
|
+
.map((s) => {
|
|
45
|
+
const offsetX = toRem(s.offsetX ?? s.x);
|
|
46
|
+
const offsetY = toRem(s.offsetY ?? s.y);
|
|
47
|
+
const blur = toRem(s.blur);
|
|
48
|
+
const spread = toRem(s.spread);
|
|
49
|
+
const color = s.color ?? 'transparent';
|
|
50
|
+
const inset = s.inset ? 'inset ' : '';
|
|
51
|
+
return `${inset}${offsetX} ${offsetY} ${blur} ${spread} ${color}`;
|
|
52
|
+
})
|
|
53
|
+
.join(', ');
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// 2. px → rem (dimension tokens only, skips values already in rem)
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
StyleDictionary.registerTransform({
|
|
61
|
+
name: 'size/pxToRem-smart',
|
|
62
|
+
type: 'value',
|
|
63
|
+
filter: (token) => {
|
|
64
|
+
const value = token.$value || token.value;
|
|
65
|
+
return (token.$type === 'dimension' || token.type === 'dimension')
|
|
66
|
+
&& typeof value === 'string'
|
|
67
|
+
&& !value.endsWith('rem');
|
|
68
|
+
},
|
|
69
|
+
transform: (token) => {
|
|
70
|
+
const value = token.$value || token.value;
|
|
71
|
+
const floatValue = parseFloat(value);
|
|
72
|
+
if (isNaN(floatValue)) return value;
|
|
73
|
+
return `${floatValue / BASE_FONT_SIZE}rem`;
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// 3. Color passthrough
|
|
79
|
+
// Ensures the resolved value is returned as-is, supporting both $value and
|
|
80
|
+
// legacy value fields and transitive alias resolution.
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
StyleDictionary.registerTransform({
|
|
83
|
+
name: 'color/css-modern',
|
|
84
|
+
type: 'value',
|
|
85
|
+
transitive: true,
|
|
86
|
+
filter: (token) => token.$type === 'color' || token.type === 'color',
|
|
87
|
+
transform: (token) => token.$value ?? token.value,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// 4. Gradient
|
|
92
|
+
// Converts gradient token objects to CSS gradient functions.
|
|
93
|
+
// Supports linear, radial and conic types. Alias references are resolved
|
|
94
|
+
// transitively; already-resolved strings are returned unchanged.
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
StyleDictionary.registerTransform({
|
|
97
|
+
name: 'gradient/css',
|
|
98
|
+
type: 'value',
|
|
99
|
+
transitive: true,
|
|
100
|
+
filter: (token) => token.$type === 'gradient' || token.type === 'gradient',
|
|
101
|
+
transform: (token) => {
|
|
102
|
+
const original = token.original?.$value ?? token.original?.value;
|
|
103
|
+
|
|
104
|
+
const value = (typeof original === 'string' && original.startsWith('{'))
|
|
105
|
+
? (token.$value ?? token.value)
|
|
106
|
+
: (original ?? token.$value ?? token.value);
|
|
107
|
+
|
|
108
|
+
if (typeof value === 'string') return value;
|
|
109
|
+
|
|
110
|
+
const { type = 'linear', angle = 90, stops = [] } = value;
|
|
111
|
+
|
|
112
|
+
const stopsList = stops
|
|
113
|
+
.map((s) => {
|
|
114
|
+
let color = s.color ?? 'transparent';
|
|
115
|
+
if (s.alpha !== undefined) {
|
|
116
|
+
color = `color-mix(in srgb, ${color} ${s.alpha * 100}%, transparent)`;
|
|
117
|
+
}
|
|
118
|
+
const position = s.position !== undefined ? ` ${s.position * 100}%` : '';
|
|
119
|
+
return `${color}${position}`;
|
|
120
|
+
})
|
|
121
|
+
.join(', ');
|
|
122
|
+
|
|
123
|
+
if (type === 'linear') return `linear-gradient(${angle}deg, ${stopsList})`;
|
|
124
|
+
if (type === 'radial') return `radial-gradient(circle, ${stopsList})`;
|
|
125
|
+
if (type === 'conic') return `conic-gradient(from ${angle}deg, ${stopsList})`;
|
|
126
|
+
|
|
127
|
+
return `linear-gradient(${angle}${typeof angle === 'number' ? 'deg' : ''}, ${stopsList})`;
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// 5. Composite (border, outline, transition, animation)
|
|
133
|
+
// Converts composite token objects to their CSS shorthand equivalents.
|
|
134
|
+
// Only applied to object values; strings pass through unchanged.
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
StyleDictionary.registerTransform({
|
|
137
|
+
name: 'composite/css',
|
|
138
|
+
type: 'value',
|
|
139
|
+
transitive: true,
|
|
140
|
+
filter: (token) => {
|
|
141
|
+
const type = token.$type ?? token.type;
|
|
142
|
+
const value = token.$value ?? token.value;
|
|
143
|
+
return (
|
|
144
|
+
typeof value === 'object' &&
|
|
145
|
+
value !== null &&
|
|
146
|
+
!Array.isArray(value) &&
|
|
147
|
+
['border', 'outline', 'transition', 'animation'].includes(type)
|
|
148
|
+
);
|
|
149
|
+
},
|
|
150
|
+
transform: (token) => {
|
|
151
|
+
const type = token.$type ?? token.type;
|
|
152
|
+
const v = token.$value ?? token.value;
|
|
153
|
+
|
|
154
|
+
if (type === 'border') {
|
|
155
|
+
const parts = [v.width ?? '1px'];
|
|
156
|
+
if (v.style !== undefined && v.style !== null && v.style !== '') parts.push(v.style);
|
|
157
|
+
parts.push(v.color ?? 'transparent');
|
|
158
|
+
return parts.join(' ');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (type === 'outline') {
|
|
162
|
+
return `${v.width ?? '1px'} ${v.style ?? 'solid'} ${v.color ?? 'transparent'}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (type === 'transition') {
|
|
166
|
+
return [
|
|
167
|
+
v.duration ?? '0s',
|
|
168
|
+
v.timingFunction ?? 'ease',
|
|
169
|
+
v.delay ?? '0s',
|
|
170
|
+
v.property ?? 'all',
|
|
171
|
+
].join(' ');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (type === 'animation') {
|
|
175
|
+
return [
|
|
176
|
+
v.duration ?? '0s',
|
|
177
|
+
v.timingFunction ?? 'ease',
|
|
178
|
+
v.delay ?? '0s',
|
|
179
|
+
v.iterationCount ?? '1',
|
|
180
|
+
v.direction ?? 'normal',
|
|
181
|
+
v.fillMode ?? 'none',
|
|
182
|
+
v.playState ?? 'running',
|
|
183
|
+
v.name ?? 'none',
|
|
184
|
+
].join(' ');
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
// 6. Typography passthrough
|
|
191
|
+
// Returns the token value as-is. The css/variables-sorted format is
|
|
192
|
+
// responsible for exploding typography tokens into individual CSS properties.
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
StyleDictionary.registerTransform({
|
|
195
|
+
name: 'typography/css',
|
|
196
|
+
type: 'value',
|
|
197
|
+
transitive: true,
|
|
198
|
+
filter: (token) => token.$type === 'typography' || token.type === 'typography',
|
|
199
|
+
transform: (token) => token.$value ?? token.value,
|
|
200
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// build-tokens.mjs
|
|
4
|
+
// Entry point: imports modules, runs the build, lints the CSS output, prints the log.
|
|
5
|
+
//
|
|
6
|
+
// Project structure:
|
|
7
|
+
// build-tokens.mjs <- this file
|
|
8
|
+
// build-tokens-src/config.mjs <- reads --config flag and resolves all paths
|
|
9
|
+
// build-tokens-src/transforms.mjs <- registers custom Style Dictionary transforms
|
|
10
|
+
// build-tokens-src/formats/css.mjs <- registers the css/variables-sorted format
|
|
11
|
+
// build-tokens-src/formats/penpot.mjs <- registers the json/penpot format, exports helpers
|
|
12
|
+
// build-tokens-src/platforms.mjs <- builds the platforms object (css + optional penpot)
|
|
13
|
+
|
|
14
|
+
import StyleDictionary from 'style-dictionary';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { styleText } from 'node:util';
|
|
17
|
+
import { homedir } from 'os';
|
|
18
|
+
import stylelint from 'stylelint';
|
|
19
|
+
import { pathToFileURL } from 'url';
|
|
20
|
+
|
|
21
|
+
// ── 1. Configuration (args + resolved paths) ────────────────────────────────
|
|
22
|
+
import {
|
|
23
|
+
configAbsPath,
|
|
24
|
+
buildPath,
|
|
25
|
+
destFile,
|
|
26
|
+
stylelintConfigPath,
|
|
27
|
+
penpotBuildPath,
|
|
28
|
+
penpotDestFile,
|
|
29
|
+
penpotFormat,
|
|
30
|
+
penpotExpressions,
|
|
31
|
+
source,
|
|
32
|
+
} from './build-tokens-src/config.mjs';
|
|
33
|
+
|
|
34
|
+
// ── 2. Custom transforms ─────────────────────────────────────────────────────
|
|
35
|
+
import './build-tokens-src/transforms.mjs';
|
|
36
|
+
|
|
37
|
+
// ── 3. CSS format ─────────────────────────────────────────────────────────────
|
|
38
|
+
// customPropsCount is updated by the format at render time
|
|
39
|
+
import { customPropsCount } from './build-tokens-src/formats/css.mjs';
|
|
40
|
+
|
|
41
|
+
// ── 4. Penpot format ──────────────────────────────────────────────────────────
|
|
42
|
+
import { buildPenpotFiles, collectConcreteFilePaths } from './build-tokens-src/formats/penpot.mjs';
|
|
43
|
+
|
|
44
|
+
// ── 5. Platforms builder ──────────────────────────────────────────────────────
|
|
45
|
+
import { buildPlatforms } from './build-tokens-src/platforms.mjs';
|
|
46
|
+
|
|
47
|
+
// ── 6. Collect concrete source file paths (multi-file Penpot mode only) ──────
|
|
48
|
+
// Source entries may be glob patterns. SD expands them internally and stores
|
|
49
|
+
// the concrete path in token.filePath. We need those concrete paths to build
|
|
50
|
+
// the per-file descriptors for the penpot platform.
|
|
51
|
+
//
|
|
52
|
+
// SD v5 lazy-loads sources: `await sd.hasInitialized` triggers loading;
|
|
53
|
+
// after that sd.allTokens is a plain synchronous array with filePath populated.
|
|
54
|
+
//
|
|
55
|
+
// In single-file mode (penpotDestFile is set) we skip this step entirely.
|
|
56
|
+
let concreteFilePaths = [];
|
|
57
|
+
|
|
58
|
+
if (penpotBuildPath && !penpotDestFile) {
|
|
59
|
+
const sdInit = new StyleDictionary({ source, log: { verbosity: 'silent' }, platforms: {} });
|
|
60
|
+
concreteFilePaths = await collectConcreteFilePaths(sdInit);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── 7. Build ──────────────────────────────────────────────────────────────────
|
|
64
|
+
const sd = new StyleDictionary({
|
|
65
|
+
source,
|
|
66
|
+
log: { verbosity: 'verbose' },
|
|
67
|
+
platforms: buildPlatforms({
|
|
68
|
+
buildPath,
|
|
69
|
+
destFile,
|
|
70
|
+
penpotBuildPath,
|
|
71
|
+
penpotDestFile,
|
|
72
|
+
penpotFormat,
|
|
73
|
+
penpotExpressions,
|
|
74
|
+
concreteFilePaths,
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
await sd.buildAllPlatforms();
|
|
79
|
+
|
|
80
|
+
// ── 8. CSS lint ───────────────────────────────────────────────────────────────
|
|
81
|
+
const stylelintConfig = await import(pathToFileURL(stylelintConfigPath)).then(m => m.default);
|
|
82
|
+
|
|
83
|
+
await stylelint.lint({
|
|
84
|
+
config: stylelintConfig,
|
|
85
|
+
files: [path.join(buildPath, destFile)],
|
|
86
|
+
fix: true,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// ── 9. Log ────────────────────────────────────────────────────────────────────
|
|
90
|
+
const short = (p) => p.replace(homedir(), '~');
|
|
91
|
+
|
|
92
|
+
console.log(styleText(['yellow'], `[build-tokens] config file : ${short(configAbsPath)}`));
|
|
93
|
+
console.log(styleText(['yellow'], `[build-tokens] source : ${source.map(short)}`));
|
|
94
|
+
console.log(styleText(['yellow'], `[build-tokens] dest file : ${short(path.join(buildPath, destFile))}`));
|
|
95
|
+
|
|
96
|
+
if (penpotBuildPath) {
|
|
97
|
+
const penpotFiles = buildPenpotFiles(concreteFilePaths, penpotDestFile, penpotFormat, penpotExpressions);
|
|
98
|
+
if (penpotFiles.length === 1) {
|
|
99
|
+
console.log(styleText(['yellow'],
|
|
100
|
+
`[build-tokens] penpot json : ${short(path.join(penpotBuildPath, penpotFiles[0].destination))}`
|
|
101
|
+
));
|
|
102
|
+
} else {
|
|
103
|
+
console.log(styleText(['yellow'],
|
|
104
|
+
`[build-tokens] penpot json : ${penpotFiles.length} files in ${short(penpotBuildPath)}`
|
|
105
|
+
));
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
console.log(styleText(['yellow'],
|
|
109
|
+
`[build-tokens] penpot json : (disabled — penpotBuildPath not set)`
|
|
110
|
+
));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log(styleText(['green'], `[build-tokens] ${customPropsCount} custom properties processed`));
|
|
114
|
+
console.log(styleText(['green'], '**** DONE ****'));
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/* eslint-disable no-console */
|
|
4
|
+
|
|
5
|
+
// check-unresolved-custom-props.mjs
|
|
6
|
+
// Scans CSS files in the configured directory and reports any custom property
|
|
7
|
+
// references (var(--...)) that are not defined in the generated token file.
|
|
8
|
+
//
|
|
9
|
+
// Usage: node check-unresolved-custom-props.mjs --config ./path/to/config.mjs
|
|
10
|
+
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
14
|
+
import { dirname } from 'path';
|
|
15
|
+
import { glob } from 'node:fs/promises';
|
|
16
|
+
import { styleText, parseArgs } from 'node:util';
|
|
17
|
+
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = dirname(__filename);
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// 1. Parse flags
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
const options = {
|
|
25
|
+
config: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
short: 'c',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const { values } = parseArgs({ options });
|
|
32
|
+
|
|
33
|
+
if (!values.config) {
|
|
34
|
+
console.error(styleText(['red'], 'Error: you must provide the config path with --config or -c'));
|
|
35
|
+
console.log('Example: node check-unresolved-custom-props.mjs --config ./config.mjs');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const absoluteConfigPath = path.resolve(process.cwd(), values.config);
|
|
40
|
+
const configDir = path.dirname(absoluteConfigPath);
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// 2. Main
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
async function run() {
|
|
46
|
+
try {
|
|
47
|
+
const { default: config } = await import(pathToFileURL(absoluteConfigPath).href);
|
|
48
|
+
|
|
49
|
+
// Path of the generated CSS file containing all custom property definitions
|
|
50
|
+
const custom_prop_file_path = path.resolve(configDir, path.join(config.buildPath, config.destFile));
|
|
51
|
+
const custom_prop_filename = path.basename(custom_prop_file_path);
|
|
52
|
+
|
|
53
|
+
// Directory to scan for CSS files
|
|
54
|
+
const checkdir = path.resolve(configDir, config.dir_to_check);
|
|
55
|
+
|
|
56
|
+
// Patterns for custom properties to exclude from the unresolved check
|
|
57
|
+
const exclude = config.exclude_pattern ?? [];
|
|
58
|
+
|
|
59
|
+
// Collect all .css files in the scan directory, excluding the token file itself
|
|
60
|
+
const files = [];
|
|
61
|
+
for await (const entry of glob(path.join(checkdir, '/**/*.css'))) {
|
|
62
|
+
if (!entry.endsWith(custom_prop_filename)) {
|
|
63
|
+
files.push(entry);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!fs.existsSync(custom_prop_file_path)) {
|
|
68
|
+
throw new Error(`Token CSS file not found: ${custom_prop_file_path}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// 3. Build the list of defined custom properties
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
const cssContent = fs.readFileSync(custom_prop_file_path, 'utf-8');
|
|
75
|
+
const definitionRegex = /--([a-z0-9-]+)(?=\s*:)/g;
|
|
76
|
+
const propertyNamesList = [...cssContent.matchAll(definitionRegex)].map(m => `--${m[1]}`);
|
|
77
|
+
|
|
78
|
+
// Also include any extra CSS files declared in extra_custom_props_files
|
|
79
|
+
for (const extraFile of (config.extra_custom_props_files ?? [])) {
|
|
80
|
+
const extraContent = fs.readFileSync(path.resolve(configDir, extraFile), 'utf-8');
|
|
81
|
+
const extraMatches = [...extraContent.matchAll(definitionRegex)].map(m => `--${m[1]}`);
|
|
82
|
+
propertyNamesList.push(...extraMatches);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// 4. Scan files for unresolved var() references
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
const usageRegex = /var\(\s*(--[a-z0-9-]+)\s*(,.*?)?\)/g;
|
|
89
|
+
const unresolved_props = [];
|
|
90
|
+
|
|
91
|
+
files.forEach(file => {
|
|
92
|
+
const fileContent = fs.readFileSync(file, 'utf-8');
|
|
93
|
+
const filename = path.relative(configDir, file);
|
|
94
|
+
const matches = [...fileContent.matchAll(usageRegex)];
|
|
95
|
+
|
|
96
|
+
matches.forEach(match => {
|
|
97
|
+
const cprop = match[1];
|
|
98
|
+
const isExcluded = exclude.some(exRegex => exRegex.test(cprop));
|
|
99
|
+
const isDefined = propertyNamesList.includes(cprop);
|
|
100
|
+
const lineNumber = fileContent.substring(0, match.index).split('\n').length;
|
|
101
|
+
|
|
102
|
+
if (!isExcluded && !isDefined) {
|
|
103
|
+
unresolved_props.push({ file: filename, line: lineNumber, prop: cprop });
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
unresolved_props.sort((a, b) => a.file.localeCompare(b.file));
|
|
109
|
+
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// 5. Write the report
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
const result_file = path.resolve(configDir, 'unresolved-props.md');
|
|
114
|
+
|
|
115
|
+
console.log(styleText(['green'],
|
|
116
|
+
`${unresolved_props.length} unresolved custom properties found -> ${path.relative(process.cwd(), result_file)}`
|
|
117
|
+
));
|
|
118
|
+
|
|
119
|
+
fs.writeFileSync(
|
|
120
|
+
result_file,
|
|
121
|
+
unresolved_props
|
|
122
|
+
.map(item =>
|
|
123
|
+
`* [${path.basename(item.file)} :: ${item.line}](${item.file}#L${item.line}) -> var(${item.prop})`
|
|
124
|
+
)
|
|
125
|
+
.join('\n'),
|
|
126
|
+
'utf-8'
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
console.log(styleText(['green'], '**** DONE ****'));
|
|
130
|
+
|
|
131
|
+
} catch (err) {
|
|
132
|
+
console.log(styleText(['redBright'], err.stack));
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
run();
|