@massimo-cassandro/minimo 1.33.7 → 1.35.0
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/design-tokens/README.md +2 -197
- package/dev-tools/README.md +1 -0
- package/dev-tools/design-tokens-builder/README.md +212 -0
- package/dev-tools/design-tokens-builder/build-tokens-src/build-source-modes.mjs +155 -0
- package/{design-tokens/utilities → dev-tools/design-tokens-builder}/build-tokens-src/config.mjs +35 -1
- package/{design-tokens/utilities → dev-tools/design-tokens-builder}/build-tokens-src/formats/css.mjs +16 -4
- package/{design-tokens/utilities → dev-tools/design-tokens-builder}/build-tokens-src/formats/json.mjs +9 -4
- package/dev-tools/design-tokens-builder/build-tokens-src/legacy-tokens-parser.mjs +105 -0
- package/dev-tools/design-tokens-builder/build-tokens-src/merge-css.mjs +192 -0
- package/{design-tokens/utilities → dev-tools/design-tokens-builder}/build-tokens-src/platforms.mjs +3 -1
- package/dev-tools/design-tokens-builder/build-tokens.mjs +219 -0
- package/{design-tokens/utilities → dev-tools/design-tokens-builder}/check-unresolved-custom-props.mjs +70 -13
- package/{design-tokens → dev-tools/design-tokens-builder}/tokens-config-sample.mjs +55 -3
- package/dev-tools/starter-kit/source_files/root/__project__.code-workspace +4 -0
- package/dev-tools/starter-kit/starter-install.sh +2 -2
- package/package.json +6 -8
- package/design-tokens/utilities/build-tokens-src/merge-css.mjs +0 -60
- package/design-tokens/utilities/build-tokens.mjs +0 -153
- /package/{design-tokens/utilities → dev-tools/design-tokens-builder}/build-tokens-src/resolve-source-paths.mjs +0 -0
- /package/{design-tokens/utilities → dev-tools/design-tokens-builder}/build-tokens-src/transforms.mjs +0 -0
|
@@ -25,6 +25,10 @@ import StyleDictionary from 'style-dictionary';
|
|
|
25
25
|
import './build-tokens-src/transforms.mjs';
|
|
26
26
|
import { CSS_TRANSFORMS } from './build-tokens-src/platforms.mjs';
|
|
27
27
|
import { resolveSourcePaths } from './build-tokens-src/resolve-source-paths.mjs';
|
|
28
|
+
import { findMediaModeBlocks } from './build-tokens-src/merge-css.mjs';
|
|
29
|
+
import { LEGACY_TOKENS_PARSER_NAME, registerLegacyTokensParser } from './build-tokens-src/legacy-tokens-parser.mjs';
|
|
30
|
+
|
|
31
|
+
registerLegacyTokensParser();
|
|
28
32
|
|
|
29
33
|
// const __filename = fileURLToPath(import.meta.url);
|
|
30
34
|
// const __dirname = dirname(__filename);
|
|
@@ -90,25 +94,48 @@ async function run() {
|
|
|
90
94
|
const definitionRegex = /--([a-z0-9-]+)(?=\s*:)/g;
|
|
91
95
|
|
|
92
96
|
// defined_props keeps file/line info too, so unused properties (section 6)
|
|
93
|
-
// can be reported with a link back to their definition.
|
|
94
|
-
|
|
97
|
+
// can be reported with a link back to their definition. With sourceModes
|
|
98
|
+
// (see build-tokens-src/config.mjs), the same prop name can legitimately
|
|
99
|
+
// appear once per mode with a different value — `mode` disambiguates
|
|
100
|
+
// which one a given entry is, so section 5 resolves each to the correct
|
|
101
|
+
// per-mode source file instead of mixing them up (see findMediaModeBlocks
|
|
102
|
+
// in build-tokens-src/merge-css.mjs).
|
|
103
|
+
/** @type {{file: string, line: number, prop: string, mode: string|null}[]} */
|
|
95
104
|
const defined_props = [];
|
|
96
105
|
|
|
97
106
|
const cssContent = fs.readFileSync(custom_prop_file_path, 'utf-8');
|
|
98
107
|
const custom_prop_relpath = path.relative(configDir, custom_prop_file_path);
|
|
108
|
+
|
|
109
|
+
// sourceModesBase mirrors the default-resolution logic in
|
|
110
|
+
// build-tokens-src/config.mjs: the configured sourceModesBase if it's
|
|
111
|
+
// actually a key of sourceModes, otherwise the first key.
|
|
112
|
+
const sourceModeNames = config.sourceModes ? Object.keys(config.sourceModes) : [];
|
|
113
|
+
const sourceModesBase = sourceModeNames.includes(config.sourceModesBase)
|
|
114
|
+
? config.sourceModesBase
|
|
115
|
+
: sourceModeNames[0];
|
|
116
|
+
const mediaModeBlocks = config.sourceModes ? findMediaModeBlocks(cssContent) : [];
|
|
117
|
+
|
|
118
|
+
/** @param {number} index @returns {string|null} */
|
|
119
|
+
const modeAtIndex = (index) => {
|
|
120
|
+
if (!config.sourceModes) return null;
|
|
121
|
+
const block = mediaModeBlocks.find(b => index > b.start && index < b.end);
|
|
122
|
+
return block ? block.mode : sourceModesBase;
|
|
123
|
+
};
|
|
124
|
+
|
|
99
125
|
[...cssContent.matchAll(definitionRegex)].forEach(m => {
|
|
100
126
|
const line = cssContent.substring(0, m.index).split('\n').length;
|
|
101
|
-
defined_props.push({ file: custom_prop_relpath, line, prop: `--${m[1]}
|
|
127
|
+
defined_props.push({ file: custom_prop_relpath, line, prop: `--${m[1]}`, mode: modeAtIndex(m.index) });
|
|
102
128
|
});
|
|
103
129
|
|
|
104
|
-
// Also include any extra CSS files declared in extraCustomPropsFiles
|
|
130
|
+
// Also include any extra CSS files declared in extraCustomPropsFiles.
|
|
131
|
+
// These are plain (non-sourceModes) files, so mode is always null.
|
|
105
132
|
for (const extraFile of (config.extraCustomPropsFiles ?? [])) {
|
|
106
133
|
const extraFilePath = path.resolve(configDir, extraFile);
|
|
107
134
|
const extraContent = fs.readFileSync(extraFilePath, 'utf-8');
|
|
108
135
|
const extraRelpath = path.relative(configDir, extraFilePath);
|
|
109
136
|
[...extraContent.matchAll(definitionRegex)].forEach(m => {
|
|
110
137
|
const line = extraContent.substring(0, m.index).split('\n').length;
|
|
111
|
-
defined_props.push({ file: extraRelpath, line, prop: `--${m[1]}
|
|
138
|
+
defined_props.push({ file: extraRelpath, line, prop: `--${m[1]}`, mode: null });
|
|
112
139
|
});
|
|
113
140
|
}
|
|
114
141
|
|
|
@@ -161,28 +188,58 @@ async function run() {
|
|
|
161
188
|
// CSS (or an extraCustomPropsFiles entry) — e.g. manual additions kept
|
|
162
189
|
// across rebuilds via the mergeCustomProps option — have no token source
|
|
163
190
|
// and are reported by name only.
|
|
191
|
+
//
|
|
192
|
+
// With sourceModes, resolution runs once PER MODE, keyed by mode name in
|
|
193
|
+
// sourceFileByPropByMode: the same prop name can be defined by a
|
|
194
|
+
// different source file in each mode (see the `dead: {...}` example in
|
|
195
|
+
// both a light and dark token file), so a single flattened name -> file
|
|
196
|
+
// map would silently point some entries at the wrong mode's file.
|
|
197
|
+
// sourceFileByProp (mode: null) covers the non-sourceModes case and
|
|
198
|
+
// extraCustomPropsFiles entries.
|
|
164
199
|
// ---------------------------------------------------------------------------
|
|
165
200
|
/** @type {Map<string, string>} */
|
|
166
201
|
const sourceFileByProp = new Map();
|
|
167
|
-
|
|
168
|
-
|
|
202
|
+
/** @type {Map<string, Map<string, string>>} */
|
|
203
|
+
const sourceFileByPropByMode = new Map();
|
|
204
|
+
|
|
205
|
+
/** @param {string[]} sourcePatterns @returns {Promise<Map<string,string>>} */
|
|
206
|
+
const resolveSourceFileMap = async (sourcePatterns) => {
|
|
207
|
+
/** @type {Map<string, string>} */
|
|
208
|
+
const map = new Map();
|
|
209
|
+
const resolvedPaths = resolveSourcePaths(sourcePatterns, configDir);
|
|
210
|
+
if (!resolvedPaths.length) return map;
|
|
169
211
|
try {
|
|
170
|
-
const tokenSourcePaths = resolveSourcePaths(config.source, configDir);
|
|
171
212
|
const sd = new StyleDictionary({
|
|
172
|
-
source:
|
|
213
|
+
source: resolvedPaths,
|
|
214
|
+
parsers: [LEGACY_TOKENS_PARSER_NAME],
|
|
173
215
|
log: { verbosity: 'silent' },
|
|
174
216
|
platforms: { css: { transforms: CSS_TRANSFORMS } },
|
|
175
217
|
});
|
|
176
218
|
const dictionary = await sd.getPlatformTokens('css');
|
|
177
|
-
|
|
178
219
|
for (const token of dictionary.allTokens) {
|
|
179
|
-
if (token.filePath)
|
|
220
|
+
if (token.filePath) map.set(`--${token.name}`, token.filePath);
|
|
180
221
|
}
|
|
181
222
|
} catch {
|
|
182
223
|
// Non-fatal: unused properties will be reported by name only.
|
|
183
224
|
}
|
|
225
|
+
return map;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
if (checkUnused && config.sourceModes) {
|
|
229
|
+
for (const [mode, modeSource] of Object.entries(config.sourceModes)) {
|
|
230
|
+
sourceFileByPropByMode.set(mode, await resolveSourceFileMap(modeSource));
|
|
231
|
+
}
|
|
232
|
+
} else if (checkUnused && Array.isArray(config.source) && config.source.length) {
|
|
233
|
+
const map = await resolveSourceFileMap(config.source);
|
|
234
|
+
for (const [prop, filePath] of map) sourceFileByProp.set(prop, filePath);
|
|
184
235
|
}
|
|
185
236
|
|
|
237
|
+
/** @param {{prop: string, mode: string|null}} item @returns {string|undefined} */
|
|
238
|
+
const resolveSourceFilePath = (item) =>
|
|
239
|
+
item.mode
|
|
240
|
+
? sourceFileByPropByMode.get(item.mode)?.get(item.prop)
|
|
241
|
+
: sourceFileByProp.get(item.prop);
|
|
242
|
+
|
|
186
243
|
// ---------------------------------------------------------------------------
|
|
187
244
|
// 6. Build the list of unused custom properties (defined but never
|
|
188
245
|
// referenced via var() in any scanned CSS file) — the opposite check
|
|
@@ -194,7 +251,7 @@ async function run() {
|
|
|
194
251
|
.filter(item => !used_props_set.has(item.prop))
|
|
195
252
|
.filter(item => {
|
|
196
253
|
if (!ignoreUnusedInNodeModules) return true;
|
|
197
|
-
const sourceFilePath =
|
|
254
|
+
const sourceFilePath = resolveSourceFilePath(item);
|
|
198
255
|
if (!sourceFilePath) return true;
|
|
199
256
|
return !/(^|[\\/])node_modules([\\/]|$)/.test(sourceFilePath);
|
|
200
257
|
})
|
|
@@ -232,7 +289,7 @@ async function run() {
|
|
|
232
289
|
'## Unused custom properties\n\n'
|
|
233
290
|
+ unused_props
|
|
234
291
|
.map(item => {
|
|
235
|
-
const sourceFilePath =
|
|
292
|
+
const sourceFilePath = resolveSourceFilePath(item);
|
|
236
293
|
if (!sourceFilePath) return `* \`${item.prop}\``;
|
|
237
294
|
|
|
238
295
|
const relSource = path.relative(configDir, sourceFilePath);
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
// minimo package path in node_modules
|
|
16
|
-
const
|
|
16
|
+
const node_modules_path = '../../node_modules',
|
|
17
|
+
minimo_path = `${node_modules_path}/@massimo-cassandro/minimo`;
|
|
17
18
|
|
|
18
19
|
const config = {
|
|
19
20
|
|
|
@@ -33,6 +34,19 @@ const config = {
|
|
|
33
34
|
// Source token files — accept paths, globs, or a mix of both.
|
|
34
35
|
// Supported formats: .json, .jsonc, .mjs (must export a DTCG-compliant default object)
|
|
35
36
|
//
|
|
37
|
+
// Legacy (non-DTCG) .json files — e.g. exported from Open Props — are
|
|
38
|
+
// auto-detected and converted to DTCG v5 syntax at parse time, so they can
|
|
39
|
+
// be freely mixed with regular DTCG sources (see README.md). Not covered:
|
|
40
|
+
// .jsonc, reserved for hand-authored DTCG sources. These .json files are
|
|
41
|
+
// parsed with plain JSON.parse (no comments, no trailing commas).
|
|
42
|
+
//
|
|
43
|
+
// LIMITATION: a legacy node that is at the same time a token (own
|
|
44
|
+
// value/type) AND a group with further nested children (e.g. Open Props'
|
|
45
|
+
// other.ease.out, which has its own value plus out.1 ... out.5) cannot be
|
|
46
|
+
// represented in DTCG v5 — a node with $value is always a leaf, Style
|
|
47
|
+
// Dictionary does not descend into its children. In that case only the
|
|
48
|
+
// node's own value is converted; the nested children are silently lost.
|
|
49
|
+
//
|
|
36
50
|
// WARNING: do not use path.join() to build glob patterns — it may corrupt
|
|
37
51
|
// the pattern syntax. Use template literals instead:
|
|
38
52
|
// OK: `${minimo_path}/**/*.{json,mjs}`
|
|
@@ -42,13 +56,51 @@ const config = {
|
|
|
42
56
|
`${minimo_path}/src/**/*.tokens.{mjs,jsonc}`, // components tokens
|
|
43
57
|
|
|
44
58
|
// optional extra token source (in this example, openProps, https://open-props.style/)
|
|
45
|
-
//
|
|
59
|
+
// `${node_modules_path}/open-props/open-props.style-dictionary-tokens.json`,
|
|
46
60
|
|
|
47
61
|
// your project tokens
|
|
48
62
|
'./project-tokens/*.{js,mjs,jsonc,json}',
|
|
49
|
-
|
|
50
63
|
],
|
|
51
64
|
|
|
65
|
+
// Optional light/dark (or other) custom properties split — alternative to
|
|
66
|
+
// `source`. If this key is set (a non-null object), `source` above is
|
|
67
|
+
// ignored: each key is a mode name with its own array of source patterns
|
|
68
|
+
// (same rules as `source`: paths, globs, or a mix).
|
|
69
|
+
//
|
|
70
|
+
// The generated CSS composes all modes into a single destFile:
|
|
71
|
+
// - the base mode's props (see sourceModesBase below) go in a top-level
|
|
72
|
+
// `:root { ... }` block, with `color-scheme: <all modes>;` prepended
|
|
73
|
+
// (e.g. "light dark")
|
|
74
|
+
// - every other mode's props go in
|
|
75
|
+
// `@media (prefers-color-scheme: <mode>) { :root { ... } }`, with
|
|
76
|
+
// `color-scheme: <mode>;` prepended
|
|
77
|
+
// customPropsGroups, mergeCustomProps, addLayer, pxToRem etc. all apply
|
|
78
|
+
// the same way as with a single `source`.
|
|
79
|
+
//
|
|
80
|
+
// The JSON output (see JSON TOKENS OUTPUT below) is produced once per
|
|
81
|
+
// mode, with the mode name appended to each filename, e.g. with
|
|
82
|
+
// jsonDestFile: 'tokens' -> "tokens-light.jsonc", "tokens-dark.jsonc".
|
|
83
|
+
//
|
|
84
|
+
// sample:
|
|
85
|
+
// sourceModes: {
|
|
86
|
+
// light: [
|
|
87
|
+
// `${minimo_path}/design-tokens/_src/**/*.tokens.{mjs,jsonc}`,
|
|
88
|
+
// `${minimo_path}/src/**/*.tokens.{mjs,jsonc}`,
|
|
89
|
+
// './project-tokens/light/*.{js,mjs,jsonc,json}',
|
|
90
|
+
// ],
|
|
91
|
+
// dark: [
|
|
92
|
+
// './project-tokens/dark/*.{js,mjs,jsonc,json}',
|
|
93
|
+
// ],
|
|
94
|
+
// },
|
|
95
|
+
sourceModes: null,
|
|
96
|
+
|
|
97
|
+
// With sourceModes set: the mode whose declarations go in the top-level
|
|
98
|
+
// `:root { ... }` block, instead of nested inside its own
|
|
99
|
+
// `@media (prefers-color-scheme: <mode>) { ... }` rule (default: the first
|
|
100
|
+
// key of sourceModes, e.g. 'light'). Ignored when sourceModes is not set.
|
|
101
|
+
sourceModesBase: 'light',
|
|
102
|
+
|
|
103
|
+
|
|
52
104
|
// If true (default), dimension token values expressed in px are converted
|
|
53
105
|
// to rem in the generated CSS. Values in other units (em, %, vh, dvw, ...)
|
|
54
106
|
// are never touched regardless of this option.
|
|
@@ -75,7 +75,7 @@ optionalPackages=(
|
|
|
75
75
|
"@svgdotjs/svg.js | | richiesto da charts/"
|
|
76
76
|
"blurhash | | richiesto da unsplash-page (create-blurhash-canvas)"
|
|
77
77
|
"simple-datatables | | richiesto da s-datatable-component"
|
|
78
|
-
"style-dictionary | dev | richiesto da design-tokens
|
|
78
|
+
"style-dictionary | dev | richiesto da dev-tools/design-tokens-builder (build-tokens)"
|
|
79
79
|
"@cybozu/eslint-config | dev | utilizzato da eslint per il check dei file css"
|
|
80
80
|
"postcss-cli postcss-import postcss-nested cssnano | dev | per css email"
|
|
81
81
|
)
|
|
@@ -421,7 +421,7 @@ force_cat "${MINIMO_PKG_DIR}/src/custom-media.css" "${FRONTEND_INSTALL_PATH}/css
|
|
|
421
421
|
force_cat "${MINIMO_PKG_DIR}/src/minimo.css" "${FRONTEND_INSTALL_PATH}/css/index.css"
|
|
422
422
|
|
|
423
423
|
# config di build-tokens: collocato accanto al css generato (custom-properties.css).
|
|
424
|
-
force_cat "${MINIMO_PKG_DIR}/design-tokens/tokens-config-sample.mjs" "${FRONTEND_INSTALL_PATH}/css/tokens-config.mjs"
|
|
424
|
+
force_cat "${MINIMO_PKG_DIR}/dev-tools/design-tokens-builder/tokens-config-sample.mjs" "${FRONTEND_INSTALL_PATH}/css/tokens-config.mjs"
|
|
425
425
|
|
|
426
426
|
|
|
427
427
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@massimo-cassandro/minimo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.0",
|
|
4
4
|
"description": "Light css framework & utilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
22
|
"bin": {
|
|
23
|
-
"buildTokens": "design-tokens/
|
|
24
|
-
"checkUnresolvedProps": "design-tokens/
|
|
23
|
+
"buildTokens": "dev-tools/design-tokens-builder/build-tokens.mjs",
|
|
24
|
+
"checkUnresolvedProps": "dev-tools/design-tokens-builder/check-unresolved-custom-props.mjs",
|
|
25
25
|
"update-version": "dev-tools/update-version/index.mjs",
|
|
26
26
|
"upd@m": "dev-tools/upd@m/index.mjs",
|
|
27
27
|
"svgIconsTools": "dev-tools/svg-icons-tools/index.mjs",
|
|
@@ -36,16 +36,16 @@
|
|
|
36
36
|
"UPD-version": "npx update-version # --config=./dev-utilities.config.mjs",
|
|
37
37
|
"npm-publish": "npm publish",
|
|
38
38
|
"upd@m": "npx upd@m",
|
|
39
|
-
"build-tokens": "node ./design-tokens/
|
|
39
|
+
"build-tokens": "node ./dev-tools/design-tokens-builder/build-tokens.mjs --config ./design-tokens/tokens-config.mjs",
|
|
40
40
|
"build-margin-padding-css": "node ./scripts/build-margin-padding-css.mjs",
|
|
41
41
|
"generate:types": "node ./scripts/generate-class-types.mjs",
|
|
42
42
|
"prepublishOnly": "npm run generate:types",
|
|
43
|
-
"check unresolved props": "node ./design-tokens/
|
|
43
|
+
"check unresolved props": "node ./dev-tools/design-tokens-builder/check-unresolved-custom-props.mjs --config ./design-tokens/tokens-config.mjs",
|
|
44
44
|
"test": "node --test \"test/**/*.test.mjs\"",
|
|
45
45
|
"test sideEffects tree-shaking": "node ./test/sideEffects-tree-shaking/run-test.mjs",
|
|
46
46
|
"checkJS": "npx tsc --noEmit -p jsconfig.json",
|
|
47
47
|
"checkJS ./src": "npx tsc --noEmit --allowJs --checkJs --target ES2023 --module ESNext --allowSyntheticDefaultImports --moduleResolution bundler declarations.d.ts types/global.d.ts src/**/*.js",
|
|
48
|
-
"
|
|
48
|
+
"npm outdated": "echo '*** ROOT ***' && npm outdated --omit=peer && echo '*** DEMO ***' && npm --prefix ./demo outdated && echo '*** END ***'",
|
|
49
49
|
"npm upd": "echo '*** ROOT ***' && npm update && echo '*** DEMO ***' && npm --prefix ./demo update && echo '*** END ***'"
|
|
50
50
|
},
|
|
51
51
|
"license": "MIT",
|
|
@@ -63,10 +63,8 @@
|
|
|
63
63
|
"src/**/*.{js,mjs,css,svg,md}",
|
|
64
64
|
"types/**/*.d.ts",
|
|
65
65
|
"design-tokens/README.md",
|
|
66
|
-
"design-tokens/tokens-config-sample.mjs",
|
|
67
66
|
"design-tokens/tokens/**/*.{tokens.json,tokens.jsonc}",
|
|
68
67
|
"design-tokens/_src/**/*.{tokens.json,tokens.jsonc,mjs,js,md}",
|
|
69
|
-
"design-tokens/utilities/**/*.{mjs,md}",
|
|
70
68
|
"charts/**/*.{js,mjs,md}",
|
|
71
69
|
"dev-tools/**/*",
|
|
72
70
|
"snippets/**/*",
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
// build-tokens-src/merge-css.mjs
|
|
2
|
-
// Supports the `mergeCustomProps` config option: pre-existing custom
|
|
3
|
-
// properties in the CSS destination file take priority over the ones
|
|
4
|
-
// generated by the current build (manual overrides/additions survive rebuilds).
|
|
5
|
-
//
|
|
6
|
-
// Usage:
|
|
7
|
-
// 1. Call loadExistingCustomProps(absDestPath) BEFORE Style Dictionary runs,
|
|
8
|
-
// since it reads the file that is about to be overwritten.
|
|
9
|
-
// 2. The css/variables-sorted format (see formats/css.mjs) calls
|
|
10
|
-
// mergeCustomProps(generatedMap) while building the output, right before
|
|
11
|
-
// serialising the final CSS text.
|
|
12
|
-
|
|
13
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
14
|
-
|
|
15
|
-
const CUSTOM_PROP_RE = /^\s*--([\w-]+):\s*(.+)$/gm;
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Parses `--name: <tail>` declarations out of a CSS text block. <tail> is
|
|
19
|
-
* captured verbatim (value, terminating `;`, and anything else on the same
|
|
20
|
-
* line, e.g. a trailing comment) so it round-trips unchanged through a merge.
|
|
21
|
-
* Only single-line declarations are supported.
|
|
22
|
-
* @param {string} cssText
|
|
23
|
-
* @returns {Record<string,string>} name → declaration-tail map, in first-seen order
|
|
24
|
-
*/
|
|
25
|
-
export const parseCustomProps = (cssText) => {
|
|
26
|
-
/** @type {Record<string,string>} */
|
|
27
|
-
const map = {};
|
|
28
|
-
for (const match of cssText.matchAll(CUSTOM_PROP_RE)) {
|
|
29
|
-
map[match[1]] = match[2].trim();
|
|
30
|
-
}
|
|
31
|
-
return map;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// null = merge disabled, or destination file not found yet (first build)
|
|
35
|
-
/** @type {Record<string,string>|null} */
|
|
36
|
-
let existingCustomProps = null;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Reads the CSS destination file (if it exists) and stores a name → value
|
|
40
|
-
* map of its current custom properties for later merging.
|
|
41
|
-
* @param {string} absDestPath absolute path of the CSS file to read
|
|
42
|
-
* @returns {Record<string,string>|null}
|
|
43
|
-
*/
|
|
44
|
-
export const loadExistingCustomProps = (absDestPath) => {
|
|
45
|
-
existingCustomProps = existsSync(absDestPath)
|
|
46
|
-
? parseCustomProps(readFileSync(absDestPath, 'utf8'))
|
|
47
|
-
: null;
|
|
48
|
-
return existingCustomProps;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Merges freshly generated custom properties with the ones previously loaded
|
|
53
|
-
* via loadExistingCustomProps(). Pre-existing values take priority; custom
|
|
54
|
-
* properties found only in the pre-existing file (no longer generated) are
|
|
55
|
-
* kept as well.
|
|
56
|
-
* @param {Record<string,string>} generated name → value map produced by this build
|
|
57
|
-
* @returns {Record<string,string>}
|
|
58
|
-
*/
|
|
59
|
-
export const mergeCustomProps = (generated) =>
|
|
60
|
-
existingCustomProps ? { ...generated, ...existingCustomProps } : generated;
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/// <reference types="node" />
|
|
3
|
-
/* eslint-disable no-console */
|
|
4
|
-
|
|
5
|
-
// build-tokens.mjs
|
|
6
|
-
// Entry point: imports modules, runs the build, lints the CSS output, prints the log.
|
|
7
|
-
//
|
|
8
|
-
// Project structure:
|
|
9
|
-
// build-tokens.mjs <- this file
|
|
10
|
-
// build-tokens-src/config.mjs <- reads --config flag and resolves all paths
|
|
11
|
-
// build-tokens-src/transforms.mjs <- registers custom Style Dictionary transforms
|
|
12
|
-
// build-tokens-src/formats/css.mjs <- registers the css/variables-sorted format
|
|
13
|
-
// build-tokens-src/formats/json.mjs <- registers the json/tokens format, exports helpers
|
|
14
|
-
// build-tokens-src/platforms.mjs <- builds the platforms object (css + optional json)
|
|
15
|
-
|
|
16
|
-
import StyleDictionary from 'style-dictionary';
|
|
17
|
-
import * as path from 'node:path';
|
|
18
|
-
import { styleText } from 'node:util';
|
|
19
|
-
import { homedir } from 'node:os';
|
|
20
|
-
import stylelint from 'stylelint';
|
|
21
|
-
import { pathToFileURL } from 'node:url';
|
|
22
|
-
|
|
23
|
-
// ── 1. Configuration (args + resolved paths) ────────────────────────────────
|
|
24
|
-
import {
|
|
25
|
-
configAbsPath,
|
|
26
|
-
buildPath,
|
|
27
|
-
destFile,
|
|
28
|
-
stylelintConfigPath,
|
|
29
|
-
jsonBuildPath,
|
|
30
|
-
jsonDestFile,
|
|
31
|
-
jsonFormat,
|
|
32
|
-
jsonExpression,
|
|
33
|
-
source,
|
|
34
|
-
mergeCustomProps,
|
|
35
|
-
customPropsGroups,
|
|
36
|
-
pxToRem,
|
|
37
|
-
addLayer,
|
|
38
|
-
} from './build-tokens-src/config.mjs';
|
|
39
|
-
|
|
40
|
-
// ── 2. Custom transforms ─────────────────────────────────────────────────────
|
|
41
|
-
import './build-tokens-src/transforms.mjs';
|
|
42
|
-
|
|
43
|
-
// ── 3. CSS format ─────────────────────────────────────────────────────────────
|
|
44
|
-
// customPropsCount is updated by the format at render time
|
|
45
|
-
// loadExistingCustomProps supports the mergeCustomProps option (see below)
|
|
46
|
-
import { customPropsCount } from './build-tokens-src/formats/css.mjs';
|
|
47
|
-
import { loadExistingCustomProps } from './build-tokens-src/merge-css.mjs';
|
|
48
|
-
|
|
49
|
-
// ── 4. JSON format ────────────────────────────────────────────────────────────
|
|
50
|
-
import { buildJsonFiles, collectConcreteFilePaths } from './build-tokens-src/formats/json.mjs';
|
|
51
|
-
|
|
52
|
-
// ── 5. Platforms builder ──────────────────────────────────────────────────────
|
|
53
|
-
import { buildPlatforms } from './build-tokens-src/platforms.mjs';
|
|
54
|
-
|
|
55
|
-
// ── 5b. Pre-existing custom properties (mergeCustomProps option) ────────────
|
|
56
|
-
// Read before Style Dictionary runs, since it overwrites the destination
|
|
57
|
-
// file. The css/variables-sorted format (formats/css.mjs) merges these
|
|
58
|
-
// values in — pre-existing values take priority — right before serialising
|
|
59
|
-
// the final CSS output. See merge-css.mjs.
|
|
60
|
-
if (mergeCustomProps) {
|
|
61
|
-
loadExistingCustomProps(path.join(buildPath, destFile));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ── 6. Collect concrete source file paths (multi-file JSON mode only) ────────
|
|
65
|
-
// Source entries may be glob patterns. SD expands them internally and stores
|
|
66
|
-
// the concrete path in token.filePath. We need those concrete paths to build
|
|
67
|
-
// the per-file descriptors for the json platform.
|
|
68
|
-
//
|
|
69
|
-
// SD v5 lazy-loads sources: `await sd.hasInitialized` triggers loading;
|
|
70
|
-
// after that sd.allTokens is a plain synchronous array with filePath populated.
|
|
71
|
-
//
|
|
72
|
-
// In single-file mode (jsonDestFile is set) we skip this step entirely.
|
|
73
|
-
let concreteFilePaths = [];
|
|
74
|
-
|
|
75
|
-
if (jsonBuildPath && !jsonDestFile) {
|
|
76
|
-
const sdInit = new StyleDictionary({ source, log: { verbosity: 'silent' }, platforms: {} });
|
|
77
|
-
concreteFilePaths = await collectConcreteFilePaths(sdInit);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ── 7. Clean json output directory ───────────────────────────────────────────
|
|
81
|
-
// Remove only previously generated *.json/*.jsonc files, so that tokens
|
|
82
|
-
// removed from the source are not left behind as stale artefacts, while any
|
|
83
|
-
// other file placed in jsonBuildPath by the user (e.g. a README) is left
|
|
84
|
-
// untouched. Files are written flat in jsonBuildPath (no subdirectories), so
|
|
85
|
-
// only its top level is scanned.
|
|
86
|
-
if (jsonBuildPath) {
|
|
87
|
-
const { readdir, rm, mkdir } = await import('fs/promises');
|
|
88
|
-
const existingEntries = await readdir(jsonBuildPath, { withFileTypes: true }).catch(() => []);
|
|
89
|
-
await Promise.all(
|
|
90
|
-
existingEntries
|
|
91
|
-
.filter((entry) => entry.isFile() && /\.jsonc?$/.test(entry.name))
|
|
92
|
-
.map((entry) => rm(path.join(jsonBuildPath, entry.name)))
|
|
93
|
-
);
|
|
94
|
-
await mkdir(jsonBuildPath, { recursive: true });
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// ── 8. Build ──────────────────────────────────────────────────────────────────
|
|
98
|
-
const sd = new StyleDictionary({
|
|
99
|
-
source,
|
|
100
|
-
log: { verbosity: 'verbose' },
|
|
101
|
-
platforms: buildPlatforms({
|
|
102
|
-
buildPath,
|
|
103
|
-
destFile,
|
|
104
|
-
jsonBuildPath,
|
|
105
|
-
jsonDestFile,
|
|
106
|
-
jsonFormat,
|
|
107
|
-
jsonExpression,
|
|
108
|
-
concreteFilePaths,
|
|
109
|
-
customPropsGroups,
|
|
110
|
-
pxToRem,
|
|
111
|
-
addLayer,
|
|
112
|
-
}),
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
await sd.buildAllPlatforms();
|
|
116
|
-
|
|
117
|
-
// ── 9. CSS lint ───────────────────────────────────────────────────────────────
|
|
118
|
-
const stylelintConfig = await import(pathToFileURL(stylelintConfigPath).href).then(m => m.default);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
await stylelint.lint({
|
|
122
|
-
config: stylelintConfig,
|
|
123
|
-
files: [path.join(buildPath, destFile)],
|
|
124
|
-
fix: true,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// ── 10. Log ────────────────────────────────────────────────────────────────────
|
|
128
|
-
/** @param {string} p */
|
|
129
|
-
const short = (p) => p.replace(homedir(), '~');
|
|
130
|
-
|
|
131
|
-
console.log(styleText(['yellow'], `[build-tokens] config file : ${short(configAbsPath)}`));
|
|
132
|
-
console.log(styleText(['yellow'], `[build-tokens] source : ${source.map(short)}`));
|
|
133
|
-
console.log(styleText(['yellow'], `[build-tokens] dest file : ${short(path.join(buildPath, destFile))}`));
|
|
134
|
-
|
|
135
|
-
if (jsonBuildPath) {
|
|
136
|
-
const jsonFiles = buildJsonFiles(concreteFilePaths, jsonDestFile, jsonFormat, jsonExpression);
|
|
137
|
-
if (jsonFiles.length === 1) {
|
|
138
|
-
console.log(styleText(['yellow'],
|
|
139
|
-
`[build-tokens] json : ${short(path.join(jsonBuildPath, jsonFiles[0].destination))}`
|
|
140
|
-
));
|
|
141
|
-
} else {
|
|
142
|
-
console.log(styleText(['yellow'],
|
|
143
|
-
`[build-tokens] json : ${jsonFiles.length} files in ${short(jsonBuildPath)}`
|
|
144
|
-
));
|
|
145
|
-
}
|
|
146
|
-
} else {
|
|
147
|
-
console.log(styleText(['yellow'],
|
|
148
|
-
'[build-tokens] json : (disabled — jsonBuildPath not set)'
|
|
149
|
-
));
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
console.log(styleText(['green'], `[build-tokens] ${customPropsCount} custom properties processed`));
|
|
153
|
-
console.log(styleText(['green'], '**** DONE ****'));
|
|
File without changes
|
/package/{design-tokens/utilities → dev-tools/design-tokens-builder}/build-tokens-src/transforms.mjs
RENAMED
|
File without changes
|