@khaledeltohamy14/token-pipeline-engine 1.0.1 → 1.0.5
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/generators/token-utils.js +107 -147
- package/package.json +1 -1
- package/split/split-token-document.js +11 -58
|
@@ -12,26 +12,34 @@ function readJSON(filePath) {
|
|
|
12
12
|
);
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
|
|
16
15
|
function ensureDir(dir) {
|
|
17
|
-
fs.mkdirSync(dir, {
|
|
16
|
+
fs.mkdirSync(dir, {
|
|
17
|
+
recursive: true,
|
|
18
|
+
});
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
function isObject(value) {
|
|
21
|
-
return
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
return (
|
|
23
|
+
value !== null &&
|
|
24
|
+
typeof value === 'object' &&
|
|
25
|
+
!Array.isArray(value)
|
|
26
|
+
);
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
function isToken(value) {
|
|
27
|
-
return
|
|
28
|
-
|
|
30
|
+
return (
|
|
31
|
+
isObject(value) &&
|
|
32
|
+
(
|
|
33
|
+
'$value' in value ||
|
|
34
|
+
'value' in value
|
|
35
|
+
)
|
|
36
|
+
);
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
function getTokenValue(token) {
|
|
32
40
|
return '$value' in token
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
? token.$value
|
|
42
|
+
: token.value;
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
function getTokenType(token) {
|
|
@@ -39,123 +47,80 @@ function getTokenType(token) {
|
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
function isReference(value) {
|
|
42
|
-
return
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
return (
|
|
51
|
+
typeof value === 'string' &&
|
|
52
|
+
value.startsWith('{') &&
|
|
53
|
+
value.endsWith('}')
|
|
54
|
+
);
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
function cleanReference(value) {
|
|
48
58
|
return value
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
59
|
+
.replace(/^\{/, '')
|
|
60
|
+
.replace(/\}$/, '')
|
|
61
|
+
.trim();
|
|
52
62
|
}
|
|
53
63
|
|
|
54
|
-
|
|
55
|
-
|--------------------------------------------------------------------------
|
|
56
|
-
| Dart naming
|
|
57
|
-
|--------------------------------------------------------------------------
|
|
58
|
-
|
|
|
59
|
-
| JSON naming stays untouched.
|
|
60
|
-
|
|
|
61
|
-
| Dart:
|
|
62
|
-
|
|
|
63
|
-
| red_500 -> red500
|
|
64
|
-
| card_background -> cardBackground
|
|
65
|
-
| textfieldcomponent_padding -> textfieldComponentPadding
|
|
66
|
-
| topRightButtonsComponent -> topRightButtonsComponent
|
|
67
|
-
| toprightbuttonscomponent -> topRightButtonsComponent
|
|
68
|
-
|
|
|
69
|
-
|--------------------------------------------------------------------------
|
|
70
|
-
*/
|
|
71
|
-
|
|
72
|
-
const KNOWN_COMPOUND_NAMES = {
|
|
73
|
-
toprightbuttons: 'topRightButtons',
|
|
74
|
-
textfield: 'textfield',
|
|
75
|
-
button: 'button',
|
|
76
|
-
radio: 'radio',
|
|
77
|
-
card: 'card',
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
function normalizeCompoundWord(word) {
|
|
81
|
-
const lower = word.toLowerCase();
|
|
82
|
-
|
|
83
|
-
/*
|
|
84
|
-
* Handle:
|
|
85
|
-
*
|
|
86
|
-
* textfieldcomponent
|
|
87
|
-
* buttoncomponent
|
|
88
|
-
* radiocomponent
|
|
89
|
-
* cardcomponent
|
|
90
|
-
* toprightbuttonscomponent
|
|
91
|
-
*/
|
|
92
|
-
if (lower.endsWith('component')) {
|
|
93
|
-
const base = lower.substring(
|
|
94
|
-
0,
|
|
95
|
-
lower.length - 'component'.length
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
const normalizedBase =
|
|
99
|
-
KNOWN_COMPOUND_NAMES[base] || base;
|
|
100
|
-
|
|
101
|
-
return `${normalizedBase}Component`;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return KNOWN_COMPOUND_NAMES[lower] || lower;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function splitIdentifier(value) {
|
|
64
|
+
function canonicalTokenKey(value) {
|
|
108
65
|
return String(value)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
.replace(/[_./\\\-]+/g, ' ')
|
|
112
|
-
.trim()
|
|
113
|
-
.split(/\s+/)
|
|
114
|
-
.filter(Boolean);
|
|
66
|
+
.replace(/[^a-zA-Z0-9]/g, '')
|
|
67
|
+
.toLowerCase();
|
|
115
68
|
}
|
|
116
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Convert token path to Dart lowerCamelCase.
|
|
72
|
+
*
|
|
73
|
+
* Examples:
|
|
74
|
+
*
|
|
75
|
+
* primary.500
|
|
76
|
+
* -> primary500
|
|
77
|
+
*
|
|
78
|
+
* text.primary
|
|
79
|
+
* -> textPrimary
|
|
80
|
+
*
|
|
81
|
+
* textFieldComponent.padding
|
|
82
|
+
* -> textFieldComponentPadding
|
|
83
|
+
*
|
|
84
|
+
* topRightButtonsComponent.size
|
|
85
|
+
* -> topRightButtonsComponentSize
|
|
86
|
+
*/
|
|
117
87
|
function toDartIdentifier(value) {
|
|
118
|
-
const words =
|
|
88
|
+
const words = String(value)
|
|
89
|
+
.replace(
|
|
90
|
+
/([a-z0-9])([A-Z])/g,
|
|
91
|
+
'$1 $2',
|
|
92
|
+
)
|
|
93
|
+
.replace(
|
|
94
|
+
/[^a-zA-Z0-9]+/g,
|
|
95
|
+
' ',
|
|
96
|
+
)
|
|
97
|
+
.trim()
|
|
98
|
+
.split(/\s+/)
|
|
99
|
+
.filter(Boolean);
|
|
119
100
|
|
|
120
101
|
if (!words.length) {
|
|
121
102
|
return '_token';
|
|
122
103
|
}
|
|
123
104
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
*/
|
|
127
|
-
const normalizedWords = [];
|
|
128
|
-
|
|
129
|
-
for (finalWord of words) {
|
|
130
|
-
const normalized = normalizeCompoundWord(finalWord);
|
|
131
|
-
|
|
132
|
-
/*
|
|
133
|
-
* A word such as:
|
|
134
|
-
*
|
|
135
|
-
* topRightButtonsComponent
|
|
136
|
-
*
|
|
137
|
-
* can already contain camel casing.
|
|
138
|
-
*/
|
|
139
|
-
const nested = normalized
|
|
140
|
-
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
141
|
-
.split(/\s+/);
|
|
142
|
-
|
|
143
|
-
normalizedWords.push(...nested);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const first = normalizedWords[0].toLowerCase();
|
|
105
|
+
const first =
|
|
106
|
+
words[0].toLowerCase();
|
|
147
107
|
|
|
148
|
-
const rest =
|
|
108
|
+
const rest =
|
|
109
|
+
words
|
|
149
110
|
.slice(1)
|
|
150
|
-
.map(word => {
|
|
151
|
-
const lower =
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
111
|
+
.map((word) => {
|
|
112
|
+
const lower =
|
|
113
|
+
word.toLowerCase();
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
lower.charAt(0).toUpperCase() +
|
|
117
|
+
lower.slice(1)
|
|
118
|
+
);
|
|
155
119
|
})
|
|
156
120
|
.join('');
|
|
157
121
|
|
|
158
|
-
let result =
|
|
122
|
+
let result =
|
|
123
|
+
first + rest;
|
|
159
124
|
|
|
160
125
|
if (/^\d/.test(result)) {
|
|
161
126
|
result = `_${result}`;
|
|
@@ -164,32 +129,23 @@ function toDartIdentifier(value) {
|
|
|
164
129
|
return result;
|
|
165
130
|
}
|
|
166
131
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
* buttonComponent
|
|
171
|
-
* buttoncomponent
|
|
172
|
-
*
|
|
173
|
-
* => buttoncomponent
|
|
174
|
-
*/
|
|
175
|
-
function canonicalTokenKey(value) {
|
|
176
|
-
return String(value)
|
|
177
|
-
.replace(/[^a-zA-Z0-9]/g, '')
|
|
178
|
-
.toLowerCase();
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const toSafeName = toDartIdentifier;
|
|
132
|
+
// Backwards-compatible alias.
|
|
133
|
+
const toSafeName =
|
|
134
|
+
toDartIdentifier;
|
|
182
135
|
|
|
183
136
|
function flattenTokens(
|
|
184
137
|
object,
|
|
185
138
|
prefix = '',
|
|
186
|
-
result = {}
|
|
139
|
+
result = {},
|
|
187
140
|
) {
|
|
188
|
-
for (
|
|
141
|
+
for (
|
|
142
|
+
const [key, value]
|
|
143
|
+
of Object.entries(object || {})
|
|
144
|
+
) {
|
|
189
145
|
const currentPath =
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
146
|
+
prefix
|
|
147
|
+
? `${prefix}.${key}`
|
|
148
|
+
: key;
|
|
193
149
|
|
|
194
150
|
if (isToken(value)) {
|
|
195
151
|
result[currentPath] = value;
|
|
@@ -197,7 +153,7 @@ function flattenTokens(
|
|
|
197
153
|
flattenTokens(
|
|
198
154
|
value,
|
|
199
155
|
currentPath,
|
|
200
|
-
result
|
|
156
|
+
result,
|
|
201
157
|
);
|
|
202
158
|
}
|
|
203
159
|
}
|
|
@@ -213,15 +169,16 @@ function colorValue(value) {
|
|
|
213
169
|
return null;
|
|
214
170
|
}
|
|
215
171
|
|
|
216
|
-
let hex =
|
|
172
|
+
let hex =
|
|
173
|
+
value
|
|
217
174
|
.replace('#', '')
|
|
218
175
|
.toUpperCase();
|
|
219
176
|
|
|
220
177
|
if (hex.length === 3) {
|
|
221
178
|
hex = hex
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
179
|
+
.split('')
|
|
180
|
+
.map((x) => x + x)
|
|
181
|
+
.join('');
|
|
225
182
|
}
|
|
226
183
|
|
|
227
184
|
if (hex.length === 6) {
|
|
@@ -236,15 +193,20 @@ function colorValue(value) {
|
|
|
236
193
|
}
|
|
237
194
|
|
|
238
195
|
function dimensionValue(value) {
|
|
239
|
-
if (
|
|
196
|
+
if (
|
|
197
|
+
typeof value !== 'string'
|
|
198
|
+
) {
|
|
240
199
|
return null;
|
|
241
200
|
}
|
|
242
201
|
|
|
243
|
-
const match =
|
|
244
|
-
|
|
245
|
-
|
|
202
|
+
const match =
|
|
203
|
+
value.match(
|
|
204
|
+
/^(-?\d+(?:\.\d+)?)px$/,
|
|
205
|
+
);
|
|
246
206
|
|
|
247
|
-
return match
|
|
207
|
+
return match
|
|
208
|
+
? match[1]
|
|
209
|
+
: null;
|
|
248
210
|
}
|
|
249
211
|
|
|
250
212
|
function dartType(type) {
|
|
@@ -258,7 +220,7 @@ function dartType(type) {
|
|
|
258
220
|
|
|
259
221
|
if (!types[type]) {
|
|
260
222
|
throw new Error(
|
|
261
|
-
`Unsupported type: ${type}
|
|
223
|
+
`Unsupported type: ${type}`,
|
|
262
224
|
);
|
|
263
225
|
}
|
|
264
226
|
|
|
@@ -268,15 +230,15 @@ function dartType(type) {
|
|
|
268
230
|
function dartLiteral(
|
|
269
231
|
value,
|
|
270
232
|
type,
|
|
271
|
-
tokenPath = 'token'
|
|
233
|
+
tokenPath = 'token',
|
|
272
234
|
) {
|
|
273
235
|
if (type === 'color') {
|
|
274
|
-
const color =
|
|
236
|
+
const color =
|
|
237
|
+
colorValue(value);
|
|
275
238
|
|
|
276
239
|
if (!color) {
|
|
277
240
|
throw new Error(
|
|
278
|
-
`❌ Invalid color token: ${tokenPath}\
|
|
279
|
-
`Value: ${value}`
|
|
241
|
+
`❌ Invalid color token: ${tokenPath}\nValue: ${value}`,
|
|
280
242
|
);
|
|
281
243
|
}
|
|
282
244
|
|
|
@@ -288,12 +250,11 @@ function dartLiteral(
|
|
|
288
250
|
|
|
289
251
|
if (type === 'dimension') {
|
|
290
252
|
const dimension =
|
|
291
|
-
|
|
253
|
+
dimensionValue(value);
|
|
292
254
|
|
|
293
255
|
if (dimension === null) {
|
|
294
256
|
throw new Error(
|
|
295
|
-
`❌ Invalid dimension token: ${tokenPath}\
|
|
296
|
-
`Value: ${value}`
|
|
257
|
+
`❌ Invalid dimension token: ${tokenPath}\nValue: ${value}`,
|
|
297
258
|
);
|
|
298
259
|
}
|
|
299
260
|
|
|
@@ -314,7 +275,7 @@ function dartLiteral(
|
|
|
314
275
|
return {
|
|
315
276
|
type: 'String',
|
|
316
277
|
value: JSON.stringify(
|
|
317
|
-
String(value)
|
|
278
|
+
String(value),
|
|
318
279
|
),
|
|
319
280
|
};
|
|
320
281
|
}
|
|
@@ -329,8 +290,7 @@ function dartLiteral(
|
|
|
329
290
|
}
|
|
330
291
|
|
|
331
292
|
throw new Error(
|
|
332
|
-
`❌ Unsupported token type: ${type}\
|
|
333
|
-
`Token: ${tokenPath}`
|
|
293
|
+
`❌ Unsupported token type: ${type}\nToken: ${tokenPath}`,
|
|
334
294
|
);
|
|
335
295
|
}
|
|
336
296
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khaledeltohamy14/token-pipeline-engine",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Reusable Galaxy design token pipeline engine for splitting design tokens and generating token/theme files.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -1,58 +1,11 @@
|
|
|
1
|
-
const CATEGORY_NAMES = [
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
];
|
|
11
|
-
|
|
12
|
-
function asRecord(value) {
|
|
13
|
-
return value && typeof value === 'object' && !Array.isArray(value)
|
|
14
|
-
? value
|
|
15
|
-
: {};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function splitTokenDocument(document) {
|
|
19
|
-
const source = asRecord(document);
|
|
20
|
-
const tokens = asRecord(source.tokens);
|
|
21
|
-
const result = {};
|
|
22
|
-
|
|
23
|
-
for (const category of CATEGORY_NAMES) {
|
|
24
|
-
result[category] = asRecord(tokens[category]);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
result.brands = asRecord(tokens.brands);
|
|
28
|
-
|
|
29
|
-
return result;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function splitTokenFiles(document, root = 'token_pipeline/tokens') {
|
|
33
|
-
const split = splitTokenDocument(document);
|
|
34
|
-
const files = [];
|
|
35
|
-
|
|
36
|
-
for (const category of CATEGORY_NAMES) {
|
|
37
|
-
files.push({
|
|
38
|
-
path: `${root}/${category}.json`,
|
|
39
|
-
content: `${JSON.stringify(split[category], null, 2)}\n`,
|
|
40
|
-
mode: '100644',
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
for (const [brandName, brandTokens] of Object.entries(split.brands)) {
|
|
45
|
-
files.push({
|
|
46
|
-
path: `token_pipeline/brands/${brandName}.json`,
|
|
47
|
-
content: `${JSON.stringify(asRecord(brandTokens), null, 2)}\n`,
|
|
48
|
-
mode: '100644',
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return files;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
module.exports = {
|
|
56
|
-
splitTokenDocument,
|
|
57
|
-
splitTokenFiles,
|
|
58
|
-
};
|
|
1
|
+
const CATEGORY_NAMES = ['global', 'alias', 'components', 'spacing', 'typography', 'radius', 'shadow', 'dimensions'];
|
|
2
|
+
const ALIASES = { global: 'global', globals: 'global', alias: 'alias', aliases: 'alias', component: 'components', components: 'components', spacing: 'spacing', typography: 'typography', radius: 'radius', shadow: 'shadow', dimension: 'dimensions', dimensions: 'dimensions' };
|
|
3
|
+
const asRecord = v => v && typeof v === 'object' && !Array.isArray(v) ? v : {};
|
|
4
|
+
const norm = v => String(v || '').trim().replace(/[_-]+/g, ' ').replace(/\s+/g, ' ').toLowerCase();
|
|
5
|
+
const clone = v => v && typeof v === 'object' ? (Array.isArray(v) ? v.map(clone) : Object.fromEntries(Object.entries(v).map(([k, x]) => [k, clone(x)]))) : v;
|
|
6
|
+
function setNested(root, path, value) { let cur = root; const parts = path.filter(Boolean); if (!parts.length) return; for (let i = 0; i < parts.length - 1; i++) { if (!cur[parts[i]] || typeof cur[parts[i]] !== 'object' || Array.isArray(cur[parts[i]])) cur[parts[i]] = {}; cur = cur[parts[i]]; } cur[parts.at(-1)] = clone(value); }
|
|
7
|
+
function leaf(v) { return !!(v && typeof v === 'object' && !Array.isArray(v) && ('$value' in v || '$type' in v || v.$extensions?.figma)); }
|
|
8
|
+
function collect(v, path, out) { if (leaf(v)) { const c = norm(v.$extensions?.figma?.collectionName), cat = ALIASES[c]; if (cat) { setNested(out[cat], path, v); } else if (c) { if (!out.brands[c]) out.brands[c] = {}; setNested(out.brands[c], path, v); } return; } if (!v || typeof v !== 'object' || Array.isArray(v)) return; for (const [k, x] of Object.entries(v)) collect(x, path.concat(k), out); }
|
|
9
|
+
function splitTokenDocument(document) { const tokens = asRecord(asRecord(document).tokens); if (CATEGORY_NAMES.some(k => Object.hasOwn(tokens, k))) { const r = {}; for (const k of CATEGORY_NAMES) r[k] = asRecord(tokens[k]); r.brands = asRecord(tokens.brands); return r; } const r = Object.fromEntries(CATEGORY_NAMES.map(k => [k, {}])); r.brands = {}; collect(tokens, [], r); return r; }
|
|
10
|
+
function splitTokenFiles(document, root = 'token_pipeline/tokens') { const s = splitTokenDocument(document), files = CATEGORY_NAMES.map(k => ({ path: `${root}/${k}.json`, content: `${JSON.stringify(s[k], null, 2)}\n`, mode: '100644' })); for (const [name, tokens] of Object.entries(s.brands)) files.push({ path: `token_pipeline/brands/${name}.json`, content: `${JSON.stringify(asRecord(tokens), null, 2)}\n`, mode: '100644' }); return files; }
|
|
11
|
+
module.exports = { splitTokenDocument, splitTokenFiles };
|