@atomixstudio/mcp 0.1.1 → 1.0.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/README.md +72 -288
- package/dist/index.d.ts +1 -201
- package/dist/index.js +1288 -2740
- package/dist/index.js.map +1 -1
- package/package.json +31 -31
- package/data/component-tokens-snapshot.json +0 -659
- package/data/tenants/default.json +0 -73
- package/scripts/sync-component-tokens.cjs +0 -974
- package/scripts/sync-component-tokens.js +0 -678
- package/src/ai-rules-generator.ts +0 -1144
- package/src/component-tokens.ts +0 -702
- package/src/index.ts +0 -1155
- package/src/tenant-store.ts +0 -436
- package/src/tokens.ts +0 -208
- package/src/user-tokens.ts +0 -268
- package/src/utils.ts +0 -465
- package/tests/stress-test.cjs +0 -907
- package/tsconfig.json +0 -21
- package/tsup.config.ts +0 -16
|
@@ -1,974 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Sync Component Tokens from Atomix Lab
|
|
4
|
-
*
|
|
5
|
-
* FUTURE-PROOF DYNAMIC VERSION
|
|
6
|
-
*
|
|
7
|
-
* This script reads component-defaults.ts and COMPONENT_MANIFEST from atomix-lab
|
|
8
|
-
* and generates component-tokens.ts for the MCP server.
|
|
9
|
-
*
|
|
10
|
-
* Features:
|
|
11
|
-
* - Auto-discovers components from COMPONENT_DEFAULTS
|
|
12
|
-
* - Extracts variants/sizes dynamically (no hardcoding)
|
|
13
|
-
* - Uses token-map.json for semantic key → MCP path transformation
|
|
14
|
-
* - Validates all token references
|
|
15
|
-
* - Supports multi-tenant via ATOMIX_TENANT_ID env var
|
|
16
|
-
*
|
|
17
|
-
* Usage:
|
|
18
|
-
* node scripts/sync-component-tokens.cjs
|
|
19
|
-
* ATOMIX_TENANT_ID=acme-corp node scripts/sync-component-tokens.cjs
|
|
20
|
-
*
|
|
21
|
-
* Called automatically by:
|
|
22
|
-
* - npm run sync (in atomix-mcp)
|
|
23
|
-
* - npm run build (in atomix package)
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
const fs = require("fs");
|
|
27
|
-
const path = require("path");
|
|
28
|
-
|
|
29
|
-
// ============================================
|
|
30
|
-
// CONFIGURATION
|
|
31
|
-
// ============================================
|
|
32
|
-
|
|
33
|
-
const CONFIG = {
|
|
34
|
-
tenantId: process.env.ATOMIX_TENANT_ID || "default",
|
|
35
|
-
cloudApiUrl: process.env.ATOMIX_CLOUD_API || null,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
// Paths
|
|
39
|
-
const LAB_DEFAULTS_PATH = path.join(__dirname, "../../../apps/atomix-lab/src/config/component-defaults.ts");
|
|
40
|
-
const LAB_TENANT_OVERRIDES_PATH = path.join(__dirname, "../../../apps/atomix-lab/src/config/tenants");
|
|
41
|
-
const TOKEN_MAP_PATH = path.join(__dirname, "../../atomix/dist/token-map.json");
|
|
42
|
-
const MCP_TOKENS_PATH = path.join(__dirname, "../src/component-tokens.ts");
|
|
43
|
-
const MCP_TENANT_DATA_PATH = path.join(__dirname, "../data/tenants");
|
|
44
|
-
const SNAPSHOT_PATH = path.join(__dirname, "../data/component-tokens-snapshot.json");
|
|
45
|
-
|
|
46
|
-
// ============================================
|
|
47
|
-
// TOKEN MAP LOADING
|
|
48
|
-
// ============================================
|
|
49
|
-
|
|
50
|
-
let tokenMap = null;
|
|
51
|
-
|
|
52
|
-
function loadTokenMap() {
|
|
53
|
-
if (tokenMap) return tokenMap;
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
if (fs.existsSync(TOKEN_MAP_PATH)) {
|
|
57
|
-
tokenMap = JSON.parse(fs.readFileSync(TOKEN_MAP_PATH, "utf-8"));
|
|
58
|
-
console.log(` Loaded token-map.json (${tokenMap.tokenCount} tokens)`);
|
|
59
|
-
} else {
|
|
60
|
-
console.warn(" Warning: token-map.json not found. Run 'npm run build' in packages/atomix first.");
|
|
61
|
-
tokenMap = { tokens: {}, semanticKeys: {} };
|
|
62
|
-
}
|
|
63
|
-
} catch (error) {
|
|
64
|
-
console.warn(" Warning: Failed to parse token-map.json:", error.message);
|
|
65
|
-
tokenMap = { tokens: {}, semanticKeys: {} };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return tokenMap;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ============================================
|
|
72
|
-
// SEMANTIC KEY TRANSFORMER
|
|
73
|
-
// ============================================
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Transforms semantic keys (like "action-primary", "bg-surface") to MCP paths.
|
|
77
|
-
* Uses token-map.json for validation and path lookup.
|
|
78
|
-
*/
|
|
79
|
-
function semanticToMCPPath(semanticKey) {
|
|
80
|
-
const map = loadTokenMap();
|
|
81
|
-
|
|
82
|
-
// Direct mappings for common semantic keys
|
|
83
|
-
const directMappings = {
|
|
84
|
-
// Action colors
|
|
85
|
-
"action-primary": "colors.modes.{mode}.actionPrimary",
|
|
86
|
-
"action-primary-hover": "colors.modes.{mode}.actionPrimaryHover",
|
|
87
|
-
"action-secondary": "colors.modes.{mode}.actionSecondary",
|
|
88
|
-
"action-secondary-hover": "colors.modes.{mode}.actionSecondaryHover",
|
|
89
|
-
"action-destructive": "colors.modes.{mode}.actionDestructive",
|
|
90
|
-
"action-destructive-hover": "colors.modes.{mode}.actionDestructiveHover",
|
|
91
|
-
|
|
92
|
-
// Backgrounds
|
|
93
|
-
"bg-page": "colors.modes.{mode}.bgPage",
|
|
94
|
-
"bg-surface": "colors.modes.{mode}.bgSurface",
|
|
95
|
-
"bg-muted": "colors.modes.{mode}.bgMuted",
|
|
96
|
-
"bg-subtle": "colors.modes.{mode}.bgSubtle",
|
|
97
|
-
"bg-elevated": "colors.modes.{mode}.bgElevated",
|
|
98
|
-
|
|
99
|
-
// Text
|
|
100
|
-
"text-primary": "colors.modes.{mode}.textPrimary",
|
|
101
|
-
"text-secondary": "colors.modes.{mode}.textSecondary",
|
|
102
|
-
"text-muted": "colors.modes.{mode}.textMuted",
|
|
103
|
-
"text-disabled": "colors.modes.{mode}.textDisabled",
|
|
104
|
-
"text-on-brand": "colors.modes.{mode}.textOnBrand",
|
|
105
|
-
|
|
106
|
-
// Borders
|
|
107
|
-
"border-primary": "colors.modes.{mode}.borderPrimary",
|
|
108
|
-
"border-secondary": "colors.modes.{mode}.borderSecondary",
|
|
109
|
-
"border-strong": "colors.modes.{mode}.borderStrong",
|
|
110
|
-
"border-transparent": "transparent",
|
|
111
|
-
"border-error": "colors.adaptive.error.{mode}.border",
|
|
112
|
-
"border-success": "colors.adaptive.success.{mode}.border",
|
|
113
|
-
|
|
114
|
-
// On colors (for indicators)
|
|
115
|
-
"on-action-primary": "colors.static.white",
|
|
116
|
-
"on-action-secondary": "colors.modes.{mode}.textPrimary",
|
|
117
|
-
"on-action-destructive": "colors.static.white",
|
|
118
|
-
"on-bg-primary": "colors.modes.{mode}.onBgPrimary",
|
|
119
|
-
"on-bg-secondary": "colors.modes.{mode}.onBgSecondary",
|
|
120
|
-
|
|
121
|
-
// Brand
|
|
122
|
-
"brand-primary": "colors.static.brand.primary",
|
|
123
|
-
"text-brand": "colors.static.brand.primary",
|
|
124
|
-
"text-brand-secondary": "colors.static.brandSecondary.primary",
|
|
125
|
-
|
|
126
|
-
// Feedback text
|
|
127
|
-
"text-error": "colors.adaptive.error.{mode}.text",
|
|
128
|
-
"text-success": "colors.adaptive.success.{mode}.text",
|
|
129
|
-
"text-warning": "colors.adaptive.warning.{mode}.text",
|
|
130
|
-
"text-info": "colors.adaptive.info.{mode}.text",
|
|
131
|
-
|
|
132
|
-
// Special
|
|
133
|
-
"transparent": "transparent",
|
|
134
|
-
"none": "none",
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// Check direct mapping first
|
|
138
|
-
if (directMappings[semanticKey]) {
|
|
139
|
-
return directMappings[semanticKey];
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Check if it's in the token map's semantic keys
|
|
143
|
-
if (map.semanticKeys && map.semanticKeys[semanticKey]) {
|
|
144
|
-
return map.semanticKeys[semanticKey].mcpPath;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Try to construct from key pattern
|
|
148
|
-
const kebabToCamel = (s) => s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
149
|
-
|
|
150
|
-
// Check if it matches a mode color pattern
|
|
151
|
-
const camelKey = kebabToCamel(semanticKey);
|
|
152
|
-
const lightPath = `colors.modes.light.${camelKey}`;
|
|
153
|
-
if (map.tokens && map.tokens[lightPath]) {
|
|
154
|
-
return `colors.modes.{mode}.${camelKey}`;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Return as-is (might be a non-color token like spacing key)
|
|
158
|
-
return semanticKey;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// ============================================
|
|
162
|
-
// DYNAMIC COMPONENT PARSING
|
|
163
|
-
// ============================================
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Auto-discover components from COMPONENT_DEFAULTS export
|
|
167
|
-
*/
|
|
168
|
-
function discoverComponents(content) {
|
|
169
|
-
const components = [];
|
|
170
|
-
|
|
171
|
-
// Find COMPONENT_DEFAULTS export
|
|
172
|
-
const defaultsMatch = content.match(
|
|
173
|
-
/export const COMPONENT_DEFAULTS\s*=\s*\{([\s\S]*?)\}\s*as const/
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
if (!defaultsMatch) {
|
|
177
|
-
console.error(" Error: Could not find COMPONENT_DEFAULTS export");
|
|
178
|
-
return components;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Extract component names dynamically
|
|
182
|
-
const componentRegex = /(\w+):\s*(\w+)_DEFAULTS/g;
|
|
183
|
-
let match;
|
|
184
|
-
while ((match = componentRegex.exec(defaultsMatch[1])) !== null) {
|
|
185
|
-
components.push({
|
|
186
|
-
key: match[1], // e.g., "button"
|
|
187
|
-
defaultsName: match[2], // e.g., "BUTTON"
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
console.log(` Found ${components.length} components: ${components.map(c => c.key).join(", ")}`);
|
|
192
|
-
|
|
193
|
-
return components;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Parse COMPONENT_MANIFEST for metadata
|
|
198
|
-
*/
|
|
199
|
-
function parseManifest(content) {
|
|
200
|
-
const manifest = {};
|
|
201
|
-
|
|
202
|
-
// Find COMPONENT_MANIFEST export
|
|
203
|
-
const manifestMatch = content.match(
|
|
204
|
-
/export const COMPONENT_MANIFEST[^=]*=\s*\{([\s\S]*?)\n\};/
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
if (!manifestMatch) {
|
|
208
|
-
console.log(" No COMPONENT_MANIFEST found, using auto-generated metadata");
|
|
209
|
-
return manifest;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Parse each component entry
|
|
213
|
-
const entryRegex = /(\w+):\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/g;
|
|
214
|
-
let match;
|
|
215
|
-
while ((match = entryRegex.exec(manifestMatch[1])) !== null) {
|
|
216
|
-
const key = match[1];
|
|
217
|
-
const body = match[2];
|
|
218
|
-
|
|
219
|
-
// Extract fields
|
|
220
|
-
const description = body.match(/description:\s*"([^"]+)"/)?.[1] || `${key} component`;
|
|
221
|
-
const mcpName = body.match(/mcpName:\s*"([^"]+)"/)?.[1] || key;
|
|
222
|
-
const tenantOverridable = body.includes("tenantOverridable: true");
|
|
223
|
-
|
|
224
|
-
// Extract arrays
|
|
225
|
-
const lockedTokens = extractArray(body, "lockedTokens");
|
|
226
|
-
const requiredVariants = extractArray(body, "requiredVariants");
|
|
227
|
-
|
|
228
|
-
manifest[key] = {
|
|
229
|
-
description,
|
|
230
|
-
mcpName,
|
|
231
|
-
tenantOverridable,
|
|
232
|
-
lockedTokens,
|
|
233
|
-
requiredVariants,
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
console.log(` Parsed manifest for ${Object.keys(manifest).length} components`);
|
|
238
|
-
|
|
239
|
-
return manifest;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function extractArray(text, propName) {
|
|
243
|
-
const match = text.match(new RegExp(`${propName}:\\s*\\[([^\\]]*)]`));
|
|
244
|
-
if (!match) return [];
|
|
245
|
-
|
|
246
|
-
const items = [];
|
|
247
|
-
const itemRegex = /"([^"]+)"/g;
|
|
248
|
-
let itemMatch;
|
|
249
|
-
while ((itemMatch = itemRegex.exec(match[1])) !== null) {
|
|
250
|
-
items.push(itemMatch[1]);
|
|
251
|
-
}
|
|
252
|
-
return items;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Extract the raw defaults block for a component
|
|
257
|
-
*/
|
|
258
|
-
function extractDefaultsBlock(content, defaultsName) {
|
|
259
|
-
const regex = new RegExp(
|
|
260
|
-
`export const ${defaultsName}_DEFAULTS[^=]*=\\s*(\\{[\\s\\S]*?\\n\\});`,
|
|
261
|
-
"m"
|
|
262
|
-
);
|
|
263
|
-
const match = content.match(regex);
|
|
264
|
-
return match ? match[1] : null;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Extract object keys from a nested property (variants, sizes, etc.)
|
|
269
|
-
*/
|
|
270
|
-
function extractObjectKeys(block, propName) {
|
|
271
|
-
// Find the property block - handle nested braces
|
|
272
|
-
const propRegex = new RegExp(`${propName}:\\s*\\{`, "g");
|
|
273
|
-
const match = propRegex.exec(block);
|
|
274
|
-
if (!match) return [];
|
|
275
|
-
|
|
276
|
-
// Find matching closing brace
|
|
277
|
-
let depth = 1;
|
|
278
|
-
let i = match.index + match[0].length;
|
|
279
|
-
const start = i;
|
|
280
|
-
|
|
281
|
-
while (i < block.length && depth > 0) {
|
|
282
|
-
if (block[i] === "{") depth++;
|
|
283
|
-
if (block[i] === "}") depth--;
|
|
284
|
-
i++;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const propBody = block.slice(start, i - 1);
|
|
288
|
-
|
|
289
|
-
// Extract top-level keys
|
|
290
|
-
const keys = [];
|
|
291
|
-
const keyRegex = /^\s*["']?([\w-]+)["']?\s*:\s*\{/gm;
|
|
292
|
-
let keyMatch;
|
|
293
|
-
while ((keyMatch = keyRegex.exec(propBody)) !== null) {
|
|
294
|
-
keys.push(keyMatch[1]);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
return keys;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Extract property values from a variant/size block
|
|
302
|
-
*/
|
|
303
|
-
function extractPropertyValues(block, variantKey) {
|
|
304
|
-
const props = {};
|
|
305
|
-
|
|
306
|
-
// Find the variant/size block
|
|
307
|
-
const variantRegex = new RegExp(`["']?${variantKey}["']?:\\s*\\{([\\s\\S]*?)\\n \\}`, "m");
|
|
308
|
-
const match = block.match(variantRegex);
|
|
309
|
-
if (!match) return props;
|
|
310
|
-
|
|
311
|
-
const body = match[1];
|
|
312
|
-
|
|
313
|
-
// Extract simple string properties
|
|
314
|
-
const stringPropRegex = /(\w+):\s*"([^"]+)"/g;
|
|
315
|
-
let propMatch;
|
|
316
|
-
while ((propMatch = stringPropRegex.exec(body)) !== null) {
|
|
317
|
-
props[propMatch[1]] = propMatch[2];
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Extract boolean properties
|
|
321
|
-
const boolPropRegex = /(\w+):\s*(true|false)/g;
|
|
322
|
-
while ((propMatch = boolPropRegex.exec(body)) !== null) {
|
|
323
|
-
props[propMatch[1]] = propMatch[2] === "true";
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
return props;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// ============================================
|
|
330
|
-
// MCP TOKEN GENERATION
|
|
331
|
-
// ============================================
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Generate MCP component tokens from parsed data
|
|
335
|
-
*/
|
|
336
|
-
function generateMCPTokens(components, manifest, content) {
|
|
337
|
-
const output = {};
|
|
338
|
-
|
|
339
|
-
for (const comp of components) {
|
|
340
|
-
const block = extractDefaultsBlock(content, comp.defaultsName);
|
|
341
|
-
if (!block) {
|
|
342
|
-
console.warn(` Warning: Could not find ${comp.defaultsName}_DEFAULTS block`);
|
|
343
|
-
continue;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const variants = extractObjectKeys(block, "variants");
|
|
347
|
-
const sizes = extractObjectKeys(block, "sizes");
|
|
348
|
-
const states = extractObjectKeys(block, "states");
|
|
349
|
-
const validation = extractObjectKeys(block, "validation");
|
|
350
|
-
|
|
351
|
-
const meta = manifest[comp.key] || { description: `${comp.key} component`, mcpName: comp.key };
|
|
352
|
-
const mcpKey = meta.mcpName || comp.key;
|
|
353
|
-
|
|
354
|
-
// Build variant tokens
|
|
355
|
-
const variantTokens = {};
|
|
356
|
-
const variantKeys = variants.length > 0 ? variants : (states.length > 0 ? states : validation);
|
|
357
|
-
|
|
358
|
-
for (const variantKey of variantKeys) {
|
|
359
|
-
const props = extractPropertyValues(block, variantKey);
|
|
360
|
-
const tokens = {};
|
|
361
|
-
|
|
362
|
-
for (const [propKey, propValue] of Object.entries(props)) {
|
|
363
|
-
if (typeof propValue === "string" && !propValue.includes("{")) {
|
|
364
|
-
tokens[propKey] = semanticToMCPPath(propValue);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
if (Object.keys(tokens).length > 0) {
|
|
369
|
-
variantTokens[variantKey] = {
|
|
370
|
-
description: `${variantKey} variant`,
|
|
371
|
-
tokens,
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// Build size tokens
|
|
377
|
-
const sizeTokens = {};
|
|
378
|
-
for (const sizeKey of sizes) {
|
|
379
|
-
const props = extractPropertyValues(block, sizeKey);
|
|
380
|
-
const tokens = {};
|
|
381
|
-
|
|
382
|
-
for (const [propKey, propValue] of Object.entries(props)) {
|
|
383
|
-
if (typeof propValue === "string") {
|
|
384
|
-
// Map spacing/radius keys to paths
|
|
385
|
-
if (["xs", "sm", "md", "lg", "xl", "2xl", "3xl", "4xl"].includes(propValue)) {
|
|
386
|
-
if (propKey.includes("padding") || propKey.includes("gap")) {
|
|
387
|
-
tokens[propKey] = `spacing.scale.${propValue}`;
|
|
388
|
-
} else if (propKey.includes("Radius") || propKey.includes("borderRadius")) {
|
|
389
|
-
tokens[propKey] = `radius.scale.${propValue}`;
|
|
390
|
-
} else {
|
|
391
|
-
tokens[propKey] = propValue;
|
|
392
|
-
}
|
|
393
|
-
} else if (propValue.includes("-") && (propValue.startsWith("text-") || propValue.startsWith("title-") || propValue.startsWith("label-"))) {
|
|
394
|
-
// TypeSet reference
|
|
395
|
-
tokens[propKey] = `typography.typeSets.${propValue}`;
|
|
396
|
-
} else {
|
|
397
|
-
tokens[propKey] = propValue;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
if (Object.keys(tokens).length > 0) {
|
|
403
|
-
sizeTokens[sizeKey] = { tokens };
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// Build component output
|
|
408
|
-
output[mcpKey] = {
|
|
409
|
-
description: meta.description,
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
if (Object.keys(variantTokens).length > 0) {
|
|
413
|
-
output[mcpKey].variants = variantTokens;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
if (Object.keys(sizeTokens).length > 0) {
|
|
417
|
-
output[mcpKey].sizes = sizeTokens;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Add shared tokens (global properties)
|
|
421
|
-
const sharedTokens = {};
|
|
422
|
-
const globalProps = ["fontFamily", "titleFontFamily", "bodyFontFamily"];
|
|
423
|
-
for (const prop of globalProps) {
|
|
424
|
-
const match = block.match(new RegExp(`${prop}:\\s*"([^"]+)"`));
|
|
425
|
-
if (match) {
|
|
426
|
-
sharedTokens[prop] = `typography.fontFamily.${match[1]}`;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
if (Object.keys(sharedTokens).length > 0) {
|
|
431
|
-
output[mcpKey].shared = sharedTokens;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// Handle selectionControls split into checkbox, radio, toggle
|
|
436
|
-
if (output.selectionControls) {
|
|
437
|
-
const base = output.selectionControls;
|
|
438
|
-
|
|
439
|
-
if (base.variants) {
|
|
440
|
-
// Create individual control components
|
|
441
|
-
for (const controlType of ["checkbox", "radio", "toggle"]) {
|
|
442
|
-
if (base.variants[controlType]) {
|
|
443
|
-
output[controlType] = {
|
|
444
|
-
description: `${controlType.charAt(0).toUpperCase() + controlType.slice(1)} selection control`,
|
|
445
|
-
variants: {
|
|
446
|
-
default: base.variants[controlType],
|
|
447
|
-
},
|
|
448
|
-
sizes: base.sizes,
|
|
449
|
-
shared: {
|
|
450
|
-
transitionDuration: "motion.duration.fast",
|
|
451
|
-
focusRing: "shadows.focus.ring",
|
|
452
|
-
},
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// Keep selectionControls as an alias
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
return output;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// ============================================
|
|
465
|
-
// TENANT OVERRIDES LOADING
|
|
466
|
-
// ============================================
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* Load tenant overrides from the Lab's TypeScript files.
|
|
470
|
-
* Parses the TENANT_*_OVERRIDES exports from tenants/[id]/overrides.ts
|
|
471
|
-
*/
|
|
472
|
-
function loadTenantOverrides(tenantId) {
|
|
473
|
-
const overrides = {
|
|
474
|
-
button: { variants: {}, borders: {} },
|
|
475
|
-
card: { variants: {} },
|
|
476
|
-
input: { variants: {} },
|
|
477
|
-
heading: { variants: {} },
|
|
478
|
-
};
|
|
479
|
-
|
|
480
|
-
const tenantOverridesPath = path.join(LAB_TENANT_OVERRIDES_PATH, tenantId, "overrides.ts");
|
|
481
|
-
|
|
482
|
-
if (!fs.existsSync(tenantOverridesPath)) {
|
|
483
|
-
console.log(` No tenant overrides found for ${tenantId}`);
|
|
484
|
-
return overrides;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
try {
|
|
488
|
-
const content = fs.readFileSync(tenantOverridesPath, "utf-8");
|
|
489
|
-
|
|
490
|
-
// Parse TENANT_BUTTON_OVERRIDES
|
|
491
|
-
const buttonMatch = content.match(/TENANT_BUTTON_OVERRIDES[^=]*=\s*\{([\s\S]*?)\n\};/);
|
|
492
|
-
if (buttonMatch) {
|
|
493
|
-
const variantsMatch = buttonMatch[1].match(/variants:\s*\{([\s\S]*?)\n \}/);
|
|
494
|
-
if (variantsMatch) {
|
|
495
|
-
const variantKeys = extractVariantKeys(variantsMatch[1]);
|
|
496
|
-
for (const key of variantKeys) {
|
|
497
|
-
const props = extractPropertyValues(buttonMatch[1], key);
|
|
498
|
-
if (Object.keys(props).length > 0) {
|
|
499
|
-
overrides.button.variants[key] = props;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// Parse TENANT_CARD_OVERRIDES
|
|
506
|
-
const cardMatch = content.match(/TENANT_CARD_OVERRIDES[^=]*=\s*\{([\s\S]*?)\n\};/);
|
|
507
|
-
if (cardMatch) {
|
|
508
|
-
const variantsMatch = cardMatch[1].match(/variants:\s*\{([\s\S]*?)\n \}/);
|
|
509
|
-
if (variantsMatch) {
|
|
510
|
-
const variantKeys = extractVariantKeys(variantsMatch[1]);
|
|
511
|
-
for (const key of variantKeys) {
|
|
512
|
-
const props = extractPropertyValues(cardMatch[1], key);
|
|
513
|
-
if (Object.keys(props).length > 0) {
|
|
514
|
-
overrides.card.variants[key] = props;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
const variantCount = Object.keys(overrides.button.variants).length +
|
|
521
|
-
Object.keys(overrides.card.variants).length;
|
|
522
|
-
if (variantCount > 0) {
|
|
523
|
-
console.log(` Loaded ${variantCount} tenant variant overrides for ${tenantId}`);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
return overrides;
|
|
527
|
-
} catch (error) {
|
|
528
|
-
console.warn(` Warning: Failed to parse tenant overrides for ${tenantId}:`, error.message);
|
|
529
|
-
return overrides;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
/**
|
|
534
|
-
* Extract variant keys from a variants block
|
|
535
|
-
*/
|
|
536
|
-
function extractVariantKeys(block) {
|
|
537
|
-
const keys = [];
|
|
538
|
-
const keyRegex = /^\s*["']?([\w-]+)["']?\s*:\s*\{/gm;
|
|
539
|
-
let match;
|
|
540
|
-
while ((match = keyRegex.exec(block)) !== null) {
|
|
541
|
-
// Skip empty objects or comments
|
|
542
|
-
if (match[1] && !match[1].startsWith("//")) {
|
|
543
|
-
keys.push(match[1]);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
return keys;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
/**
|
|
550
|
-
* Merge tenant overrides into component tokens
|
|
551
|
-
*/
|
|
552
|
-
function mergeTenantOverrides(mcpTokens, tenantOverrides) {
|
|
553
|
-
// Merge button variants
|
|
554
|
-
for (const [variantKey, variantConfig] of Object.entries(tenantOverrides.button.variants)) {
|
|
555
|
-
if (!mcpTokens.button) continue;
|
|
556
|
-
if (!mcpTokens.button.variants) mcpTokens.button.variants = {};
|
|
557
|
-
|
|
558
|
-
mcpTokens.button.variants[variantKey] = {
|
|
559
|
-
description: `${variantKey} variant (tenant override)`,
|
|
560
|
-
tokens: transformConfigToTokens(variantConfig, "button"),
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// Merge card variants
|
|
565
|
-
for (const [variantKey, variantConfig] of Object.entries(tenantOverrides.card.variants)) {
|
|
566
|
-
if (!mcpTokens.card) continue;
|
|
567
|
-
if (!mcpTokens.card.variants) mcpTokens.card.variants = {};
|
|
568
|
-
|
|
569
|
-
mcpTokens.card.variants[variantKey] = {
|
|
570
|
-
description: `${variantKey} variant (tenant override)`,
|
|
571
|
-
tokens: transformConfigToTokens(variantConfig, "card"),
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
return mcpTokens;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
/**
|
|
579
|
-
* Transform variant config to MCP token format
|
|
580
|
-
*/
|
|
581
|
-
function transformConfigToTokens(config, componentType) {
|
|
582
|
-
const tokens = {};
|
|
583
|
-
|
|
584
|
-
for (const [key, value] of Object.entries(config)) {
|
|
585
|
-
if (typeof value === "string") {
|
|
586
|
-
tokens[key] = semanticToMCPPath(value);
|
|
587
|
-
} else if (typeof value === "boolean") {
|
|
588
|
-
// Skip boolean values for MCP tokens
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
return tokens;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// ============================================
|
|
596
|
-
// VALIDATION
|
|
597
|
-
// ============================================
|
|
598
|
-
|
|
599
|
-
function validateTokenReferences(components, tokenMap) {
|
|
600
|
-
const errors = [];
|
|
601
|
-
const warnings = [];
|
|
602
|
-
|
|
603
|
-
// This is a simplified validation - in production you'd check each token
|
|
604
|
-
// against the tokenMap to ensure it exists
|
|
605
|
-
|
|
606
|
-
return { errors, warnings };
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
// ============================================
|
|
610
|
-
// BREAKING CHANGE DETECTION
|
|
611
|
-
// ============================================
|
|
612
|
-
|
|
613
|
-
/**
|
|
614
|
-
* Load the previous component tokens snapshot
|
|
615
|
-
*/
|
|
616
|
-
function loadPreviousSnapshot() {
|
|
617
|
-
try {
|
|
618
|
-
// Ensure data directory exists
|
|
619
|
-
const dataDir = path.dirname(SNAPSHOT_PATH);
|
|
620
|
-
if (!fs.existsSync(dataDir)) {
|
|
621
|
-
fs.mkdirSync(dataDir, { recursive: true });
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
if (!fs.existsSync(SNAPSHOT_PATH)) {
|
|
625
|
-
return null;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
return JSON.parse(fs.readFileSync(SNAPSHOT_PATH, "utf-8"));
|
|
629
|
-
} catch (error) {
|
|
630
|
-
console.warn(" Warning: Could not load previous snapshot:", error.message);
|
|
631
|
-
return null;
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
/**
|
|
636
|
-
* Save current tokens as snapshot for future comparison
|
|
637
|
-
*/
|
|
638
|
-
function saveSnapshot(tokens) {
|
|
639
|
-
try {
|
|
640
|
-
const dataDir = path.dirname(SNAPSHOT_PATH);
|
|
641
|
-
if (!fs.existsSync(dataDir)) {
|
|
642
|
-
fs.mkdirSync(dataDir, { recursive: true });
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
const snapshot = {
|
|
646
|
-
version: "1.0.0",
|
|
647
|
-
timestamp: new Date().toISOString(),
|
|
648
|
-
tenantId: CONFIG.tenantId,
|
|
649
|
-
components: tokens,
|
|
650
|
-
};
|
|
651
|
-
|
|
652
|
-
fs.writeFileSync(SNAPSHOT_PATH, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
653
|
-
} catch (error) {
|
|
654
|
-
console.warn(" Warning: Could not save snapshot:", error.message);
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
/**
|
|
659
|
-
* Detect breaking changes between old and new tokens
|
|
660
|
-
*/
|
|
661
|
-
function detectBreakingChanges(oldTokens, newTokens) {
|
|
662
|
-
const changes = {
|
|
663
|
-
removedComponents: [],
|
|
664
|
-
removedVariants: [],
|
|
665
|
-
removedSizes: [],
|
|
666
|
-
renamedComponents: [],
|
|
667
|
-
addedComponents: [],
|
|
668
|
-
addedVariants: [],
|
|
669
|
-
addedSizes: [],
|
|
670
|
-
};
|
|
671
|
-
|
|
672
|
-
if (!oldTokens) {
|
|
673
|
-
// First run - no comparison possible
|
|
674
|
-
return changes;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
const oldComponentKeys = Object.keys(oldTokens);
|
|
678
|
-
const newComponentKeys = Object.keys(newTokens);
|
|
679
|
-
|
|
680
|
-
// Detect removed components
|
|
681
|
-
for (const key of oldComponentKeys) {
|
|
682
|
-
if (!newComponentKeys.includes(key)) {
|
|
683
|
-
changes.removedComponents.push(key);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// Detect added components
|
|
688
|
-
for (const key of newComponentKeys) {
|
|
689
|
-
if (!oldComponentKeys.includes(key)) {
|
|
690
|
-
changes.addedComponents.push(key);
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// Detect removed/added variants and sizes
|
|
695
|
-
for (const key of newComponentKeys) {
|
|
696
|
-
if (!oldTokens[key]) continue;
|
|
697
|
-
|
|
698
|
-
const oldComp = oldTokens[key];
|
|
699
|
-
const newComp = newTokens[key];
|
|
700
|
-
|
|
701
|
-
// Check variants
|
|
702
|
-
if (oldComp.variants && newComp.variants) {
|
|
703
|
-
const oldVariants = Object.keys(oldComp.variants);
|
|
704
|
-
const newVariants = Object.keys(newComp.variants);
|
|
705
|
-
|
|
706
|
-
for (const v of oldVariants) {
|
|
707
|
-
if (!newVariants.includes(v)) {
|
|
708
|
-
changes.removedVariants.push({ component: key, variant: v });
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
for (const v of newVariants) {
|
|
713
|
-
if (!oldVariants.includes(v)) {
|
|
714
|
-
changes.addedVariants.push({ component: key, variant: v });
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
// Check sizes
|
|
720
|
-
if (oldComp.sizes && newComp.sizes) {
|
|
721
|
-
const oldSizes = Object.keys(oldComp.sizes);
|
|
722
|
-
const newSizes = Object.keys(newComp.sizes);
|
|
723
|
-
|
|
724
|
-
for (const s of oldSizes) {
|
|
725
|
-
if (!newSizes.includes(s)) {
|
|
726
|
-
changes.removedSizes.push({ component: key, size: s });
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
for (const s of newSizes) {
|
|
731
|
-
if (!oldSizes.includes(s)) {
|
|
732
|
-
changes.addedSizes.push({ component: key, size: s });
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
return changes;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
/**
|
|
742
|
-
* Report breaking changes with clear warnings
|
|
743
|
-
*/
|
|
744
|
-
function reportBreakingChanges(changes) {
|
|
745
|
-
const hasBreaking = changes.removedComponents.length > 0 ||
|
|
746
|
-
changes.removedVariants.length > 0 ||
|
|
747
|
-
changes.removedSizes.length > 0;
|
|
748
|
-
|
|
749
|
-
const hasAdditions = changes.addedComponents.length > 0 ||
|
|
750
|
-
changes.addedVariants.length > 0 ||
|
|
751
|
-
changes.addedSizes.length > 0;
|
|
752
|
-
|
|
753
|
-
if (!hasBreaking && !hasAdditions) {
|
|
754
|
-
return;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
console.log("");
|
|
758
|
-
|
|
759
|
-
// Report breaking changes (removals)
|
|
760
|
-
if (hasBreaking) {
|
|
761
|
-
console.log("\x1b[33m ⚠ BREAKING CHANGES DETECTED:\x1b[0m");
|
|
762
|
-
|
|
763
|
-
if (changes.removedComponents.length > 0) {
|
|
764
|
-
console.log("\x1b[31m Removed components:\x1b[0m");
|
|
765
|
-
for (const comp of changes.removedComponents) {
|
|
766
|
-
console.log(` - ${comp}`);
|
|
767
|
-
console.log(` \x1b[2mCode using <${comp.charAt(0).toUpperCase() + comp.slice(1)} /> will break\x1b[0m`);
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
if (changes.removedVariants.length > 0) {
|
|
772
|
-
console.log("\x1b[31m Removed variants:\x1b[0m");
|
|
773
|
-
for (const { component, variant } of changes.removedVariants) {
|
|
774
|
-
console.log(` - ${component}.${variant}`);
|
|
775
|
-
console.log(` \x1b[2mCode using variant="${variant}" will break\x1b[0m`);
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
if (changes.removedSizes.length > 0) {
|
|
780
|
-
console.log("\x1b[31m Removed sizes:\x1b[0m");
|
|
781
|
-
for (const { component, size } of changes.removedSizes) {
|
|
782
|
-
console.log(` - ${component}.${size}`);
|
|
783
|
-
console.log(` \x1b[2mCode using size="${size}" will break\x1b[0m`);
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
console.log("");
|
|
788
|
-
console.log("\x1b[33m Action required: Update consuming code or restore removed items.\x1b[0m");
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
// Report additions (non-breaking)
|
|
792
|
-
if (hasAdditions) {
|
|
793
|
-
console.log("\x1b[32m ✓ New additions:\x1b[0m");
|
|
794
|
-
|
|
795
|
-
if (changes.addedComponents.length > 0) {
|
|
796
|
-
console.log(` Components: ${changes.addedComponents.join(", ")}`);
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
if (changes.addedVariants.length > 0) {
|
|
800
|
-
const grouped = {};
|
|
801
|
-
for (const { component, variant } of changes.addedVariants) {
|
|
802
|
-
if (!grouped[component]) grouped[component] = [];
|
|
803
|
-
grouped[component].push(variant);
|
|
804
|
-
}
|
|
805
|
-
for (const [comp, variants] of Object.entries(grouped)) {
|
|
806
|
-
console.log(` ${comp} variants: ${variants.join(", ")}`);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
if (changes.addedSizes.length > 0) {
|
|
811
|
-
const grouped = {};
|
|
812
|
-
for (const { component, size } of changes.addedSizes) {
|
|
813
|
-
if (!grouped[component]) grouped[component] = [];
|
|
814
|
-
grouped[component].push(size);
|
|
815
|
-
}
|
|
816
|
-
for (const [comp, sizes] of Object.entries(grouped)) {
|
|
817
|
-
console.log(` ${comp} sizes: ${sizes.join(", ")}`);
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
console.log("");
|
|
823
|
-
|
|
824
|
-
return hasBreaking;
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
// ============================================
|
|
828
|
-
// OUTPUT GENERATION
|
|
829
|
-
// ============================================
|
|
830
|
-
|
|
831
|
-
function generateOutputFile(components, manifest, tenantId) {
|
|
832
|
-
const timestamp = new Date().toISOString();
|
|
833
|
-
|
|
834
|
-
return `/**
|
|
835
|
-
* Component Token Mappings
|
|
836
|
-
*
|
|
837
|
-
* AUTO-GENERATED from apps/atomix-lab/src/config/component-defaults.ts
|
|
838
|
-
* DO NOT EDIT MANUALLY - run \`npm run sync\` to regenerate.
|
|
839
|
-
*
|
|
840
|
-
* Maps each component to its design tokens.
|
|
841
|
-
* This allows AI tools to understand which tokens apply to which components.
|
|
842
|
-
*
|
|
843
|
-
* Last synced: ${timestamp}
|
|
844
|
-
* Tenant: ${tenantId}
|
|
845
|
-
*/
|
|
846
|
-
|
|
847
|
-
export interface ComponentTokenConfig {
|
|
848
|
-
description: string;
|
|
849
|
-
variants?: Record<string, VariantTokens>;
|
|
850
|
-
sizes?: Record<string, SizeTokens>;
|
|
851
|
-
shared?: Record<string, string>;
|
|
852
|
-
cssVariables?: string[];
|
|
853
|
-
tailwindClasses?: string[];
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
export interface VariantTokens {
|
|
857
|
-
description?: string;
|
|
858
|
-
tokens: Record<string, string>;
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
export interface SizeTokens {
|
|
862
|
-
description?: string;
|
|
863
|
-
tokens: Record<string, string>;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
// Multi-tenant ready token store
|
|
867
|
-
export interface MCPTokenStore {
|
|
868
|
-
version: string;
|
|
869
|
-
tenantId: string;
|
|
870
|
-
generatedAt: string;
|
|
871
|
-
components: Record<string, ComponentTokenConfig>;
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
export const COMPONENT_TOKENS: Record<string, ComponentTokenConfig> = ${JSON.stringify(components, null, 2)};
|
|
875
|
-
|
|
876
|
-
// Multi-tenant wrapper (backward compatible)
|
|
877
|
-
export const TOKEN_STORE: MCPTokenStore = {
|
|
878
|
-
version: "1.0.0",
|
|
879
|
-
tenantId: "${tenantId}",
|
|
880
|
-
generatedAt: "${timestamp}",
|
|
881
|
-
components: COMPONENT_TOKENS,
|
|
882
|
-
};
|
|
883
|
-
`;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
// ============================================
|
|
887
|
-
// MAIN
|
|
888
|
-
// ============================================
|
|
889
|
-
|
|
890
|
-
async function main() {
|
|
891
|
-
console.log("Syncing component tokens (dynamic parser)...");
|
|
892
|
-
console.log(` Tenant: ${CONFIG.tenantId}`);
|
|
893
|
-
|
|
894
|
-
// Future: If cloud API is configured, fetch from there
|
|
895
|
-
if (CONFIG.cloudApiUrl) {
|
|
896
|
-
console.log(` Cloud API: ${CONFIG.cloudApiUrl}`);
|
|
897
|
-
// TODO: Implement cloud sync
|
|
898
|
-
// const tenantData = await fetchFromCloud(CONFIG.tenantId);
|
|
899
|
-
// syncFromCloudData(tenantData);
|
|
900
|
-
// return;
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
// Check if source file exists
|
|
904
|
-
if (!fs.existsSync(LAB_DEFAULTS_PATH)) {
|
|
905
|
-
console.error(`Error: Could not find ${LAB_DEFAULTS_PATH}`);
|
|
906
|
-
process.exit(1);
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
// Load previous snapshot for breaking change detection
|
|
910
|
-
const previousSnapshot = loadPreviousSnapshot();
|
|
911
|
-
|
|
912
|
-
// Load token map
|
|
913
|
-
loadTokenMap();
|
|
914
|
-
|
|
915
|
-
// Read and parse component defaults
|
|
916
|
-
const content = fs.readFileSync(LAB_DEFAULTS_PATH, "utf-8");
|
|
917
|
-
|
|
918
|
-
// Discover components dynamically
|
|
919
|
-
const components = discoverComponents(content);
|
|
920
|
-
|
|
921
|
-
// Parse manifest for metadata
|
|
922
|
-
const manifest = parseManifest(content);
|
|
923
|
-
|
|
924
|
-
// Generate MCP tokens from shared defaults
|
|
925
|
-
let mcpTokens = generateMCPTokens(components, manifest, content);
|
|
926
|
-
|
|
927
|
-
console.log(` Generated tokens for: ${Object.keys(mcpTokens).join(", ")}`);
|
|
928
|
-
|
|
929
|
-
// Load and merge tenant overrides
|
|
930
|
-
const tenantOverrides = loadTenantOverrides(CONFIG.tenantId);
|
|
931
|
-
mcpTokens = mergeTenantOverrides(mcpTokens, tenantOverrides);
|
|
932
|
-
|
|
933
|
-
// Detect breaking changes
|
|
934
|
-
const oldTokens = previousSnapshot?.components || null;
|
|
935
|
-
const changes = detectBreakingChanges(oldTokens, mcpTokens);
|
|
936
|
-
const hasBreaking = reportBreakingChanges(changes);
|
|
937
|
-
|
|
938
|
-
// Validate token references
|
|
939
|
-
const { errors, warnings } = validateTokenReferences(mcpTokens, tokenMap);
|
|
940
|
-
|
|
941
|
-
if (warnings.length > 0) {
|
|
942
|
-
console.log("\n Warnings:");
|
|
943
|
-
warnings.forEach(w => console.log(` - ${w}`));
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
if (errors.length > 0) {
|
|
947
|
-
console.log("\n Errors:");
|
|
948
|
-
errors.forEach(e => console.log(` - ${e}`));
|
|
949
|
-
process.exit(1);
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
// Generate output file
|
|
953
|
-
const output = generateOutputFile(mcpTokens, manifest, CONFIG.tenantId);
|
|
954
|
-
|
|
955
|
-
// Write output
|
|
956
|
-
fs.writeFileSync(MCP_TOKENS_PATH, output, "utf-8");
|
|
957
|
-
console.log(` ✓ Written to ${MCP_TOKENS_PATH}`);
|
|
958
|
-
|
|
959
|
-
// Save snapshot for future comparison
|
|
960
|
-
saveSnapshot(mcpTokens);
|
|
961
|
-
|
|
962
|
-
if (hasBreaking) {
|
|
963
|
-
console.log("\n\x1b[33m Note: Breaking changes will affect existing code.\x1b[0m");
|
|
964
|
-
console.log(" Run your test suite to identify affected components.\n");
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
console.log("\nTo apply changes, rebuild the MCP server:");
|
|
968
|
-
console.log(" cd packages/atomix-mcp && npm run build");
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
main().catch((error) => {
|
|
972
|
-
console.error("Error syncing component tokens:", error);
|
|
973
|
-
process.exit(1);
|
|
974
|
-
});
|