prism-design 2.13.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/CHANGELOG.md +292 -0
- package/LICENSE +21 -0
- package/README.md +203 -0
- package/bin/clone-architect.mjs +476 -0
- package/bin/prism.mjs +467 -0
- package/catalog/index.json +1155 -0
- package/extractions/airbnb.com/DESIGN.md +1068 -0
- package/extractions/airbnb.com/tokens.json +507 -0
- package/extractions/attio.com/DESIGN.md +1295 -0
- package/extractions/attio.com/tokens.json +438 -0
- package/extractions/auroxdashboard.com/DESIGN.md +724 -0
- package/extractions/auroxdashboard.com/tokens.json +195 -0
- package/extractions/careerexplorer.com/DESIGN.md +1178 -0
- package/extractions/careerexplorer.com/tokens.json +141 -0
- package/extractions/chance.co/DESIGN.md +1209 -0
- package/extractions/chance.co/tokens.json +160 -0
- package/extractions/choisis-ton-avenir.com/DESIGN.md +1265 -0
- package/extractions/choisis-ton-avenir.com/tokens.json +227 -0
- package/extractions/example.com/DESIGN.md +436 -0
- package/extractions/example.com/tokens.json +91 -0
- package/extractions/getdesign.md/DESIGN.md +1009 -0
- package/extractions/getdesign.md/tokens.json +219 -0
- package/extractions/github.com/DESIGN.md +1130 -0
- package/extractions/github.com/tokens.json +2092 -0
- package/extractions/hello-charly.com/DESIGN.md +1146 -0
- package/extractions/hello-charly.com/tokens.json +322 -0
- package/extractions/hyperliquid.xyz/DESIGN.md +779 -0
- package/extractions/hyperliquid.xyz/tokens.json +598 -0
- package/extractions/instagram.com/DESIGN.md +996 -0
- package/extractions/instagram.com/tokens.json +1240 -0
- package/extractions/jobirl.com/DESIGN.md +1160 -0
- package/extractions/jobirl.com/tokens.json +139 -0
- package/extractions/life360.com/DESIGN.md +1133 -0
- package/extractions/life360.com/tokens.json +491 -0
- package/extractions/lifesum.com/DESIGN.md +965 -0
- package/extractions/lifesum.com/tokens.json +170 -0
- package/extractions/linear.app/DESIGN.md +1301 -0
- package/extractions/linear.app/tokens.json +732 -0
- package/extractions/mavoie.org/DESIGN.md +1148 -0
- package/extractions/mavoie.org/tokens.json +128 -0
- package/extractions/miro.com/DESIGN.md +1237 -0
- package/extractions/miro.com/tokens.json +401 -0
- package/extractions/notion.so/DESIGN.md +1319 -0
- package/extractions/notion.so/tokens.json +906 -0
- package/extractions/onetonline.org/DESIGN.md +909 -0
- package/extractions/onetonline.org/tokens.json +280 -0
- package/extractions/posthog.com/DESIGN.md +1024 -0
- package/extractions/posthog.com/tokens.json +197 -0
- package/extractions/revolut.com/DESIGN.md +1080 -0
- package/extractions/revolut.com/tokens.json +401 -0
- package/extractions/stripe.com/DESIGN.md +1272 -0
- package/extractions/stripe.com/tokens.json +794 -0
- package/extractions/switchcollective.com/DESIGN.md +1040 -0
- package/extractions/switchcollective.com/tokens.json +98 -0
- package/extractions/truity.com/DESIGN.md +970 -0
- package/extractions/truity.com/tokens.json +166 -0
- package/extractions/uniquekicks.be/DESIGN.md +1171 -0
- package/extractions/uniquekicks.be/tokens.json +237 -0
- package/package.json +122 -0
- package/scripts/analyze.ts +281 -0
- package/scripts/bank-register.ts +379 -0
- package/scripts/bank.ts +374 -0
- package/scripts/browser-stealth.ts +189 -0
- package/scripts/clone.ts +198 -0
- package/scripts/compare-vs-gd-final.ts +273 -0
- package/scripts/compare-vs-gd.ts +269 -0
- package/scripts/compare.ts +405 -0
- package/scripts/deploy-site.ts +181 -0
- package/scripts/diff-snapshots.ts +340 -0
- package/scripts/enrich-catalog.ts +212 -0
- package/scripts/extract.ts +2038 -0
- package/scripts/extractors/advanced.ts +524 -0
- package/scripts/extractors/widgets.ts +711 -0
- package/scripts/generate-design-md.ts +5775 -0
- package/scripts/generate-final-pdf.ts +274 -0
- package/scripts/generate-og-image.ts +87 -0
- package/scripts/generate-showcase.ts +1588 -0
- package/scripts/generate-site.ts +847 -0
- package/scripts/mass-extract.sh +91 -0
- package/scripts/post-process-all.sh +55 -0
- package/scripts/regen-catalog.ts +203 -0
- package/scripts/shared/cache.ts +149 -0
- package/scripts/shared/css-helpers.ts +263 -0
- package/scripts/shared/logger.ts +57 -0
- package/scripts/shared/named-colors.ts +355 -0
- package/scripts/shared/types.ts +220 -0
- package/scripts/sync-catalog.ts +105 -0
- package/scripts/tokenize.ts +988 -0
- package/templates/layout-template.md +52 -0
- package/templates/tokens-template.json +34 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bank-register.ts — Prism Component Bank
|
|
3
|
+
* Reads existing extractions → creates ComponentSnapshot JSON files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
|
|
9
|
+
const EXTRACTIONS_DIR = path.join(process.cwd(), 'extractions');
|
|
10
|
+
export const BANK_DIR = path.join(process.cwd(), 'bank');
|
|
11
|
+
|
|
12
|
+
// ─── Interfaces (unifiées via shared/types.ts) ──────────────────────────────
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
SiteTokens,
|
|
16
|
+
ComponentSnapshot,
|
|
17
|
+
BankIndexEntry,
|
|
18
|
+
BankIndex,
|
|
19
|
+
BankCatalog,
|
|
20
|
+
} from './shared/types.js';
|
|
21
|
+
|
|
22
|
+
// Re-export pour backward compat
|
|
23
|
+
export type { SiteTokens, ComponentSnapshot, BankIndexEntry, BankIndex, BankCatalog };
|
|
24
|
+
|
|
25
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
export function slugify(str: string): string {
|
|
28
|
+
return str
|
|
29
|
+
.replace(/\./g, '-')
|
|
30
|
+
.replace(/[^a-z0-9-]/gi, '-')
|
|
31
|
+
.replace(/-+/g, '-')
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.replace(/^-|-$/g, '');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
import {
|
|
37
|
+
parseRgb as parseRgbShared,
|
|
38
|
+
luminanceMemo as luminance,
|
|
39
|
+
colorHueTag,
|
|
40
|
+
} from './shared/css-helpers.js';
|
|
41
|
+
|
|
42
|
+
/** Wrapper avec fallback [0,0,0] pour compat bank-register */
|
|
43
|
+
function parseRgb(color: string): [number, number, number] {
|
|
44
|
+
return parseRgbShared(color) ?? [0, 0, 0];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function detectTags(domain: string, siteTokens: SiteTokens): string[] {
|
|
48
|
+
const tags: string[] = [];
|
|
49
|
+
|
|
50
|
+
// Theme
|
|
51
|
+
tags.push(siteTokens.isDark ? 'dark' : 'light');
|
|
52
|
+
|
|
53
|
+
// Industry
|
|
54
|
+
if (/sneakers|resell|wehaveit|limited|wethenew|shopify|product|store|ecom/i.test(domain)) {
|
|
55
|
+
tags.push('ecommerce');
|
|
56
|
+
}
|
|
57
|
+
if (/linear|raindrop|notion|figma|vercel|app|studio|dashboard/i.test(domain)) {
|
|
58
|
+
tags.push('saas');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Font style
|
|
62
|
+
const font = siteTokens.typography.primary.toLowerCase().replace(/['"]/g, '');
|
|
63
|
+
if (/grotesque|grotesk|gothic|helvetica|inter|dm\s?sans|figtree|roboto|manrope|outfit/i.test(font)) {
|
|
64
|
+
tags.push('sans-modern');
|
|
65
|
+
} else if (/serif|times|georgia|playfair|garamond/i.test(font)) {
|
|
66
|
+
tags.push('serif');
|
|
67
|
+
} else if (/mono|code|courier|jetbrains/i.test(font)) {
|
|
68
|
+
tags.push('mono');
|
|
69
|
+
} else if (/montserrat|futura|avenir|proxima|gilroy/i.test(font)) {
|
|
70
|
+
tags.push('geometric-sans');
|
|
71
|
+
} else {
|
|
72
|
+
tags.push('custom-font');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Border radius
|
|
76
|
+
const br = parseFloat(siteTokens.borderRadius.md || '0');
|
|
77
|
+
if (br <= 2) tags.push('sharp');
|
|
78
|
+
else if (br <= 8) tags.push('subtle-radius');
|
|
79
|
+
else if (br <= 16) tags.push('rounded');
|
|
80
|
+
else tags.push('pill-radius');
|
|
81
|
+
|
|
82
|
+
// Accent color hue
|
|
83
|
+
const hueTag = colorHueTag(siteTokens.colors.accent);
|
|
84
|
+
if (hueTag) tags.push(hueTag);
|
|
85
|
+
else tags.push('accent-neutral');
|
|
86
|
+
|
|
87
|
+
return [...new Set(tags)];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function buildSiteTokens(tokens: any): SiteTokens {
|
|
91
|
+
const bgColor = tokens.colors?.background?.primary || 'rgb(255, 255, 255)';
|
|
92
|
+
const isTransparent = bgColor.includes('rgba(0, 0, 0, 0)');
|
|
93
|
+
const resolvedBg = isTransparent ? 'rgb(255, 255, 255)' : bgColor;
|
|
94
|
+
const [r, g, b] = parseRgb(resolvedBg);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
colors: {
|
|
98
|
+
background: resolvedBg,
|
|
99
|
+
text: tokens.colors?.text?.primary || 'rgb(0, 0, 0)',
|
|
100
|
+
accent: tokens.colors?.accent?.primary || 'rgb(0, 0, 0)',
|
|
101
|
+
border: tokens.colors?.border || 'rgb(229, 231, 235)',
|
|
102
|
+
},
|
|
103
|
+
typography: {
|
|
104
|
+
primary: tokens.typography?.fontFamily?.primary || 'sans-serif',
|
|
105
|
+
secondary: tokens.typography?.fontFamily?.secondary,
|
|
106
|
+
},
|
|
107
|
+
borderRadius: {
|
|
108
|
+
sm: tokens.borderRadius?.sm || '4px',
|
|
109
|
+
md: tokens.borderRadius?.md || '8px',
|
|
110
|
+
full: tokens.borderRadius?.full || '9999px',
|
|
111
|
+
},
|
|
112
|
+
spacing: {
|
|
113
|
+
xs: tokens.spacing?.xs || '4px',
|
|
114
|
+
sm: tokens.spacing?.sm || '8px',
|
|
115
|
+
md: tokens.spacing?.md || '16px',
|
|
116
|
+
lg: tokens.spacing?.lg || '24px',
|
|
117
|
+
},
|
|
118
|
+
isDark: luminance(r, g, b) < 0.3,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function normalizeType(rawType: string): string {
|
|
123
|
+
if (rawType.startsWith('heading')) return 'heading';
|
|
124
|
+
if (rawType === 'buttons') return 'button';
|
|
125
|
+
if (rawType === 'cards') return 'card';
|
|
126
|
+
if (rawType === 'inputs') return 'input';
|
|
127
|
+
if (rawType === 'badges') return 'badge';
|
|
128
|
+
if (rawType === 'links') return 'link';
|
|
129
|
+
return rawType;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function labelVariant(type: string, rawType: string, index: number, styles: any): string {
|
|
133
|
+
if (type === 'button') {
|
|
134
|
+
if (index === 0) return 'Primary Button';
|
|
135
|
+
const bg = styles?.backgroundColor || '';
|
|
136
|
+
const [r, g, b] = parseRgb(bg);
|
|
137
|
+
const lum = luminance(r, g, b);
|
|
138
|
+
if (lum > 0.8) return `Ghost Button`;
|
|
139
|
+
if (lum > 0.4) return `Light Button`;
|
|
140
|
+
return `Dark Button`;
|
|
141
|
+
}
|
|
142
|
+
if (type === 'card') {
|
|
143
|
+
if (index === 0) return 'Primary Card';
|
|
144
|
+
return `Card Variant ${index + 1}`;
|
|
145
|
+
}
|
|
146
|
+
if (type === 'heading') {
|
|
147
|
+
const level = rawType.replace('heading', '').replace('H', 'h');
|
|
148
|
+
return `Heading ${level.toUpperCase() || (index + 1)}`;
|
|
149
|
+
}
|
|
150
|
+
if (type === 'input') return index === 0 ? 'Text Input' : `Input Variant ${index + 1}`;
|
|
151
|
+
if (type === 'badge') return index === 0 ? 'Primary Badge' : `Badge Variant ${index + 1}`;
|
|
152
|
+
if (type === 'link') return index === 0 ? 'Text Link' : `Link Variant ${index + 1}`;
|
|
153
|
+
return `${type} ${index + 1}`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ─── Core: register one domain ────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
export async function registerDomain(domain: string, verbose = true): Promise<number> {
|
|
159
|
+
const extractionPath = path.join(EXTRACTIONS_DIR, domain);
|
|
160
|
+
|
|
161
|
+
if (!fs.existsSync(extractionPath)) {
|
|
162
|
+
if (verbose) console.error(` ❌ Extraction introuvable: ${domain}`);
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const tokensPath = path.join(extractionPath, 'tokens.json');
|
|
167
|
+
const rawCssPath = path.join(extractionPath, 'raw-css.json');
|
|
168
|
+
const designMdPath = path.join(extractionPath, 'DESIGN.md');
|
|
169
|
+
|
|
170
|
+
if (!fs.existsSync(tokensPath) || !fs.existsSync(rawCssPath)) {
|
|
171
|
+
if (verbose) console.error(` ❌ tokens.json ou raw-css.json manquant pour ${domain}`);
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const tokens = JSON.parse(fs.readFileSync(tokensPath, 'utf-8'));
|
|
176
|
+
const rawCss = JSON.parse(fs.readFileSync(rawCssPath, 'utf-8'));
|
|
177
|
+
const desktop = rawCss.desktop || rawCss;
|
|
178
|
+
const componentVariants: Record<string, any> = desktop.componentVariants || {};
|
|
179
|
+
|
|
180
|
+
const siteTokens = buildSiteTokens(tokens);
|
|
181
|
+
const baseTags = detectTags(domain, siteTokens);
|
|
182
|
+
const registeredAt = new Date().toISOString();
|
|
183
|
+
const extractedAt = tokens.meta?.extractedAt || tokens.meta?.tokenizedAt || registeredAt;
|
|
184
|
+
const url = tokens.meta?.source || `https://${domain.replace(/--.*$/, '')}/`;
|
|
185
|
+
|
|
186
|
+
// Ensure bank directory
|
|
187
|
+
fs.mkdirSync(BANK_DIR, { recursive: true });
|
|
188
|
+
|
|
189
|
+
// Load or init bank index / catalog
|
|
190
|
+
const indexPath = path.join(BANK_DIR, '_index.json');
|
|
191
|
+
const catalogPath = path.join(BANK_DIR, '_catalog.json');
|
|
192
|
+
|
|
193
|
+
const index: BankIndex = fs.existsSync(indexPath)
|
|
194
|
+
? JSON.parse(fs.readFileSync(indexPath, 'utf-8'))
|
|
195
|
+
: { version: '1.0.0', updatedAt: registeredAt, components: [] };
|
|
196
|
+
|
|
197
|
+
const catalog: BankCatalog = fs.existsSync(catalogPath)
|
|
198
|
+
? JSON.parse(fs.readFileSync(catalogPath, 'utf-8'))
|
|
199
|
+
: { version: '1.0.0', updatedAt: registeredAt, byType: {}, byDomain: {}, byTags: {} };
|
|
200
|
+
|
|
201
|
+
// Remove existing entries for this domain (clean re-register)
|
|
202
|
+
const domainSlug = slugify(domain);
|
|
203
|
+
index.components = index.components.filter(c => c.domain !== domain);
|
|
204
|
+
for (const t of Object.keys(catalog.byType)) {
|
|
205
|
+
catalog.byType[t] = catalog.byType[t].filter(id => !id.startsWith(domainSlug + '--'));
|
|
206
|
+
}
|
|
207
|
+
delete catalog.byDomain[domain];
|
|
208
|
+
|
|
209
|
+
let count = 0;
|
|
210
|
+
|
|
211
|
+
for (const [rawType, variants] of Object.entries(componentVariants)) {
|
|
212
|
+
const variantArray = Array.isArray(variants) ? variants : [variants];
|
|
213
|
+
const normType = normalizeType(rawType);
|
|
214
|
+
|
|
215
|
+
for (let i = 0; i < variantArray.length; i++) {
|
|
216
|
+
const variant = variantArray[i];
|
|
217
|
+
if (!variant || typeof variant !== 'object' || Object.keys(variant).length === 0) continue;
|
|
218
|
+
|
|
219
|
+
const id = `${domainSlug}--${rawType}--${i}`;
|
|
220
|
+
const label = labelVariant(normType, rawType, i, variant);
|
|
221
|
+
|
|
222
|
+
// Component-specific tags
|
|
223
|
+
const componentTags = [...baseTags];
|
|
224
|
+
if (normType === 'button') componentTags.push('button');
|
|
225
|
+
if (normType === 'card') componentTags.push('card');
|
|
226
|
+
if (normType === 'heading') componentTags.push('heading', 'typography');
|
|
227
|
+
if (normType === 'input') componentTags.push('form', 'input');
|
|
228
|
+
if (normType === 'badge') componentTags.push('badge');
|
|
229
|
+
if (normType === 'link') componentTags.push('link');
|
|
230
|
+
|
|
231
|
+
const screenshotFull = path.join(extractionPath, 'screenshots', 'desktop-full.png');
|
|
232
|
+
const screenshotThumb = path.join(extractionPath, 'screenshots', 'desktop-thumb.png');
|
|
233
|
+
const screenshotPath = fs.existsSync(screenshotFull)
|
|
234
|
+
? path.relative(BANK_DIR, screenshotFull)
|
|
235
|
+
: fs.existsSync(screenshotThumb)
|
|
236
|
+
? path.relative(BANK_DIR, screenshotThumb)
|
|
237
|
+
: undefined;
|
|
238
|
+
|
|
239
|
+
const snapshot: ComponentSnapshot = {
|
|
240
|
+
id,
|
|
241
|
+
domain,
|
|
242
|
+
extractionPath: path.relative(BANK_DIR, extractionPath),
|
|
243
|
+
url,
|
|
244
|
+
extractedAt,
|
|
245
|
+
registeredAt,
|
|
246
|
+
componentType: normType,
|
|
247
|
+
componentLabel: label,
|
|
248
|
+
variantIndex: i,
|
|
249
|
+
siteTokens,
|
|
250
|
+
styles: variant,
|
|
251
|
+
tags: [...new Set(componentTags)],
|
|
252
|
+
screenshotPath,
|
|
253
|
+
designMdPath: fs.existsSync(designMdPath)
|
|
254
|
+
? path.relative(BANK_DIR, designMdPath)
|
|
255
|
+
: undefined,
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Save snapshot to bank/{id}/snapshot.json
|
|
259
|
+
const snapshotDir = path.join(BANK_DIR, id);
|
|
260
|
+
fs.mkdirSync(snapshotDir, { recursive: true });
|
|
261
|
+
fs.writeFileSync(
|
|
262
|
+
path.join(snapshotDir, 'snapshot.json'),
|
|
263
|
+
JSON.stringify(snapshot, null, 2)
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
// Update index
|
|
267
|
+
index.components.push({
|
|
268
|
+
id,
|
|
269
|
+
domain,
|
|
270
|
+
type: normType,
|
|
271
|
+
label,
|
|
272
|
+
tags: snapshot.tags,
|
|
273
|
+
accent: siteTokens.colors.accent,
|
|
274
|
+
background: siteTokens.colors.background,
|
|
275
|
+
isDark: siteTokens.isDark,
|
|
276
|
+
extractedAt,
|
|
277
|
+
registeredAt,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Update catalog
|
|
281
|
+
if (!catalog.byType[normType]) catalog.byType[normType] = [];
|
|
282
|
+
if (!catalog.byType[normType].includes(id)) catalog.byType[normType].push(id);
|
|
283
|
+
|
|
284
|
+
if (!catalog.byDomain[domain]) catalog.byDomain[domain] = [];
|
|
285
|
+
catalog.byDomain[domain].push(id);
|
|
286
|
+
|
|
287
|
+
for (const tag of snapshot.tags) {
|
|
288
|
+
if (!catalog.byTags[tag]) catalog.byTags[tag] = [];
|
|
289
|
+
if (!catalog.byTags[tag].includes(id)) catalog.byTags[tag].push(id);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
count++;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ── Register block extractions (from extract-block.ts) ─────────────
|
|
297
|
+
const blocksDir = path.join(extractionPath, 'blocks');
|
|
298
|
+
if (fs.existsSync(blocksDir)) {
|
|
299
|
+
const blockFiles = fs.readdirSync(blocksDir).filter(f => f.endsWith('.json'));
|
|
300
|
+
for (const file of blockFiles) {
|
|
301
|
+
try {
|
|
302
|
+
const block = JSON.parse(fs.readFileSync(path.join(blocksDir, file), 'utf-8'));
|
|
303
|
+
const blockSlug = file.replace('.json', '');
|
|
304
|
+
const id = `${domainSlug}--block--${blockSlug}`;
|
|
305
|
+
|
|
306
|
+
const blockTags = [...baseTags, 'block'];
|
|
307
|
+
// Detect block type from selector/tag
|
|
308
|
+
const rootTag = block.rootNode?.tag || 'div';
|
|
309
|
+
if (['header', 'nav'].includes(rootTag)) blockTags.push('navigation');
|
|
310
|
+
if (['main', 'section', 'article'].includes(rootTag)) blockTags.push('content');
|
|
311
|
+
if (rootTag === 'footer') blockTags.push('footer');
|
|
312
|
+
|
|
313
|
+
const snapshot: ComponentSnapshot = {
|
|
314
|
+
id,
|
|
315
|
+
domain,
|
|
316
|
+
extractionPath: path.relative(BANK_DIR, extractionPath),
|
|
317
|
+
url: block.url || url,
|
|
318
|
+
extractedAt: block.extractedAt || extractedAt,
|
|
319
|
+
registeredAt,
|
|
320
|
+
componentType: 'block',
|
|
321
|
+
componentLabel: `Block: ${block.selector || blockSlug}`,
|
|
322
|
+
variantIndex: 0,
|
|
323
|
+
siteTokens,
|
|
324
|
+
styles: block.rootNode?.styles || {},
|
|
325
|
+
tags: [...new Set(blockTags)],
|
|
326
|
+
screenshotPath: block.screenshotPath
|
|
327
|
+
? path.relative(BANK_DIR, block.screenshotPath)
|
|
328
|
+
: undefined,
|
|
329
|
+
designMdPath: fs.existsSync(designMdPath)
|
|
330
|
+
? path.relative(BANK_DIR, designMdPath)
|
|
331
|
+
: undefined,
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
// Save snapshot
|
|
335
|
+
const snapshotDir = path.join(BANK_DIR, id);
|
|
336
|
+
fs.mkdirSync(snapshotDir, { recursive: true });
|
|
337
|
+
fs.writeFileSync(path.join(snapshotDir, 'snapshot.json'), JSON.stringify(snapshot, null, 2));
|
|
338
|
+
|
|
339
|
+
// Also copy the full block extraction (with DOM tree) alongside snapshot
|
|
340
|
+
fs.writeFileSync(path.join(snapshotDir, 'block-tree.json'), JSON.stringify(block, null, 2));
|
|
341
|
+
|
|
342
|
+
// Update index
|
|
343
|
+
index.components.push({
|
|
344
|
+
id, domain, type: 'block',
|
|
345
|
+
label: snapshot.componentLabel,
|
|
346
|
+
tags: snapshot.tags,
|
|
347
|
+
accent: siteTokens.colors.accent,
|
|
348
|
+
background: siteTokens.colors.background,
|
|
349
|
+
isDark: siteTokens.isDark,
|
|
350
|
+
extractedAt: block.extractedAt || extractedAt,
|
|
351
|
+
registeredAt,
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Update catalog
|
|
355
|
+
if (!catalog.byType['block']) catalog.byType['block'] = [];
|
|
356
|
+
catalog.byType['block'].push(id);
|
|
357
|
+
if (!catalog.byDomain[domain]) catalog.byDomain[domain] = [];
|
|
358
|
+
catalog.byDomain[domain].push(id);
|
|
359
|
+
for (const tag of snapshot.tags) {
|
|
360
|
+
if (!catalog.byTags[tag]) catalog.byTags[tag] = [];
|
|
361
|
+
if (!catalog.byTags[tag].includes(id)) catalog.byTags[tag].push(id);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
count++;
|
|
365
|
+
} catch {
|
|
366
|
+
if (verbose) console.error(` ⚠️ Block ${file} skipped (parse error)`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Save updated index + catalog
|
|
372
|
+
index.updatedAt = registeredAt;
|
|
373
|
+
catalog.updatedAt = registeredAt;
|
|
374
|
+
fs.writeFileSync(indexPath, JSON.stringify(index, null, 2));
|
|
375
|
+
fs.writeFileSync(catalogPath, JSON.stringify(catalog, null, 2));
|
|
376
|
+
|
|
377
|
+
if (verbose) console.log(` ✅ ${domain} → ${count} composants enregistrés`);
|
|
378
|
+
return count;
|
|
379
|
+
}
|