sonance-brand-mcp 1.3.1 → 1.3.3

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.
Files changed (136) hide show
  1. package/dist/assets/api/sonance-analyze/route.ts +1116 -0
  2. package/dist/assets/api/sonance-assets/route.ts +113 -0
  3. package/dist/assets/api/sonance-components/route.ts +41 -0
  4. package/dist/assets/api/sonance-inject-id/route.ts +363 -0
  5. package/dist/assets/api/sonance-save-logo/route.ts +426 -0
  6. package/dist/assets/api/sonance-theme/route.ts +106 -0
  7. package/dist/assets/brand-system.ts +1265 -0
  8. package/dist/assets/components/accordion.stories.tsx +26 -26
  9. package/dist/assets/components/accordion.tsx +3 -3
  10. package/dist/assets/components/alert-dialog.stories.tsx +7 -7
  11. package/dist/assets/components/alert-dialog.tsx +2 -1
  12. package/dist/assets/components/alert.stories.tsx +3 -3
  13. package/dist/assets/components/alert.tsx +4 -3
  14. package/dist/assets/components/aspect-ratio.stories.tsx +4 -1
  15. package/dist/assets/components/autocomplete.stories.tsx +9 -9
  16. package/dist/assets/components/autocomplete.tsx +3 -3
  17. package/dist/assets/components/avatar.stories.tsx +5 -5
  18. package/dist/assets/components/avatar.tsx +4 -4
  19. package/dist/assets/components/badge.stories.tsx +10 -10
  20. package/dist/assets/components/badge.tsx +3 -3
  21. package/dist/assets/components/breadcrumbs.stories.tsx +7 -7
  22. package/dist/assets/components/breadcrumbs.tsx +13 -8
  23. package/dist/assets/components/button.stories.tsx +74 -74
  24. package/dist/assets/components/button.tsx +2 -0
  25. package/dist/assets/components/calendar.stories.tsx +11 -11
  26. package/dist/assets/components/calendar.tsx +4 -4
  27. package/dist/assets/components/card.stories.tsx +22 -22
  28. package/dist/assets/components/card.tsx +7 -3
  29. package/dist/assets/components/carousel.stories.tsx +6 -6
  30. package/dist/assets/components/carousel.tsx +10 -8
  31. package/dist/assets/components/chart.tsx +5 -5
  32. package/dist/assets/components/checkbox-group.stories.tsx +6 -6
  33. package/dist/assets/components/checkbox-group.tsx +3 -3
  34. package/dist/assets/components/checkbox.stories.tsx +23 -20
  35. package/dist/assets/components/checkbox.tsx +13 -16
  36. package/dist/assets/components/code.stories.tsx +24 -24
  37. package/dist/assets/components/code.tsx +7 -14
  38. package/dist/assets/components/collapsible.stories.tsx +3 -3
  39. package/dist/assets/components/command.stories.tsx +14 -14
  40. package/dist/assets/components/command.tsx +4 -3
  41. package/dist/assets/components/context-menu.stories.tsx +1 -1
  42. package/dist/assets/components/context-menu.tsx +3 -7
  43. package/dist/assets/components/date-input.stories.tsx +9 -9
  44. package/dist/assets/components/date-input.tsx +2 -2
  45. package/dist/assets/components/date-picker.stories.tsx +9 -9
  46. package/dist/assets/components/date-picker.tsx +3 -3
  47. package/dist/assets/components/date-range-picker.stories.tsx +12 -12
  48. package/dist/assets/components/date-range-picker.tsx +3 -3
  49. package/dist/assets/components/dialog.stories.tsx +40 -40
  50. package/dist/assets/components/dialog.tsx +8 -12
  51. package/dist/assets/components/divider.stories.tsx +30 -30
  52. package/dist/assets/components/divider.tsx +4 -8
  53. package/dist/assets/components/drawer.stories.tsx +32 -31
  54. package/dist/assets/components/drawer.tsx +7 -6
  55. package/dist/assets/components/dropdown-menu.tsx +3 -7
  56. package/dist/assets/components/dropdown.stories.tsx +12 -12
  57. package/dist/assets/components/dropdown.tsx +5 -5
  58. package/dist/assets/components/form.stories.tsx +30 -29
  59. package/dist/assets/components/form.tsx +5 -5
  60. package/dist/assets/components/hover-card.stories.tsx +12 -10
  61. package/dist/assets/components/hover-card.tsx +1 -1
  62. package/dist/assets/components/image.stories.tsx +48 -25
  63. package/dist/assets/components/image.tsx +8 -5
  64. package/dist/assets/components/input-otp.stories.tsx +15 -15
  65. package/dist/assets/components/input-otp.tsx +5 -5
  66. package/dist/assets/components/input.stories.tsx +30 -25
  67. package/dist/assets/components/input.tsx +7 -4
  68. package/dist/assets/components/kbd.stories.tsx +34 -34
  69. package/dist/assets/components/kbd.tsx +5 -5
  70. package/dist/assets/components/link.stories.tsx +36 -36
  71. package/dist/assets/components/link.tsx +4 -0
  72. package/dist/assets/components/listbox.stories.tsx +5 -5
  73. package/dist/assets/components/listbox.tsx +4 -4
  74. package/dist/assets/components/menubar.tsx +3 -7
  75. package/dist/assets/components/navbar.stories.tsx +24 -24
  76. package/dist/assets/components/navbar.tsx +8 -14
  77. package/dist/assets/components/navigation-menu.stories.tsx +11 -9
  78. package/dist/assets/components/navigation-menu.tsx +1 -1
  79. package/dist/assets/components/number-input.stories.tsx +11 -11
  80. package/dist/assets/components/number-input.tsx +3 -3
  81. package/dist/assets/components/pagination.stories.tsx +13 -13
  82. package/dist/assets/components/pagination.tsx +6 -6
  83. package/dist/assets/components/popover.stories.tsx +35 -35
  84. package/dist/assets/components/popover.tsx +98 -15
  85. package/dist/assets/components/progress.stories.tsx +5 -5
  86. package/dist/assets/components/progress.tsx +5 -5
  87. package/dist/assets/components/radio-group.stories.tsx +7 -7
  88. package/dist/assets/components/radio-group.tsx +3 -3
  89. package/dist/assets/components/range-calendar.stories.tsx +18 -18
  90. package/dist/assets/components/range-calendar.tsx +3 -3
  91. package/dist/assets/components/resizable.stories.tsx +23 -23
  92. package/dist/assets/components/resizable.tsx +1 -1
  93. package/dist/assets/components/scroll-area.stories.tsx +15 -15
  94. package/dist/assets/components/scroll-area.tsx +1 -1
  95. package/dist/assets/components/scroll-shadow.stories.tsx +17 -17
  96. package/dist/assets/components/scroll-shadow.tsx +2 -2
  97. package/dist/assets/components/select.stories.tsx +20 -19
  98. package/dist/assets/components/select.tsx +10 -6
  99. package/dist/assets/components/separator.tsx +1 -1
  100. package/dist/assets/components/sheet.tsx +3 -7
  101. package/dist/assets/components/sidebar.stories.tsx +30 -30
  102. package/dist/assets/components/sidebar.tsx +24 -27
  103. package/dist/assets/components/skeleton.stories.tsx +3 -3
  104. package/dist/assets/components/skeleton.tsx +2 -2
  105. package/dist/assets/components/slider.stories.tsx +6 -6
  106. package/dist/assets/components/slider.tsx +3 -3
  107. package/dist/assets/components/spacer.stories.tsx +11 -11
  108. package/dist/assets/components/spacer.tsx +2 -2
  109. package/dist/assets/components/spinner.stories.tsx +8 -8
  110. package/dist/assets/components/spinner.tsx +5 -5
  111. package/dist/assets/components/switch.stories.tsx +24 -20
  112. package/dist/assets/components/switch.tsx +14 -6
  113. package/dist/assets/components/table.stories.tsx +7 -7
  114. package/dist/assets/components/table.tsx +8 -8
  115. package/dist/assets/components/tabs.stories.tsx +37 -37
  116. package/dist/assets/components/tabs.tsx +3 -3
  117. package/dist/assets/components/textarea.stories.tsx +13 -12
  118. package/dist/assets/components/textarea.tsx +3 -3
  119. package/dist/assets/components/theme-toggle.stories.tsx +31 -30
  120. package/dist/assets/components/theme-toggle.tsx +2 -2
  121. package/dist/assets/components/time-input.stories.tsx +16 -16
  122. package/dist/assets/components/time-input.tsx +2 -2
  123. package/dist/assets/components/toast.stories.tsx +8 -5
  124. package/dist/assets/components/toast.tsx +6 -6
  125. package/dist/assets/components/toggle-group.tsx +1 -1
  126. package/dist/assets/components/toggle.tsx +1 -1
  127. package/dist/assets/components/tooltip.stories.tsx +49 -27
  128. package/dist/assets/components/tooltip.tsx +1 -1
  129. package/dist/assets/components/user.stories.tsx +23 -23
  130. package/dist/assets/components/user.tsx +7 -4
  131. package/dist/assets/dev-tools/SonanceDevTools.tsx +4201 -0
  132. package/dist/assets/dev-tools/index.ts +10 -0
  133. package/dist/assets/globals.css +9 -0
  134. package/dist/assets/styles/brand-overrides.css +37 -0
  135. package/dist/index.js +1882 -7
  136. package/package.json +1 -1
@@ -0,0 +1,426 @@
1
+ import { NextResponse } from "next/server";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+
5
+ /**
6
+ * Sonance DevTools API - Save Logo Configuration
7
+ *
8
+ * This endpoint is DEVELOPMENT ONLY.
9
+ * It updates the brandLogos configuration in brand-system.ts
10
+ * to persist logo changes across page reloads.
11
+ */
12
+
13
+ interface SaveLogoRequest {
14
+ srcLight?: string;
15
+ srcDark?: string;
16
+ width?: number;
17
+ height?: number;
18
+ scale?: number;
19
+ brandId?: string; // Optional - defaults to 'sonance'
20
+ selector?: string; // Optional CSS selector for targeting specific elements (e.g., "#my-logo")
21
+ reset?: boolean; // If true, removes overrides instead of setting them
22
+ }
23
+
24
+ interface LogoDimensions {
25
+ width?: number;
26
+ height?: number;
27
+ scale?: number;
28
+ }
29
+
30
+ interface SelectorOverride {
31
+ selector: string;
32
+ scale?: number;
33
+ width?: number;
34
+ height?: number;
35
+ }
36
+
37
+ /**
38
+ * Updates the brand-overrides.css file with logo sizing CSS variables.
39
+ * This ensures saved sizes are baked into production builds.
40
+ *
41
+ * Supports two modes:
42
+ * 1. Global brand defaults: Updates :root CSS variables
43
+ * 2. Selector-specific: Appends rules for specific elements (e.g., #my-logo)
44
+ */
45
+ function updateBrandOverridesCss(
46
+ cssPath: string,
47
+ brandId: string,
48
+ dimensions: LogoDimensions,
49
+ selector?: string,
50
+ reset?: boolean
51
+ ): void {
52
+ const validBrands = ["sonance", "iport", "blaze"];
53
+
54
+ // Read existing CSS or create default structure
55
+ let existingCssContent = "";
56
+ const brandSizes: Record<string, LogoDimensions> = {};
57
+ const selectorOverrides: SelectorOverride[] = [];
58
+
59
+ if (fs.existsSync(cssPath)) {
60
+ existingCssContent = fs.readFileSync(cssPath, "utf-8");
61
+
62
+ // Parse existing brand values from :root
63
+ for (const brand of validBrands) {
64
+ const scaleMatch = existingCssContent.match(new RegExp(`--${brand}-logo-scale:\\s*([^;]+);`));
65
+ const widthMatch = existingCssContent.match(new RegExp(`--${brand}-logo-width:\\s*([^;]+);`));
66
+ const heightMatch = existingCssContent.match(new RegExp(`--${brand}-logo-height:\\s*([^;]+);`));
67
+
68
+ brandSizes[brand] = {
69
+ scale: scaleMatch && scaleMatch[1] !== "1" ? parseFloat(scaleMatch[1]) : undefined,
70
+ width: widthMatch && widthMatch[1] !== "auto" ? parseInt(widthMatch[1]) : undefined,
71
+ height: heightMatch && heightMatch[1] !== "auto" ? parseInt(heightMatch[1]) : undefined,
72
+ };
73
+ }
74
+
75
+ // Parse existing selector-specific rules
76
+ // Match patterns like: #my-logo { --sonance-logo-scale: 0.5; }
77
+ const selectorPattern = /\/\* Selector: ([^\s]+) \*\/\s*\n([^\{]+)\s*\{([^}]+)\}/g;
78
+ let selectorMatch;
79
+ while ((selectorMatch = selectorPattern.exec(existingCssContent)) !== null) {
80
+ const sel = selectorMatch[1];
81
+ const content = selectorMatch[3];
82
+ const scaleMatch = content.match(/transform:\s*scale\(([^)]+)\)/);
83
+ const widthMatch = content.match(/width:\s*(\d+)px/);
84
+ const heightMatch = content.match(/height:\s*(\d+)px/);
85
+
86
+ selectorOverrides.push({
87
+ selector: sel,
88
+ scale: scaleMatch ? parseFloat(scaleMatch[1]) : undefined,
89
+ width: widthMatch ? parseInt(widthMatch[1]) : undefined,
90
+ height: heightMatch ? parseInt(heightMatch[1]) : undefined,
91
+ });
92
+ }
93
+ }
94
+
95
+ // If this is a selector-specific override, add/update it
96
+ if (selector) {
97
+ if (reset) {
98
+ // Remove the selector override
99
+ const existingIndex = selectorOverrides.findIndex(s => s.selector === selector);
100
+ if (existingIndex >= 0) {
101
+ selectorOverrides.splice(existingIndex, 1);
102
+ }
103
+ } else {
104
+ const existingIndex = selectorOverrides.findIndex(s => s.selector === selector);
105
+ const newOverride: SelectorOverride = {
106
+ selector,
107
+ ...(dimensions.scale !== undefined && dimensions.scale !== 1 && { scale: dimensions.scale }),
108
+ ...(dimensions.width !== undefined && { width: dimensions.width }),
109
+ ...(dimensions.height !== undefined && { height: dimensions.height }),
110
+ };
111
+
112
+ if (existingIndex >= 0) {
113
+ selectorOverrides[existingIndex] = { ...selectorOverrides[existingIndex], ...newOverride };
114
+ } else {
115
+ selectorOverrides.push(newOverride);
116
+ }
117
+ }
118
+ } else {
119
+ // Update global brand defaults
120
+ if (reset) {
121
+ // Reset to defaults
122
+ brandSizes[brandId] = {
123
+ scale: 1,
124
+ width: undefined, // auto
125
+ height: undefined // auto
126
+ };
127
+ } else {
128
+ brandSizes[brandId] = {
129
+ ...brandSizes[brandId],
130
+ ...(dimensions.scale !== undefined && dimensions.scale !== 1 && { scale: dimensions.scale }),
131
+ ...(dimensions.width !== undefined && { width: dimensions.width }),
132
+ ...(dimensions.height !== undefined && { height: dimensions.height }),
133
+ };
134
+ }
135
+ }
136
+
137
+ // Generate new CSS content
138
+ let newCssContent = `/* ==========================================================================
139
+ Sonance Brand Overrides - Auto-generated by Sonance DevTools
140
+ DO NOT EDIT MANUALLY - Changes will be overwritten by DevTools saves
141
+ ========================================================================== */
142
+
143
+ :root {
144
+ /* Sonance Logo Sizing */
145
+ --sonance-logo-scale: ${brandSizes.sonance?.scale || 1};
146
+ --sonance-logo-width: ${brandSizes.sonance?.width ? brandSizes.sonance.width + "px" : "auto"};
147
+ --sonance-logo-height: ${brandSizes.sonance?.height ? brandSizes.sonance.height + "px" : "auto"};
148
+
149
+ /* IPORT Logo Sizing */
150
+ --iport-logo-scale: ${brandSizes.iport?.scale || 1};
151
+ --iport-logo-width: ${brandSizes.iport?.width ? brandSizes.iport.width + "px" : "auto"};
152
+ --iport-logo-height: ${brandSizes.iport?.height ? brandSizes.iport.height + "px" : "auto"};
153
+
154
+ /* Blaze Audio Logo Sizing */
155
+ --blaze-logo-scale: ${brandSizes.blaze?.scale || 1};
156
+ --blaze-logo-width: ${brandSizes.blaze?.width ? brandSizes.blaze.width + "px" : "auto"};
157
+ --blaze-logo-height: ${brandSizes.blaze?.height ? brandSizes.blaze.height + "px" : "auto"};
158
+ }
159
+ `;
160
+
161
+ // Append selector-specific overrides
162
+ if (selectorOverrides.length > 0) {
163
+ newCssContent += `
164
+ /* ==========================================================================
165
+ Element-Specific Overrides
166
+ ========================================================================== */
167
+ `;
168
+
169
+ for (const override of selectorOverrides) {
170
+ const styles: string[] = [];
171
+ if (override.scale !== undefined && override.scale !== 1) {
172
+ styles.push(` transform: scale(${override.scale})`);
173
+ styles.push(` transform-origin: center`);
174
+ }
175
+ if (override.width !== undefined) {
176
+ styles.push(` width: ${override.width}px`);
177
+ }
178
+ if (override.height !== undefined) {
179
+ styles.push(` height: ${override.height}px`);
180
+ }
181
+
182
+ if (styles.length > 0) {
183
+ newCssContent += `
184
+ /* Selector: ${override.selector} */
185
+ ${override.selector} {
186
+ ${styles.join(";\n")};
187
+ }
188
+ `;
189
+ }
190
+ }
191
+ }
192
+
193
+ // Ensure directory exists
194
+ const cssDir = path.dirname(cssPath);
195
+ if (!fs.existsSync(cssDir)) {
196
+ fs.mkdirSync(cssDir, { recursive: true });
197
+ }
198
+
199
+ fs.writeFileSync(cssPath, newCssContent, "utf-8");
200
+ }
201
+
202
+ export async function POST(request: Request) {
203
+ // Security: Only allow in development
204
+ if (process.env.NODE_ENV !== "development") {
205
+ return NextResponse.json(
206
+ { error: "This endpoint is only available in development mode." },
207
+ { status: 403 }
208
+ );
209
+ }
210
+
211
+ try {
212
+ const body: SaveLogoRequest = await request.json();
213
+ const { srcLight, srcDark, width, height, scale, brandId = "sonance", selector, reset } = body;
214
+
215
+ // Check if there's anything to save
216
+ const hasSrcChanges = srcLight || srcDark;
217
+ const hasDimensionChanges = width !== undefined || height !== undefined || (scale !== undefined && scale !== 1);
218
+
219
+ if (!reset && !hasSrcChanges && !hasDimensionChanges) {
220
+ return NextResponse.json(
221
+ { error: "No changes to save. Provide srcLight, srcDark, or dimension properties." },
222
+ { status: 400 }
223
+ );
224
+ }
225
+
226
+ // Validate brand ID
227
+ const validBrands = ["sonance", "iport", "blaze"];
228
+ if (!validBrands.includes(brandId)) {
229
+ return NextResponse.json(
230
+ { error: `Invalid brandId. Must be one of: ${validBrands.join(", ")}` },
231
+ { status: 400 }
232
+ );
233
+ }
234
+
235
+ // Resolve path to brand-system.ts
236
+ const projectRoot = process.cwd();
237
+ const brandSystemPath = path.join(projectRoot, "src", "lib", "brand-system.ts");
238
+
239
+ if (!fs.existsSync(brandSystemPath)) {
240
+ return NextResponse.json(
241
+ { error: "brand-system.ts not found" },
242
+ { status: 404 }
243
+ );
244
+ }
245
+
246
+ // Read the current file
247
+ let fileContent = fs.readFileSync(brandSystemPath, "utf-8");
248
+
249
+ // Track what was updated
250
+ const updated: Record<string, unknown> = { brandId };
251
+
252
+ // Handle source path changes (update brandLogos object)
253
+ if (hasSrcChanges && srcLight && srcDark) {
254
+ // Create regex patterns to find and replace the logo configuration
255
+ // Pattern matches: brandId: { light: '...', dark: '...', alt: '...' }
256
+ const brandLogoPattern = new RegExp(
257
+ `(${brandId}:\\s*\\{\\s*)(light:\\s*['"][^'"]*['"],?\\s*)(dark:\\s*['"][^'"]*['"],?\\s*)(alt:\\s*['"][^'"]*['"])`,
258
+ "s"
259
+ );
260
+
261
+ const match = fileContent.match(brandLogoPattern);
262
+
263
+ if (!match) {
264
+ return NextResponse.json(
265
+ { error: `Could not find ${brandId} logo configuration in brand-system.ts` },
266
+ { status: 400 }
267
+ );
268
+ }
269
+
270
+ // Replace the logo paths
271
+ const newLogoConfig = `$1light: '${srcLight}',\n dark: '${srcDark}',\n $4`;
272
+ fileContent = fileContent.replace(brandLogoPattern, newLogoConfig);
273
+
274
+ updated.light = srcLight;
275
+ updated.dark = srcDark;
276
+ }
277
+
278
+ // Handle dimension changes (store in a separate config section or CSS variables)
279
+ // For now, we'll store dimensions in a logoSizes object in brand-system.ts
280
+ // Note: brand-system.ts (TypeScript) currently only supports global overrides, not selectors
281
+ // So we only update it if no selector is provided
282
+ if (!selector && (hasDimensionChanges || reset)) {
283
+ const sizeConfig = {
284
+ ...(width !== undefined && { width }),
285
+ ...(height !== undefined && { height }),
286
+ ...(scale !== undefined && scale !== 1 && { scale }),
287
+ };
288
+
289
+ // More robust pattern: match the entire logoSizes declaration including nested braces
290
+ // Pattern: export const logoSizes: Record<...> = { content };
291
+ const logoSizesPattern = /export const logoSizes:\s*Record<string,\s*\{[^}]+\}>\s*=\s*\{([\s\S]*?)\};/;
292
+ const sizesMatch = fileContent.match(logoSizesPattern);
293
+
294
+ if (sizesMatch) {
295
+ // Update existing logoSizes
296
+ const existingSizes = sizesMatch[1];
297
+ const brandSizePattern = new RegExp(`${brandId}:\\s*\\{[^}]*\\}`, "g");
298
+
299
+ if (reset) {
300
+ // Remove brand size if reset
301
+ let newSizesContent = existingSizes;
302
+ if (brandSizePattern.test(existingSizes)) {
303
+ newSizesContent = existingSizes.replace(brandSizePattern, "");
304
+ // Clean up trailing commas/newlines
305
+ newSizesContent = newSizesContent.replace(/,\s*,/g, ",").replace(/,\s*$/, "");
306
+ }
307
+ const newLogoSizesBlock = `export const logoSizes: Record<string, { width?: number; height?: number; scale?: number }> = {${newSizesContent}\n};`;
308
+ fileContent = fileContent.replace(logoSizesPattern, newLogoSizesBlock);
309
+ } else {
310
+ // Update/Add brand size
311
+ const newBrandSize = `${brandId}: ${JSON.stringify(sizeConfig)}`;
312
+
313
+ let newSizesContent: string;
314
+ if (brandSizePattern.test(existingSizes)) {
315
+ // Replace existing brand size
316
+ newSizesContent = existingSizes.replace(
317
+ new RegExp(`${brandId}:\\s*\\{[^}]*\\}`, "g"),
318
+ newBrandSize
319
+ );
320
+ } else {
321
+ // Add new brand size
322
+ const trimmedExisting = existingSizes.trim();
323
+ newSizesContent = trimmedExisting
324
+ ? `${trimmedExisting.replace(/,?\s*$/, "")},\n ${newBrandSize}`
325
+ : `\n ${newBrandSize}`;
326
+ }
327
+
328
+ const newLogoSizesBlock = `export const logoSizes: Record<string, { width?: number; height?: number; scale?: number }> = {${newSizesContent}\n};`;
329
+ fileContent = fileContent.replace(logoSizesPattern, newLogoSizesBlock);
330
+ }
331
+ } else if (!reset) {
332
+ // Create new logoSizes object (doesn't exist yet)
333
+ const newLogoSizesBlock = `\n// Logo dimension overrides\nexport const logoSizes: Record<string, { width?: number; height?: number; scale?: number }> = {\n ${brandId}: ${JSON.stringify(sizeConfig)}\n};\n`;
334
+
335
+ // Add after brandLogos definition
336
+ const brandLogosEndPattern = /export const brandLogos[^;]+;/s;
337
+ const brandLogosMatch = fileContent.match(brandLogosEndPattern);
338
+ if (brandLogosMatch) {
339
+ fileContent = fileContent.replace(brandLogosEndPattern, `${brandLogosMatch[0]}${newLogoSizesBlock}`);
340
+ } else {
341
+ // Fallback: append to end of file
342
+ fileContent += newLogoSizesBlock;
343
+ }
344
+ }
345
+
346
+ if (!reset) {
347
+ updated.dimensions = sizeConfig;
348
+ }
349
+ }
350
+
351
+ // Write the updated brand-system.ts file
352
+ fs.writeFileSync(brandSystemPath, fileContent, "utf-8");
353
+
354
+ // Also update the CSS overrides file for production builds
355
+ const brandOverridesPath = path.join(projectRoot, "src", "styles", "brand-overrides.css");
356
+ updateBrandOverridesCss(brandOverridesPath, brandId, { width, height, scale }, selector, reset);
357
+
358
+ const targetDescription = selector || brandId;
359
+ return NextResponse.json({
360
+ success: true,
361
+ message: `Logo configuration for ${targetDescription} saved successfully`,
362
+ updated: { ...updated, selector, reset },
363
+ });
364
+ } catch (error) {
365
+ console.error("Error saving logo configuration:", error);
366
+ return NextResponse.json(
367
+ { error: "Failed to save logo configuration", details: String(error) },
368
+ { status: 500 }
369
+ );
370
+ }
371
+ }
372
+
373
+ export async function GET() {
374
+ // Security: Only allow in development
375
+ if (process.env.NODE_ENV !== "development") {
376
+ return NextResponse.json(
377
+ { error: "This endpoint is only available in development mode." },
378
+ { status: 403 }
379
+ );
380
+ }
381
+
382
+ try {
383
+ const projectRoot = process.cwd();
384
+ const brandSystemPath = path.join(projectRoot, "src", "lib", "brand-system.ts");
385
+
386
+ if (!fs.existsSync(brandSystemPath)) {
387
+ return NextResponse.json({ exists: false });
388
+ }
389
+
390
+ // Read and parse the brandLogos from the file
391
+ const fileContent = fs.readFileSync(brandSystemPath, "utf-8");
392
+
393
+ // Extract brandLogos object using regex
394
+ const brandLogosMatch = fileContent.match(/export const brandLogos[^{]*{([^}]+(?:{[^}]*}[^}]*)*)}/s);
395
+
396
+ if (!brandLogosMatch) {
397
+ return NextResponse.json({ exists: true, parsed: false });
398
+ }
399
+
400
+ // Parse individual brand configurations
401
+ const brands: Record<string, { light: string; dark: string; alt: string }> = {};
402
+ const brandPattern = /(\w+):\s*{\s*light:\s*['"]([^'"]+)['"],?\s*dark:\s*['"]([^'"]+)['"],?\s*alt:\s*['"]([^'"]+)['"]/g;
403
+
404
+ let match;
405
+ while ((match = brandPattern.exec(brandLogosMatch[0])) !== null) {
406
+ brands[match[1]] = {
407
+ light: match[2],
408
+ dark: match[3],
409
+ alt: match[4],
410
+ };
411
+ }
412
+
413
+ return NextResponse.json({
414
+ exists: true,
415
+ parsed: true,
416
+ brands,
417
+ });
418
+ } catch (error) {
419
+ console.error("Error reading logo configuration:", error);
420
+ return NextResponse.json(
421
+ { error: "Failed to read logo configuration", details: String(error) },
422
+ { status: 500 }
423
+ );
424
+ }
425
+ }
426
+
@@ -0,0 +1,106 @@
1
+ import { NextResponse } from "next/server";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+
5
+ /**
6
+ * Sonance DevTools API - Save Theme
7
+ *
8
+ * This endpoint is DEVELOPMENT ONLY.
9
+ * It writes theme configuration to disk so changes persist.
10
+ */
11
+
12
+ export async function POST(request: Request) {
13
+ // Security: Only allow in development
14
+ if (process.env.NODE_ENV !== "development") {
15
+ return NextResponse.json(
16
+ { error: "This endpoint is only available in development mode." },
17
+ { status: 403 }
18
+ );
19
+ }
20
+
21
+ try {
22
+ const body = await request.json();
23
+ const { css, config } = body;
24
+
25
+ if (!css || !config) {
26
+ return NextResponse.json(
27
+ { error: "Missing required fields: css and config" },
28
+ { status: 400 }
29
+ );
30
+ }
31
+
32
+ // Resolve paths relative to project root
33
+ const projectRoot = process.cwd();
34
+ const themeDir = path.join(projectRoot, "src", "theme");
35
+
36
+ // Ensure theme directory exists
37
+ if (!fs.existsSync(themeDir)) {
38
+ fs.mkdirSync(themeDir, { recursive: true });
39
+ }
40
+
41
+ // Write CSS file
42
+ const cssPath = path.join(themeDir, "sonance-theme.css");
43
+ const cssContent = `/**
44
+ * Sonance Theme - Auto-generated by DevTools
45
+ * Do not edit manually - use the DevTools plugin to make changes.
46
+ *
47
+ * Generated: ${new Date().toISOString()}
48
+ */
49
+
50
+ :root {
51
+ ${css}
52
+ }
53
+ `;
54
+ fs.writeFileSync(cssPath, cssContent, "utf-8");
55
+
56
+ // Write JSON config file
57
+ const configPath = path.join(themeDir, "sonance-config.json");
58
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
59
+
60
+ return NextResponse.json({
61
+ success: true,
62
+ message: "Theme saved successfully",
63
+ files: {
64
+ css: "src/theme/sonance-theme.css",
65
+ config: "src/theme/sonance-config.json",
66
+ },
67
+ });
68
+ } catch (error) {
69
+ console.error("Error saving theme:", error);
70
+ return NextResponse.json(
71
+ { error: "Failed to save theme", details: String(error) },
72
+ { status: 500 }
73
+ );
74
+ }
75
+ }
76
+
77
+ export async function GET() {
78
+ // Security: Only allow in development
79
+ if (process.env.NODE_ENV !== "development") {
80
+ return NextResponse.json(
81
+ { error: "This endpoint is only available in development mode." },
82
+ { status: 403 }
83
+ );
84
+ }
85
+
86
+ try {
87
+ const projectRoot = process.cwd();
88
+ const configPath = path.join(projectRoot, "src", "theme", "sonance-config.json");
89
+
90
+ if (!fs.existsSync(configPath)) {
91
+ return NextResponse.json({ config: null, exists: false });
92
+ }
93
+
94
+ const configContent = fs.readFileSync(configPath, "utf-8");
95
+ const config = JSON.parse(configContent);
96
+
97
+ return NextResponse.json({ config, exists: true });
98
+ } catch (error) {
99
+ console.error("Error reading theme:", error);
100
+ return NextResponse.json(
101
+ { error: "Failed to read theme", details: String(error) },
102
+ { status: 500 }
103
+ );
104
+ }
105
+ }
106
+