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.
- package/dist/assets/api/sonance-analyze/route.ts +1116 -0
- package/dist/assets/api/sonance-assets/route.ts +113 -0
- package/dist/assets/api/sonance-components/route.ts +41 -0
- package/dist/assets/api/sonance-inject-id/route.ts +363 -0
- package/dist/assets/api/sonance-save-logo/route.ts +426 -0
- package/dist/assets/api/sonance-theme/route.ts +106 -0
- package/dist/assets/brand-system.ts +1265 -0
- package/dist/assets/components/accordion.stories.tsx +26 -26
- package/dist/assets/components/accordion.tsx +3 -3
- package/dist/assets/components/alert-dialog.stories.tsx +7 -7
- package/dist/assets/components/alert-dialog.tsx +2 -1
- package/dist/assets/components/alert.stories.tsx +3 -3
- package/dist/assets/components/alert.tsx +4 -3
- package/dist/assets/components/aspect-ratio.stories.tsx +4 -1
- package/dist/assets/components/autocomplete.stories.tsx +9 -9
- package/dist/assets/components/autocomplete.tsx +3 -3
- package/dist/assets/components/avatar.stories.tsx +5 -5
- package/dist/assets/components/avatar.tsx +4 -4
- package/dist/assets/components/badge.stories.tsx +10 -10
- package/dist/assets/components/badge.tsx +3 -3
- package/dist/assets/components/breadcrumbs.stories.tsx +7 -7
- package/dist/assets/components/breadcrumbs.tsx +13 -8
- package/dist/assets/components/button.stories.tsx +74 -74
- package/dist/assets/components/button.tsx +2 -0
- package/dist/assets/components/calendar.stories.tsx +11 -11
- package/dist/assets/components/calendar.tsx +4 -4
- package/dist/assets/components/card.stories.tsx +22 -22
- package/dist/assets/components/card.tsx +7 -3
- package/dist/assets/components/carousel.stories.tsx +6 -6
- package/dist/assets/components/carousel.tsx +10 -8
- package/dist/assets/components/chart.tsx +5 -5
- package/dist/assets/components/checkbox-group.stories.tsx +6 -6
- package/dist/assets/components/checkbox-group.tsx +3 -3
- package/dist/assets/components/checkbox.stories.tsx +23 -20
- package/dist/assets/components/checkbox.tsx +13 -16
- package/dist/assets/components/code.stories.tsx +24 -24
- package/dist/assets/components/code.tsx +7 -14
- package/dist/assets/components/collapsible.stories.tsx +3 -3
- package/dist/assets/components/command.stories.tsx +14 -14
- package/dist/assets/components/command.tsx +4 -3
- package/dist/assets/components/context-menu.stories.tsx +1 -1
- package/dist/assets/components/context-menu.tsx +3 -7
- package/dist/assets/components/date-input.stories.tsx +9 -9
- package/dist/assets/components/date-input.tsx +2 -2
- package/dist/assets/components/date-picker.stories.tsx +9 -9
- package/dist/assets/components/date-picker.tsx +3 -3
- package/dist/assets/components/date-range-picker.stories.tsx +12 -12
- package/dist/assets/components/date-range-picker.tsx +3 -3
- package/dist/assets/components/dialog.stories.tsx +40 -40
- package/dist/assets/components/dialog.tsx +8 -12
- package/dist/assets/components/divider.stories.tsx +30 -30
- package/dist/assets/components/divider.tsx +4 -8
- package/dist/assets/components/drawer.stories.tsx +32 -31
- package/dist/assets/components/drawer.tsx +7 -6
- package/dist/assets/components/dropdown-menu.tsx +3 -7
- package/dist/assets/components/dropdown.stories.tsx +12 -12
- package/dist/assets/components/dropdown.tsx +5 -5
- package/dist/assets/components/form.stories.tsx +30 -29
- package/dist/assets/components/form.tsx +5 -5
- package/dist/assets/components/hover-card.stories.tsx +12 -10
- package/dist/assets/components/hover-card.tsx +1 -1
- package/dist/assets/components/image.stories.tsx +48 -25
- package/dist/assets/components/image.tsx +8 -5
- package/dist/assets/components/input-otp.stories.tsx +15 -15
- package/dist/assets/components/input-otp.tsx +5 -5
- package/dist/assets/components/input.stories.tsx +30 -25
- package/dist/assets/components/input.tsx +7 -4
- package/dist/assets/components/kbd.stories.tsx +34 -34
- package/dist/assets/components/kbd.tsx +5 -5
- package/dist/assets/components/link.stories.tsx +36 -36
- package/dist/assets/components/link.tsx +4 -0
- package/dist/assets/components/listbox.stories.tsx +5 -5
- package/dist/assets/components/listbox.tsx +4 -4
- package/dist/assets/components/menubar.tsx +3 -7
- package/dist/assets/components/navbar.stories.tsx +24 -24
- package/dist/assets/components/navbar.tsx +8 -14
- package/dist/assets/components/navigation-menu.stories.tsx +11 -9
- package/dist/assets/components/navigation-menu.tsx +1 -1
- package/dist/assets/components/number-input.stories.tsx +11 -11
- package/dist/assets/components/number-input.tsx +3 -3
- package/dist/assets/components/pagination.stories.tsx +13 -13
- package/dist/assets/components/pagination.tsx +6 -6
- package/dist/assets/components/popover.stories.tsx +35 -35
- package/dist/assets/components/popover.tsx +98 -15
- package/dist/assets/components/progress.stories.tsx +5 -5
- package/dist/assets/components/progress.tsx +5 -5
- package/dist/assets/components/radio-group.stories.tsx +7 -7
- package/dist/assets/components/radio-group.tsx +3 -3
- package/dist/assets/components/range-calendar.stories.tsx +18 -18
- package/dist/assets/components/range-calendar.tsx +3 -3
- package/dist/assets/components/resizable.stories.tsx +23 -23
- package/dist/assets/components/resizable.tsx +1 -1
- package/dist/assets/components/scroll-area.stories.tsx +15 -15
- package/dist/assets/components/scroll-area.tsx +1 -1
- package/dist/assets/components/scroll-shadow.stories.tsx +17 -17
- package/dist/assets/components/scroll-shadow.tsx +2 -2
- package/dist/assets/components/select.stories.tsx +20 -19
- package/dist/assets/components/select.tsx +10 -6
- package/dist/assets/components/separator.tsx +1 -1
- package/dist/assets/components/sheet.tsx +3 -7
- package/dist/assets/components/sidebar.stories.tsx +30 -30
- package/dist/assets/components/sidebar.tsx +24 -27
- package/dist/assets/components/skeleton.stories.tsx +3 -3
- package/dist/assets/components/skeleton.tsx +2 -2
- package/dist/assets/components/slider.stories.tsx +6 -6
- package/dist/assets/components/slider.tsx +3 -3
- package/dist/assets/components/spacer.stories.tsx +11 -11
- package/dist/assets/components/spacer.tsx +2 -2
- package/dist/assets/components/spinner.stories.tsx +8 -8
- package/dist/assets/components/spinner.tsx +5 -5
- package/dist/assets/components/switch.stories.tsx +24 -20
- package/dist/assets/components/switch.tsx +14 -6
- package/dist/assets/components/table.stories.tsx +7 -7
- package/dist/assets/components/table.tsx +8 -8
- package/dist/assets/components/tabs.stories.tsx +37 -37
- package/dist/assets/components/tabs.tsx +3 -3
- package/dist/assets/components/textarea.stories.tsx +13 -12
- package/dist/assets/components/textarea.tsx +3 -3
- package/dist/assets/components/theme-toggle.stories.tsx +31 -30
- package/dist/assets/components/theme-toggle.tsx +2 -2
- package/dist/assets/components/time-input.stories.tsx +16 -16
- package/dist/assets/components/time-input.tsx +2 -2
- package/dist/assets/components/toast.stories.tsx +8 -5
- package/dist/assets/components/toast.tsx +6 -6
- package/dist/assets/components/toggle-group.tsx +1 -1
- package/dist/assets/components/toggle.tsx +1 -1
- package/dist/assets/components/tooltip.stories.tsx +49 -27
- package/dist/assets/components/tooltip.tsx +1 -1
- package/dist/assets/components/user.stories.tsx +23 -23
- package/dist/assets/components/user.tsx +7 -4
- package/dist/assets/dev-tools/SonanceDevTools.tsx +4201 -0
- package/dist/assets/dev-tools/index.ts +10 -0
- package/dist/assets/globals.css +9 -0
- package/dist/assets/styles/brand-overrides.css +37 -0
- package/dist/index.js +1882 -7
- 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
|
+
|