@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.
@@ -12,26 +12,34 @@ function readJSON(filePath) {
12
12
  );
13
13
  }
14
14
  }
15
-
16
15
  function ensureDir(dir) {
17
- fs.mkdirSync(dir, { recursive: true });
16
+ fs.mkdirSync(dir, {
17
+ recursive: true,
18
+ });
18
19
  }
19
20
 
20
21
  function isObject(value) {
21
- return value !== null &&
22
- typeof value === 'object' &&
23
- !Array.isArray(value);
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 isObject(value) &&
28
- ('$value' in value || 'value' in value);
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
- ? token.$value
34
- : token.value;
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 typeof value === 'string' &&
43
- value.startsWith('{') &&
44
- value.endsWith('}');
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
- .replace(/^\{/, '')
50
- .replace(/\}$/, '')
51
- .trim();
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
- .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
110
- .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
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 = splitIdentifier(value);
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
- * First normalize compound component words.
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 = normalizedWords
108
+ const rest =
109
+ words
149
110
  .slice(1)
150
- .map(word => {
151
- const lower = word.toLowerCase();
152
-
153
- return lower.charAt(0).toUpperCase() +
154
- lower.slice(1);
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 = first + rest;
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
- * Same semantic key for duplicate detection.
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 (const [key, value] of Object.entries(object || {})) {
141
+ for (
142
+ const [key, value]
143
+ of Object.entries(object || {})
144
+ ) {
189
145
  const currentPath =
190
- prefix
191
- ? `${prefix}.${key}`
192
- : key;
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 = value
172
+ let hex =
173
+ value
217
174
  .replace('#', '')
218
175
  .toUpperCase();
219
176
 
220
177
  if (hex.length === 3) {
221
178
  hex = hex
222
- .split('')
223
- .map(x => x + x)
224
- .join('');
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 (typeof value !== 'string') {
196
+ if (
197
+ typeof value !== 'string'
198
+ ) {
240
199
  return null;
241
200
  }
242
201
 
243
- const match = value.match(
244
- /^(-?\d+(?:\.\d+)?)px$/
245
- );
202
+ const match =
203
+ value.match(
204
+ /^(-?\d+(?:\.\d+)?)px$/,
205
+ );
246
206
 
247
- return match ? match[1] : null;
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 = colorValue(value);
236
+ const color =
237
+ colorValue(value);
275
238
 
276
239
  if (!color) {
277
240
  throw new Error(
278
- `❌ Invalid color token: ${tokenPath}\n` +
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
- dimensionValue(value);
253
+ dimensionValue(value);
292
254
 
293
255
  if (dimension === null) {
294
256
  throw new Error(
295
- `❌ Invalid dimension token: ${tokenPath}\n` +
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}\n` +
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.1",
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
- 'global',
3
- 'alias',
4
- 'components',
5
- 'spacing',
6
- 'typography',
7
- 'radius',
8
- 'shadow',
9
- 'dimensions',
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 };