@fragments-sdk/cli 0.7.15 → 0.7.16
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/bin.js +2 -2
- package/dist/init-DIZ6UNBL.js +806 -0
- package/dist/init-DIZ6UNBL.js.map +1 -0
- package/dist/{viewer-7I4WGVU3.js → viewer-QKIAPTPG.js} +67 -4
- package/dist/viewer-QKIAPTPG.js.map +1 -0
- package/package.json +3 -2
- package/src/commands/init-framework.ts +414 -0
- package/src/commands/init.ts +41 -1
- package/src/viewer/components/App.tsx +5 -0
- package/src/viewer/components/HealthDashboard.tsx +1 -1
- package/src/viewer/components/PropsTable.tsx +2 -2
- package/src/viewer/components/RuntimeToolsRegistrar.tsx +17 -0
- package/src/viewer/components/ViewerStateSync.tsx +52 -0
- package/src/viewer/components/WebMCPDevTools.tsx +509 -0
- package/src/viewer/components/WebMCPIntegration.tsx +47 -0
- package/src/viewer/components/WebMCPStatusIndicator.tsx +60 -0
- package/src/viewer/entry.tsx +6 -3
- package/src/viewer/hooks/useA11yService.ts +1 -135
- package/src/viewer/hooks/useCompiledFragments.ts +42 -0
- package/src/viewer/server.ts +58 -3
- package/src/viewer/vite-plugin.ts +18 -0
- package/src/viewer/webmcp/__tests__/analytics.test.ts +108 -0
- package/src/viewer/webmcp/analytics.ts +165 -0
- package/src/viewer/webmcp/index.ts +3 -0
- package/src/viewer/webmcp/posthog-bridge.ts +39 -0
- package/src/viewer/webmcp/runtime-tools.ts +152 -0
- package/src/viewer/webmcp/scan-utils.ts +135 -0
- package/src/viewer/webmcp/use-tool-analytics.ts +69 -0
- package/src/viewer/webmcp/viewer-state.ts +45 -0
- package/dist/init-V42FFMUJ.js +0 -498
- package/dist/init-V42FFMUJ.js.map +0 -1
- package/dist/viewer-7I4WGVU3.js.map +0 -1
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework detection and auto-configuration for fragments init.
|
|
3
|
+
*
|
|
4
|
+
* Detects the consumer's framework (Next.js, Vite, Remix, Astro)
|
|
5
|
+
* and generates appropriate configuration files.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFile, writeFile, access } from "node:fs/promises";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import pc from "picocolors";
|
|
11
|
+
|
|
12
|
+
// ============================================
|
|
13
|
+
// Types
|
|
14
|
+
// ============================================
|
|
15
|
+
|
|
16
|
+
export type Framework = "nextjs" | "vite" | "remix" | "astro" | "unknown";
|
|
17
|
+
|
|
18
|
+
export interface FrameworkDetection {
|
|
19
|
+
framework: Framework;
|
|
20
|
+
/** Package that triggered the detection */
|
|
21
|
+
detectedBy: string | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface FrameworkSetupOptions {
|
|
25
|
+
/** Project root directory */
|
|
26
|
+
projectRoot: string;
|
|
27
|
+
/** Override auto-detected framework */
|
|
28
|
+
framework?: Framework;
|
|
29
|
+
/** Seed overrides for globals.scss generation */
|
|
30
|
+
seeds?: {
|
|
31
|
+
brand?: string;
|
|
32
|
+
neutral?: string;
|
|
33
|
+
density?: string;
|
|
34
|
+
radiusStyle?: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface FrameworkSetupResult {
|
|
39
|
+
framework: Framework;
|
|
40
|
+
filesCreated: string[];
|
|
41
|
+
packagesToInstall: string[];
|
|
42
|
+
configModified: string[];
|
|
43
|
+
warnings: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ============================================
|
|
47
|
+
// Framework Detection
|
|
48
|
+
// ============================================
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Detect framework from package.json dependencies
|
|
52
|
+
*/
|
|
53
|
+
export async function detectFramework(
|
|
54
|
+
projectRoot: string
|
|
55
|
+
): Promise<FrameworkDetection> {
|
|
56
|
+
try {
|
|
57
|
+
const pkgPath = join(projectRoot, "package.json");
|
|
58
|
+
const pkgContent = await readFile(pkgPath, "utf-8");
|
|
59
|
+
const pkg = JSON.parse(pkgContent);
|
|
60
|
+
|
|
61
|
+
const allDeps = {
|
|
62
|
+
...pkg.dependencies,
|
|
63
|
+
...pkg.devDependencies,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Check in order of specificity
|
|
67
|
+
if (allDeps["next"]) {
|
|
68
|
+
return { framework: "nextjs", detectedBy: "next" };
|
|
69
|
+
}
|
|
70
|
+
if (allDeps["@remix-run/react"]) {
|
|
71
|
+
return { framework: "remix", detectedBy: "@remix-run/react" };
|
|
72
|
+
}
|
|
73
|
+
if (allDeps["astro"]) {
|
|
74
|
+
return { framework: "astro", detectedBy: "astro" };
|
|
75
|
+
}
|
|
76
|
+
if (allDeps["vite"]) {
|
|
77
|
+
return { framework: "vite", detectedBy: "vite" };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { framework: "unknown", detectedBy: null };
|
|
81
|
+
} catch {
|
|
82
|
+
return { framework: "unknown", detectedBy: null };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ============================================
|
|
87
|
+
// Globals SCSS Generation
|
|
88
|
+
// ============================================
|
|
89
|
+
|
|
90
|
+
function generateGlobalsSCSS(seeds?: FrameworkSetupOptions["seeds"]): string {
|
|
91
|
+
const withClauses: string[] = [];
|
|
92
|
+
|
|
93
|
+
if (seeds?.brand) {
|
|
94
|
+
withClauses.push(` $fui-brand: ${seeds.brand}`);
|
|
95
|
+
}
|
|
96
|
+
if (seeds?.neutral) {
|
|
97
|
+
withClauses.push(` $fui-neutral: "${seeds.neutral}"`);
|
|
98
|
+
}
|
|
99
|
+
if (seeds?.density) {
|
|
100
|
+
withClauses.push(` $fui-density: "${seeds.density}"`);
|
|
101
|
+
}
|
|
102
|
+
if (seeds?.radiusStyle) {
|
|
103
|
+
withClauses.push(` $fui-radius-style: "${seeds.radiusStyle}"`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const useStatement =
|
|
107
|
+
withClauses.length > 0
|
|
108
|
+
? `@use '@fragments-sdk/ui/styles' with (\n${withClauses.join(",\n")}\n);`
|
|
109
|
+
: `@use '@fragments-sdk/ui/styles';`;
|
|
110
|
+
|
|
111
|
+
return `// Fragments SDK Global Styles
|
|
112
|
+
// Customize seed values to theme the entire design system.
|
|
113
|
+
// See: https://usefragments.com/docs/theming
|
|
114
|
+
${useStatement}
|
|
115
|
+
`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ============================================
|
|
119
|
+
// Providers Component Generation
|
|
120
|
+
// ============================================
|
|
121
|
+
|
|
122
|
+
function generateProviders(): string {
|
|
123
|
+
return `'use client';
|
|
124
|
+
|
|
125
|
+
import { ThemeProvider } from '@fragments-sdk/ui';
|
|
126
|
+
|
|
127
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
128
|
+
return (
|
|
129
|
+
<ThemeProvider defaultTheme="system" attribute="data-theme">
|
|
130
|
+
{children}
|
|
131
|
+
</ThemeProvider>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ============================================
|
|
138
|
+
// Per-Framework Configuration
|
|
139
|
+
// ============================================
|
|
140
|
+
|
|
141
|
+
async function setupNextJS(
|
|
142
|
+
projectRoot: string,
|
|
143
|
+
options: FrameworkSetupOptions
|
|
144
|
+
): Promise<FrameworkSetupResult> {
|
|
145
|
+
const result: FrameworkSetupResult = {
|
|
146
|
+
framework: "nextjs",
|
|
147
|
+
filesCreated: [],
|
|
148
|
+
packagesToInstall: [],
|
|
149
|
+
configModified: [],
|
|
150
|
+
warnings: [],
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Check if sass is installed
|
|
154
|
+
try {
|
|
155
|
+
const pkgContent = await readFile(
|
|
156
|
+
join(projectRoot, "package.json"),
|
|
157
|
+
"utf-8"
|
|
158
|
+
);
|
|
159
|
+
const pkg = JSON.parse(pkgContent);
|
|
160
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
161
|
+
if (!allDeps["sass"]) {
|
|
162
|
+
result.packagesToInstall.push("sass");
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
// Proceed without checking
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Update next.config if transpilePackages is needed
|
|
169
|
+
const nextConfigPaths = [
|
|
170
|
+
"next.config.ts",
|
|
171
|
+
"next.config.mjs",
|
|
172
|
+
"next.config.js",
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
for (const configName of nextConfigPaths) {
|
|
176
|
+
const configPath = join(projectRoot, configName);
|
|
177
|
+
try {
|
|
178
|
+
await access(configPath);
|
|
179
|
+
const content = await readFile(configPath, "utf-8");
|
|
180
|
+
|
|
181
|
+
if (!content.includes("transpilePackages")) {
|
|
182
|
+
// Add transpilePackages to the config
|
|
183
|
+
if (content.includes("const nextConfig")) {
|
|
184
|
+
const updated = content.replace(
|
|
185
|
+
/const nextConfig\s*=\s*\{/,
|
|
186
|
+
`const nextConfig = {\n transpilePackages: ['@fragments-sdk/ui'],`
|
|
187
|
+
);
|
|
188
|
+
await writeFile(configPath, updated, "utf-8");
|
|
189
|
+
result.configModified.push(configName);
|
|
190
|
+
} else {
|
|
191
|
+
result.warnings.push(
|
|
192
|
+
`Could not auto-modify ${configName}. Add transpilePackages: ['@fragments-sdk/ui'] manually.`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
} catch {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Generate globals.scss
|
|
203
|
+
const globalsPath = join(projectRoot, "src", "styles", "globals.scss");
|
|
204
|
+
try {
|
|
205
|
+
await access(globalsPath);
|
|
206
|
+
result.warnings.push(
|
|
207
|
+
"src/styles/globals.scss already exists. Skipped generation."
|
|
208
|
+
);
|
|
209
|
+
} catch {
|
|
210
|
+
await writeFile(globalsPath, generateGlobalsSCSS(options.seeds), "utf-8");
|
|
211
|
+
result.filesCreated.push("src/styles/globals.scss");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Generate providers.tsx
|
|
215
|
+
const providersPath = join(projectRoot, "src", "providers.tsx");
|
|
216
|
+
try {
|
|
217
|
+
await access(providersPath);
|
|
218
|
+
result.warnings.push("src/providers.tsx already exists. Skipped.");
|
|
219
|
+
} catch {
|
|
220
|
+
await writeFile(providersPath, generateProviders(), "utf-8");
|
|
221
|
+
result.filesCreated.push("src/providers.tsx");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function setupVite(
|
|
228
|
+
projectRoot: string,
|
|
229
|
+
options: FrameworkSetupOptions
|
|
230
|
+
): Promise<FrameworkSetupResult> {
|
|
231
|
+
const result: FrameworkSetupResult = {
|
|
232
|
+
framework: "vite",
|
|
233
|
+
filesCreated: [],
|
|
234
|
+
packagesToInstall: [],
|
|
235
|
+
configModified: [],
|
|
236
|
+
warnings: [],
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// Check if sass is installed
|
|
240
|
+
try {
|
|
241
|
+
const pkgContent = await readFile(
|
|
242
|
+
join(projectRoot, "package.json"),
|
|
243
|
+
"utf-8"
|
|
244
|
+
);
|
|
245
|
+
const pkg = JSON.parse(pkgContent);
|
|
246
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
247
|
+
if (!allDeps["sass"]) {
|
|
248
|
+
result.packagesToInstall.push("sass");
|
|
249
|
+
}
|
|
250
|
+
} catch {
|
|
251
|
+
// Proceed
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Generate globals.scss
|
|
255
|
+
const globalsPath = join(projectRoot, "src", "styles", "globals.scss");
|
|
256
|
+
try {
|
|
257
|
+
await access(globalsPath);
|
|
258
|
+
result.warnings.push(
|
|
259
|
+
"src/styles/globals.scss already exists. Skipped generation."
|
|
260
|
+
);
|
|
261
|
+
} catch {
|
|
262
|
+
await writeFile(globalsPath, generateGlobalsSCSS(options.seeds), "utf-8");
|
|
263
|
+
result.filesCreated.push("src/styles/globals.scss");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Generate providers.tsx
|
|
267
|
+
const providersPath = join(projectRoot, "src", "providers.tsx");
|
|
268
|
+
try {
|
|
269
|
+
await access(providersPath);
|
|
270
|
+
result.warnings.push("src/providers.tsx already exists. Skipped.");
|
|
271
|
+
} catch {
|
|
272
|
+
await writeFile(providersPath, generateProviders(), "utf-8");
|
|
273
|
+
result.filesCreated.push("src/providers.tsx");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function setupRemix(
|
|
280
|
+
projectRoot: string,
|
|
281
|
+
options: FrameworkSetupOptions
|
|
282
|
+
): Promise<FrameworkSetupResult> {
|
|
283
|
+
const result: FrameworkSetupResult = {
|
|
284
|
+
framework: "remix",
|
|
285
|
+
filesCreated: [],
|
|
286
|
+
packagesToInstall: [],
|
|
287
|
+
configModified: [],
|
|
288
|
+
warnings: [],
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
result.packagesToInstall.push("sass");
|
|
292
|
+
|
|
293
|
+
// Generate globals.scss in app/styles
|
|
294
|
+
const globalsPath = join(projectRoot, "app", "styles", "globals.scss");
|
|
295
|
+
try {
|
|
296
|
+
await access(globalsPath);
|
|
297
|
+
result.warnings.push(
|
|
298
|
+
"app/styles/globals.scss already exists. Skipped generation."
|
|
299
|
+
);
|
|
300
|
+
} catch {
|
|
301
|
+
await writeFile(globalsPath, generateGlobalsSCSS(options.seeds), "utf-8");
|
|
302
|
+
result.filesCreated.push("app/styles/globals.scss");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Generate providers.tsx
|
|
306
|
+
const providersPath = join(projectRoot, "app", "providers.tsx");
|
|
307
|
+
try {
|
|
308
|
+
await access(providersPath);
|
|
309
|
+
result.warnings.push("app/providers.tsx already exists. Skipped.");
|
|
310
|
+
} catch {
|
|
311
|
+
await writeFile(providersPath, generateProviders(), "utf-8");
|
|
312
|
+
result.filesCreated.push("app/providers.tsx");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
result.warnings.push(
|
|
316
|
+
'Add @fragments-sdk/ui to serverDependenciesToBundle in remix.config if using source imports.'
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function setupAstro(
|
|
323
|
+
projectRoot: string,
|
|
324
|
+
options: FrameworkSetupOptions
|
|
325
|
+
): Promise<FrameworkSetupResult> {
|
|
326
|
+
const result: FrameworkSetupResult = {
|
|
327
|
+
framework: "astro",
|
|
328
|
+
filesCreated: [],
|
|
329
|
+
packagesToInstall: [],
|
|
330
|
+
configModified: [],
|
|
331
|
+
warnings: [],
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
result.packagesToInstall.push("sass");
|
|
335
|
+
|
|
336
|
+
// Generate globals.scss
|
|
337
|
+
const globalsPath = join(projectRoot, "src", "styles", "globals.scss");
|
|
338
|
+
try {
|
|
339
|
+
await access(globalsPath);
|
|
340
|
+
result.warnings.push(
|
|
341
|
+
"src/styles/globals.scss already exists. Skipped generation."
|
|
342
|
+
);
|
|
343
|
+
} catch {
|
|
344
|
+
await writeFile(globalsPath, generateGlobalsSCSS(options.seeds), "utf-8");
|
|
345
|
+
result.filesCreated.push("src/styles/globals.scss");
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ============================================
|
|
352
|
+
// Main Setup Function
|
|
353
|
+
// ============================================
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Set up framework-specific configuration for @fragments-sdk/ui
|
|
357
|
+
*/
|
|
358
|
+
export async function setupFramework(
|
|
359
|
+
options: FrameworkSetupOptions
|
|
360
|
+
): Promise<FrameworkSetupResult> {
|
|
361
|
+
const { projectRoot } = options;
|
|
362
|
+
|
|
363
|
+
// Detect or use provided framework
|
|
364
|
+
let framework = options.framework;
|
|
365
|
+
if (!framework || framework === "unknown") {
|
|
366
|
+
const detection = await detectFramework(projectRoot);
|
|
367
|
+
framework = detection.framework;
|
|
368
|
+
|
|
369
|
+
if (detection.detectedBy) {
|
|
370
|
+
console.log(
|
|
371
|
+
pc.green(` Detected ${frameworkLabel(framework)}`) +
|
|
372
|
+
pc.dim(` (via ${detection.detectedBy})`)
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
} else {
|
|
376
|
+
console.log(pc.green(` Framework: ${frameworkLabel(framework)}`));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
switch (framework) {
|
|
380
|
+
case "nextjs":
|
|
381
|
+
return setupNextJS(projectRoot, options);
|
|
382
|
+
case "vite":
|
|
383
|
+
return setupVite(projectRoot, options);
|
|
384
|
+
case "remix":
|
|
385
|
+
return setupRemix(projectRoot, options);
|
|
386
|
+
case "astro":
|
|
387
|
+
return setupAstro(projectRoot, options);
|
|
388
|
+
default:
|
|
389
|
+
return {
|
|
390
|
+
framework: "unknown",
|
|
391
|
+
filesCreated: [],
|
|
392
|
+
packagesToInstall: ["sass"],
|
|
393
|
+
configModified: [],
|
|
394
|
+
warnings: [
|
|
395
|
+
"Could not detect framework. Install sass and import @fragments-sdk/ui/styles manually.",
|
|
396
|
+
],
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function frameworkLabel(framework: Framework): string {
|
|
402
|
+
switch (framework) {
|
|
403
|
+
case "nextjs":
|
|
404
|
+
return "Next.js";
|
|
405
|
+
case "vite":
|
|
406
|
+
return "Vite";
|
|
407
|
+
case "remix":
|
|
408
|
+
return "Remix";
|
|
409
|
+
case "astro":
|
|
410
|
+
return "Astro";
|
|
411
|
+
default:
|
|
412
|
+
return "Unknown";
|
|
413
|
+
}
|
|
414
|
+
}
|
package/src/commands/init.ts
CHANGED
|
@@ -14,6 +14,11 @@ import pc from "picocolors";
|
|
|
14
14
|
import { BRAND } from "../core/index.js";
|
|
15
15
|
import fg from "fast-glob";
|
|
16
16
|
import { input, confirm, select } from "@inquirer/prompts";
|
|
17
|
+
import {
|
|
18
|
+
setupFramework,
|
|
19
|
+
detectFramework,
|
|
20
|
+
type Framework,
|
|
21
|
+
} from "./init-framework.js";
|
|
17
22
|
|
|
18
23
|
export interface InitOptions {
|
|
19
24
|
/** Project root directory */
|
|
@@ -22,6 +27,8 @@ export interface InitOptions {
|
|
|
22
27
|
force?: boolean;
|
|
23
28
|
/** Non-interactive mode - auto-detect and use defaults */
|
|
24
29
|
yes?: boolean;
|
|
30
|
+
/** Explicit framework override */
|
|
31
|
+
framework?: string;
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
export interface InitResult {
|
|
@@ -596,7 +603,40 @@ export async function init(options: InitOptions = {}): Promise<InitResult> {
|
|
|
596
603
|
}
|
|
597
604
|
}
|
|
598
605
|
|
|
599
|
-
// Step 6:
|
|
606
|
+
// Step 6: Framework-specific configuration
|
|
607
|
+
console.log(pc.dim("\nConfiguring framework integration...\n"));
|
|
608
|
+
|
|
609
|
+
const frameworkOverride = options.framework as Framework | undefined;
|
|
610
|
+
const frameworkResult = await setupFramework({
|
|
611
|
+
projectRoot,
|
|
612
|
+
framework: frameworkOverride,
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
if (frameworkResult.filesCreated.length > 0) {
|
|
616
|
+
for (const file of frameworkResult.filesCreated) {
|
|
617
|
+
console.log(pc.green(`✓ Created ${file}`));
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (frameworkResult.configModified.length > 0) {
|
|
622
|
+
for (const file of frameworkResult.configModified) {
|
|
623
|
+
console.log(pc.green(`✓ Updated ${file}`));
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (frameworkResult.packagesToInstall.length > 0) {
|
|
628
|
+
const pkgs = frameworkResult.packagesToInstall.join(" ");
|
|
629
|
+
console.log(
|
|
630
|
+
pc.yellow(`\n⚠ Install required dependencies: `) +
|
|
631
|
+
pc.bold(`pnpm add -D ${pkgs}`)
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
for (const warning of frameworkResult.warnings) {
|
|
636
|
+
console.log(pc.yellow(` Note: ${warning}`));
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Step 7: Show next steps or start server
|
|
600
640
|
if (errors.length === 0) {
|
|
601
641
|
console.log(pc.green("\n✓ Setup complete!\n"));
|
|
602
642
|
|
|
@@ -50,6 +50,8 @@ import { useActions } from "../hooks/useActions.js";
|
|
|
50
50
|
import { useUrlState, findFragmentByName, findVariantIndex } from "../hooks/useUrlState.js";
|
|
51
51
|
import { usePanelDock } from "./ResizablePanel.js";
|
|
52
52
|
import { useTheme } from "./ThemeProvider.js";
|
|
53
|
+
import { WebMCPStatusIndicator } from "./WebMCPStatusIndicator.js";
|
|
54
|
+
import { ViewerStateSync } from "./ViewerStateSync.js";
|
|
53
55
|
|
|
54
56
|
interface AppProps {
|
|
55
57
|
fragments: Array<{ path: string; fragment: FragmentDefinition }>;
|
|
@@ -364,6 +366,7 @@ export function App({ fragments }: AppProps) {
|
|
|
364
366
|
|
|
365
367
|
return (
|
|
366
368
|
<>
|
|
369
|
+
<ViewerStateSync fragments={fragments} activeVariantIndex={safeVariantIndex} />
|
|
367
370
|
<KeyboardShortcutsHelp isOpen={uiState.showShortcutsHelp} onClose={() => uiActions.setShortcutsHelp(false)} />
|
|
368
371
|
<CommandPalette
|
|
369
372
|
isOpen={uiState.showCommandPalette}
|
|
@@ -611,6 +614,7 @@ function ViewerHeader({ showHealth, searchQuery, onSearchChange, searchInputRef
|
|
|
611
614
|
<HeaderSearch value={searchQuery} onChange={onSearchChange} inputRef={searchInputRef} />
|
|
612
615
|
<Header.Spacer />
|
|
613
616
|
<Header.Actions>
|
|
617
|
+
<WebMCPStatusIndicator />
|
|
614
618
|
<ThemeToggle
|
|
615
619
|
size="sm"
|
|
616
620
|
value={resolvedTheme}
|
|
@@ -833,6 +837,7 @@ function TopToolbar({
|
|
|
833
837
|
<Separator orientation="vertical" style={{ height: '16px' }} />
|
|
834
838
|
</>
|
|
835
839
|
)}
|
|
840
|
+
<WebMCPStatusIndicator />
|
|
836
841
|
<ThemeToggle
|
|
837
842
|
size="sm"
|
|
838
843
|
value={resolvedTheme}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { useMemo, useState, useCallback, useEffect } from 'react';
|
|
8
8
|
import type { FragmentDefinition } from '../../core/index.js';
|
|
9
9
|
import type { ImpactValue } from 'axe-core';
|
|
10
|
-
import { Badge, Progress, Stack, Text, Card, EmptyState
|
|
10
|
+
import { Badge, Progress, Stack, Text, Card, EmptyState } from '@fragments-sdk/ui';
|
|
11
11
|
import {
|
|
12
12
|
getAllA11yData,
|
|
13
13
|
getA11ySummary,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import type { PropDefinition } from '../../core/index.js';
|
|
3
|
-
import {
|
|
3
|
+
import { DataTable, createColumns, Badge, Text, Stack } from '@fragments-sdk/ui';
|
|
4
4
|
import { WarningIcon } from './Icons.js';
|
|
5
5
|
|
|
6
6
|
interface PropsTableProps {
|
|
@@ -99,7 +99,7 @@ export function PropsTable({ props }: PropsTableProps) {
|
|
|
99
99
|
return (
|
|
100
100
|
<section id="props" style={{ scrollMarginTop: '96px' }}>
|
|
101
101
|
<Text as="h2" size="md" weight="semibold" style={{ marginBottom: '16px' }}>Props</Text>
|
|
102
|
-
<
|
|
102
|
+
<DataTable
|
|
103
103
|
columns={columns}
|
|
104
104
|
data={data}
|
|
105
105
|
size="sm"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useWebMCPTools } from '@fragments-sdk/webmcp/react';
|
|
3
|
+
import { useCompiledFragments } from '../hooks/useCompiledFragments.js';
|
|
4
|
+
import { createRuntimeWebMCPTools } from '../webmcp/runtime-tools.js';
|
|
5
|
+
|
|
6
|
+
export function RuntimeToolsRegistrar() {
|
|
7
|
+
const { data } = useCompiledFragments();
|
|
8
|
+
|
|
9
|
+
const tools = useMemo(() => {
|
|
10
|
+
if (!data) return [];
|
|
11
|
+
return createRuntimeWebMCPTools({ compiledData: data });
|
|
12
|
+
}, [data]);
|
|
13
|
+
|
|
14
|
+
useWebMCPTools(tools);
|
|
15
|
+
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { setViewerState } from '../webmcp/viewer-state.js';
|
|
3
|
+
import { useUrlState } from '../hooks/useUrlState.js';
|
|
4
|
+
import { useAppState } from '../hooks/useAppState.js';
|
|
5
|
+
import { useViewSettings } from '../hooks/useViewSettings.js';
|
|
6
|
+
import { useTheme } from './ThemeProvider.js';
|
|
7
|
+
import { BRAND, type FragmentDefinition } from '../../core/index.js';
|
|
8
|
+
|
|
9
|
+
interface ViewerStateSyncProps {
|
|
10
|
+
fragments: Array<{ path: string; fragment: FragmentDefinition }>;
|
|
11
|
+
activeVariantIndex: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function ViewerStateSync({ fragments, activeVariantIndex }: ViewerStateSyncProps) {
|
|
15
|
+
const { state: urlState } = useUrlState();
|
|
16
|
+
const { state: uiState } = useAppState();
|
|
17
|
+
const viewSettings = useViewSettings();
|
|
18
|
+
const { resolvedTheme } = useTheme();
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const activeFragment = urlState.component
|
|
22
|
+
? fragments.find(f => f.fragment.meta.name.toLowerCase() === urlState.component!.toLowerCase())
|
|
23
|
+
: null;
|
|
24
|
+
|
|
25
|
+
const variants = activeFragment?.fragment.variants ?? [];
|
|
26
|
+
|
|
27
|
+
setViewerState({
|
|
28
|
+
currentComponent: activeFragment?.fragment.meta.name ?? null,
|
|
29
|
+
currentVariant: variants[activeVariantIndex]?.name ?? null,
|
|
30
|
+
variantIndex: activeVariantIndex,
|
|
31
|
+
totalVariants: variants.length,
|
|
32
|
+
viewport: viewSettings.viewport,
|
|
33
|
+
zoom: viewSettings.zoom,
|
|
34
|
+
theme: resolvedTheme,
|
|
35
|
+
panels: {
|
|
36
|
+
activePanel: uiState.activePanel,
|
|
37
|
+
panelOpen: uiState.panelOpen,
|
|
38
|
+
},
|
|
39
|
+
viewMode: {
|
|
40
|
+
matrixView: uiState.showMatrixView,
|
|
41
|
+
multiViewport: uiState.showMultiViewport,
|
|
42
|
+
comparison: uiState.showComparison,
|
|
43
|
+
},
|
|
44
|
+
designSystem: {
|
|
45
|
+
name: BRAND.name,
|
|
46
|
+
componentCount: fragments.length,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}, [urlState, uiState, viewSettings, resolvedTheme, fragments, activeVariantIndex]);
|
|
50
|
+
|
|
51
|
+
return null;
|
|
52
|
+
}
|