@fragments-sdk/cli 0.7.16 → 0.8.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/dist/bin.js +227 -53
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-QLTLLQBI.js → chunk-2JIKCJX3.js} +312 -24
- package/dist/chunk-2JIKCJX3.js.map +1 -0
- package/dist/{chunk-57OW43NL.js → chunk-CJEGT3WD.js} +2 -2
- package/dist/{chunk-7CRC46HV.js → chunk-GOVI6COW.js} +13 -3
- package/dist/chunk-GOVI6COW.js.map +1 -0
- package/dist/{chunk-WLXFE6XW.js → chunk-NGIMCIK2.js} +60 -2
- package/dist/chunk-NGIMCIK2.js.map +1 -0
- package/dist/{chunk-M42XIHPV.js → chunk-WI6SLMSO.js} +2 -2
- package/dist/core/index.d.ts +110 -3
- package/dist/core/index.js +12 -2
- package/dist/{defineFragment-BI9KoPrs.d.ts → defineFragment-D0UTve-I.d.ts} +9 -0
- package/dist/{generate-ICIPKCKV.js → generate-35OIMW4Y.js} +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -4
- package/dist/{init-DIZ6UNBL.js → init-KFYN37ZY.js} +4 -4
- package/dist/mcp-bin.js +67 -3
- package/dist/mcp-bin.js.map +1 -1
- package/dist/{scan-X3DI2X5G.js → scan-65RH3QMM.js} +5 -5
- package/dist/{service-JEWWTSKI.js → service-A5GIGGGK.js} +3 -3
- package/dist/{static-viewer-JIWCYKVK.js → static-viewer-NSODM5VX.js} +3 -3
- package/dist/{test-36UELXTE.js → test-RPWZAYSJ.js} +3 -3
- package/dist/{tokens-K2AGUUOJ.js → tokens-NIXSZRX7.js} +4 -4
- package/dist/{viewer-QKIAPTPG.js → viewer-HZK4BSDK.js} +43 -12
- package/dist/viewer-HZK4BSDK.js.map +1 -0
- package/package.json +3 -3
- package/src/bin.ts +32 -0
- package/src/build.ts +47 -0
- package/src/commands/perf.ts +249 -0
- package/src/core/bundle-measurer.ts +421 -0
- package/src/core/index.ts +16 -0
- package/src/core/performance-presets.ts +142 -0
- package/src/core/schema.ts +10 -0
- package/src/core/types.ts +6 -0
- package/src/mcp/server.ts +77 -0
- package/src/theme/__tests__/component-contrast.test.ts +210 -157
- package/src/viewer/components/BottomPanel.tsx +8 -0
- package/src/viewer/components/PerformancePanel.tsx +301 -0
- package/src/viewer/hooks/useAppState.ts +1 -1
- package/src/viewer/vite-plugin.ts +36 -0
- package/dist/chunk-7CRC46HV.js.map +0 -1
- package/dist/chunk-QLTLLQBI.js.map +0 -1
- package/dist/chunk-WLXFE6XW.js.map +0 -1
- package/dist/viewer-QKIAPTPG.js.map +0 -1
- /package/dist/{chunk-57OW43NL.js.map → chunk-CJEGT3WD.js.map} +0 -0
- /package/dist/{chunk-M42XIHPV.js.map → chunk-WI6SLMSO.js.map} +0 -0
- /package/dist/{generate-ICIPKCKV.js.map → generate-35OIMW4Y.js.map} +0 -0
- /package/dist/{init-DIZ6UNBL.js.map → init-KFYN37ZY.js.map} +0 -0
- /package/dist/{scan-X3DI2X5G.js.map → scan-65RH3QMM.js.map} +0 -0
- /package/dist/{service-JEWWTSKI.js.map → service-A5GIGGGK.js.map} +0 -0
- /package/dist/{static-viewer-JIWCYKVK.js.map → static-viewer-NSODM5VX.js.map} +0 -0
- /package/dist/{test-36UELXTE.js.map → test-RPWZAYSJ.js.map} +0 -0
- /package/dist/{tokens-K2AGUUOJ.js.map → tokens-NIXSZRX7.js.map} +0 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance budget presets and classification utilities.
|
|
3
|
+
*
|
|
4
|
+
* ESLint model: global defaults, zero-config, auto-measurement.
|
|
5
|
+
* Users configure 0-3 numbers. Per-component overrides are the `eslint-disable` equivalent.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Types
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
export interface PerformanceBudgets {
|
|
13
|
+
/** Maximum gzipped bundle size in bytes */
|
|
14
|
+
bundleSize: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PerformanceConfig {
|
|
18
|
+
preset: string;
|
|
19
|
+
budgets: PerformanceBudgets;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ComplexityTier = 'lightweight' | 'moderate' | 'heavy';
|
|
23
|
+
|
|
24
|
+
export interface PerformanceData {
|
|
25
|
+
/** Gzipped bundle size in bytes */
|
|
26
|
+
bundleSize: number;
|
|
27
|
+
/** Raw (minified, not gzipped) bundle size in bytes */
|
|
28
|
+
rawSize: number;
|
|
29
|
+
/** Complexity classification */
|
|
30
|
+
complexity: ComplexityTier;
|
|
31
|
+
/** Percentage of budget used (0-100+) */
|
|
32
|
+
budgetPercent: number;
|
|
33
|
+
/** Whether the component exceeds its budget */
|
|
34
|
+
overBudget: boolean;
|
|
35
|
+
/** ISO timestamp when measured */
|
|
36
|
+
measuredAt: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface PerformanceSummary {
|
|
40
|
+
/** Preset name used */
|
|
41
|
+
preset: string;
|
|
42
|
+
/** Budget applied in bytes */
|
|
43
|
+
budget: number;
|
|
44
|
+
/** Total components measured */
|
|
45
|
+
total: number;
|
|
46
|
+
/** Number of components over budget */
|
|
47
|
+
overBudget: number;
|
|
48
|
+
/** Distribution by tier */
|
|
49
|
+
tiers: Record<ComplexityTier, number>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Presets
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
const PRESETS: Record<string, PerformanceBudgets> = {
|
|
57
|
+
strict: { bundleSize: 8 * 1024 }, // 8KB gzipped
|
|
58
|
+
standard: { bundleSize: 15 * 1024 }, // 15KB gzipped
|
|
59
|
+
relaxed: { bundleSize: 30 * 1024 }, // 30KB gzipped
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const PRESET_NAMES = Object.keys(PRESETS) as readonly string[];
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Resolution
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Resolve a performance config from user input.
|
|
70
|
+
* Accepts a preset name string or a custom config object.
|
|
71
|
+
*/
|
|
72
|
+
export function resolvePerformanceConfig(
|
|
73
|
+
input: string | { preset?: string; budgets?: Partial<PerformanceBudgets> } | undefined
|
|
74
|
+
): PerformanceConfig {
|
|
75
|
+
if (!input) {
|
|
76
|
+
return { preset: 'standard', budgets: PRESETS.standard };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (typeof input === 'string') {
|
|
80
|
+
const budgets = PRESETS[input];
|
|
81
|
+
if (!budgets) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Unknown performance preset "${input}". Available: ${PRESET_NAMES.join(', ')}`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return { preset: input, budgets };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const presetName = input.preset ?? 'standard';
|
|
90
|
+
const baseBudgets = PRESETS[presetName];
|
|
91
|
+
if (!baseBudgets) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Unknown performance preset "${presetName}". Available: ${PRESET_NAMES.join(', ')}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
preset: presetName,
|
|
99
|
+
budgets: {
|
|
100
|
+
bundleSize: input.budgets?.bundleSize ?? baseBudgets.bundleSize,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// Classification
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Classify a component's complexity based on gzipped bundle size.
|
|
111
|
+
*
|
|
112
|
+
* - lightweight: < 5KB — simple, leaf components
|
|
113
|
+
* - moderate: < 15KB — typical composed components
|
|
114
|
+
* - heavy: >= 15KB — complex widgets with dependencies
|
|
115
|
+
*/
|
|
116
|
+
export function classifyComplexity(gzipBytes: number): ComplexityTier {
|
|
117
|
+
if (gzipBytes < 5 * 1024) return 'lightweight';
|
|
118
|
+
if (gzipBytes < 15 * 1024) return 'moderate';
|
|
119
|
+
return 'heavy';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// Formatting helpers
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Format bytes to a human-readable string (e.g. "2.1KB", "15.3KB").
|
|
128
|
+
*/
|
|
129
|
+
export function formatBytes(bytes: number): string {
|
|
130
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
131
|
+
const kb = bytes / 1024;
|
|
132
|
+
return kb < 10 ? `${kb.toFixed(1)}KB` : `${Math.round(kb)}KB`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Create a visual budget bar for terminal output.
|
|
137
|
+
*/
|
|
138
|
+
export function budgetBar(percent: number, width = 20): string {
|
|
139
|
+
const filled = Math.min(Math.round((percent / 100) * width), width);
|
|
140
|
+
const bar = '█'.repeat(filled) + '░'.repeat(width - filled);
|
|
141
|
+
return percent > 100 ? `\x1b[31m${bar}\x1b[0m` : `\x1b[32m${bar}\x1b[0m`;
|
|
142
|
+
}
|
package/src/core/schema.ts
CHANGED
|
@@ -133,6 +133,7 @@ export const fragmentContractSchema = z.object({
|
|
|
133
133
|
a11yRules: z.array(z.string()).optional(),
|
|
134
134
|
bans: z.array(fragmentBanSchema).optional(),
|
|
135
135
|
scenarioTags: z.array(z.string()).optional(),
|
|
136
|
+
performanceBudget: z.number().positive().optional(),
|
|
136
137
|
});
|
|
137
138
|
|
|
138
139
|
/**
|
|
@@ -203,6 +204,15 @@ export const fragmentsConfigSchema = z.object({
|
|
|
203
204
|
requireFullSnippet: z.boolean().optional(),
|
|
204
205
|
allowedExternalModules: z.array(z.string().min(1)).optional(),
|
|
205
206
|
}).optional(),
|
|
207
|
+
performance: z.union([
|
|
208
|
+
z.enum(['strict', 'standard', 'relaxed']),
|
|
209
|
+
z.object({
|
|
210
|
+
preset: z.enum(['strict', 'standard', 'relaxed']).optional(),
|
|
211
|
+
budgets: z.object({
|
|
212
|
+
bundleSize: z.number().positive().optional(),
|
|
213
|
+
}).optional(),
|
|
214
|
+
}),
|
|
215
|
+
]).optional(),
|
|
206
216
|
});
|
|
207
217
|
|
|
208
218
|
/**
|
package/src/core/types.ts
CHANGED
|
@@ -303,6 +303,9 @@ export interface FragmentContract {
|
|
|
303
303
|
|
|
304
304
|
/** Scenario tags for use-case matching (e.g., "form.submit", "navigation.primary") */
|
|
305
305
|
scenarioTags?: string[];
|
|
306
|
+
|
|
307
|
+
/** Per-component performance budget override in bytes (gzipped). Overrides global budget. */
|
|
308
|
+
performanceBudget?: number;
|
|
306
309
|
}
|
|
307
310
|
|
|
308
311
|
/**
|
|
@@ -493,6 +496,9 @@ export interface FragmentsConfig {
|
|
|
493
496
|
|
|
494
497
|
/** Snippet/render policy validation */
|
|
495
498
|
snippets?: SnippetPolicyConfig;
|
|
499
|
+
|
|
500
|
+
/** Performance budgets: preset name or custom config */
|
|
501
|
+
performance?: string | { preset?: string; budgets?: { bundleSize?: number } };
|
|
496
502
|
}
|
|
497
503
|
|
|
498
504
|
/**
|
package/src/mcp/server.ts
CHANGED
|
@@ -1536,6 +1536,83 @@ export function createMcpServer(config: McpServerConfig): Server {
|
|
|
1536
1536
|
}
|
|
1537
1537
|
}
|
|
1538
1538
|
|
|
1539
|
+
// ================================================================
|
|
1540
|
+
// PERF — query performance data
|
|
1541
|
+
// ================================================================
|
|
1542
|
+
case TOOL_NAMES.perf: {
|
|
1543
|
+
const data = await loadFragments();
|
|
1544
|
+
const componentName = (args?.component as string) ?? undefined;
|
|
1545
|
+
const sort = (args?.sort as string) ?? 'size';
|
|
1546
|
+
const filter = (args?.filter as string) ?? undefined;
|
|
1547
|
+
|
|
1548
|
+
// Collect components with performance data
|
|
1549
|
+
let entries = Object.entries(data.fragments)
|
|
1550
|
+
.filter(([, f]) => f.performance)
|
|
1551
|
+
.map(([name, f]) => ({
|
|
1552
|
+
name,
|
|
1553
|
+
...f.performance!,
|
|
1554
|
+
}));
|
|
1555
|
+
|
|
1556
|
+
if (entries.length === 0) {
|
|
1557
|
+
return {
|
|
1558
|
+
content: [{
|
|
1559
|
+
type: 'text' as const,
|
|
1560
|
+
text: JSON.stringify({
|
|
1561
|
+
total: 0,
|
|
1562
|
+
components: [],
|
|
1563
|
+
hint: `No performance data found. Run \`${BRAND.cliCommand} perf\` first to measure bundle sizes.`,
|
|
1564
|
+
}, null, 2),
|
|
1565
|
+
}],
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
// Filter by specific component
|
|
1570
|
+
if (componentName) {
|
|
1571
|
+
entries = entries.filter(
|
|
1572
|
+
(e) => e.name.toLowerCase() === componentName.toLowerCase()
|
|
1573
|
+
);
|
|
1574
|
+
if (entries.length === 0) {
|
|
1575
|
+
throw new Error(
|
|
1576
|
+
`No performance data for "${componentName}". Run \`${BRAND.cliCommand} perf --component ${componentName}\` first.`
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
// Filter by tier or over-budget
|
|
1582
|
+
if (filter) {
|
|
1583
|
+
if (filter === 'over-budget') {
|
|
1584
|
+
entries = entries.filter((e) => e.overBudget);
|
|
1585
|
+
} else {
|
|
1586
|
+
entries = entries.filter((e) => e.complexity === filter);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
// Sort
|
|
1591
|
+
switch (sort) {
|
|
1592
|
+
case 'name':
|
|
1593
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
1594
|
+
break;
|
|
1595
|
+
case 'budget':
|
|
1596
|
+
entries.sort((a, b) => b.budgetPercent - a.budgetPercent);
|
|
1597
|
+
break;
|
|
1598
|
+
case 'size':
|
|
1599
|
+
default:
|
|
1600
|
+
entries.sort((a, b) => b.bundleSize - a.bundleSize);
|
|
1601
|
+
break;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
return {
|
|
1605
|
+
content: [{
|
|
1606
|
+
type: 'text' as const,
|
|
1607
|
+
text: JSON.stringify({
|
|
1608
|
+
total: entries.length,
|
|
1609
|
+
summary: data.performanceSummary ?? undefined,
|
|
1610
|
+
components: entries,
|
|
1611
|
+
}, null, 2),
|
|
1612
|
+
}],
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1539
1616
|
default:
|
|
1540
1617
|
throw new Error(`Unknown tool: ${name}`);
|
|
1541
1618
|
}
|