vibe-design-system 2.9.1 → 2.9.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/package.json
CHANGED
|
@@ -5284,15 +5284,26 @@ function parseLocalDataImports(content) {
|
|
|
5284
5284
|
}
|
|
5285
5285
|
|
|
5286
5286
|
/**
|
|
5287
|
-
* Detect named exports from a data file
|
|
5287
|
+
* Detect named exports from a data file.
|
|
5288
|
+
* Returns { exportName → 'array' | 'object' | 'scalar' }
|
|
5289
|
+
* Also captures element type annotations: { 'EXPORTS_ELEMENT_TYPE' → 'TypeName' }
|
|
5290
|
+
* e.g. `export const METHODOLOGY_DATA: Stage[] = [...]` → { METHODOLOGY_DATA: 'array', METHODOLOGY_DATA_ELEMENT_TYPE: 'Stage' }
|
|
5288
5291
|
*/
|
|
5289
5292
|
function parseDataFileExports(filePath) {
|
|
5290
5293
|
if (!fs.existsSync(filePath)) return {};
|
|
5291
5294
|
const src = fs.readFileSync(filePath, "utf-8");
|
|
5292
5295
|
const result = {};
|
|
5293
|
-
|
|
5296
|
+
// Typed array exports: export const NAME: ElemType[] = [
|
|
5297
|
+
const typedRe = /^export\s+const\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*:\s*([A-Za-z_$][A-Za-z0-9_$]*)\[\]/gm;
|
|
5294
5298
|
let m;
|
|
5299
|
+
while ((m = typedRe.exec(src)) !== null) {
|
|
5300
|
+
result[m[1]] = "array";
|
|
5301
|
+
result[`${m[1]}_ELEMENT_TYPE`] = m[2]; // e.g. METHODOLOGY_DATA_ELEMENT_TYPE = "Stage"
|
|
5302
|
+
}
|
|
5303
|
+
// Untyped / other exports (fallback detection)
|
|
5304
|
+
const re = /^export\s+const\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*[=:]/gm;
|
|
5295
5305
|
while ((m = re.exec(src)) !== null) {
|
|
5306
|
+
if (result[m[1]]) continue; // already captured above
|
|
5296
5307
|
const name = m[1];
|
|
5297
5308
|
const afterIdx = src.indexOf("=", m.index) + 1;
|
|
5298
5309
|
const afterSlice = src.slice(afterIdx, afterIdx + 10).trimStart();
|
|
@@ -5356,8 +5367,9 @@ function extractComponentProps(content, componentName) {
|
|
|
5356
5367
|
|
|
5357
5368
|
/**
|
|
5358
5369
|
* Generate a mock value string for a TypeScript type, using available data exports.
|
|
5370
|
+
* Pass sourceContent to enable local interface/type parsing and nested property lookup.
|
|
5359
5371
|
*/
|
|
5360
|
-
function mockValueForType(type, localExports) {
|
|
5372
|
+
function mockValueForType(type, localExports, sourceContent) {
|
|
5361
5373
|
const t = type.trim();
|
|
5362
5374
|
if (t === "boolean") return "false";
|
|
5363
5375
|
if (t === "string") return '""';
|
|
@@ -5373,26 +5385,80 @@ function mockValueForType(type, localExports) {
|
|
|
5373
5385
|
const firstLiteral = t.match(/["']([^"']+)["']/);
|
|
5374
5386
|
if (firstLiteral && (t.startsWith('"') || t.startsWith("'"))) return `"${firstLiteral[1]}"`;
|
|
5375
5387
|
|
|
5376
|
-
//
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5388
|
+
// Named PascalCase type — try multiple resolution strategies
|
|
5389
|
+
if (/^[A-Z][A-Za-z0-9]+$/.test(t)) {
|
|
5390
|
+
const typeLower = t.toLowerCase().replace(/def$|id$|key$|type$|interface$|kind$/i, "");
|
|
5391
|
+
|
|
5392
|
+
// Strategy 1: Direct top-level array match (RouteDef → ROUTES[0], Stage → METHODOLOGY_DATA[0])
|
|
5393
|
+
if (typeLower.length >= 3) {
|
|
5394
|
+
// Sort by export name length descending — more-specific exports (METHODOLOGY_DATA) tried before simple ones (ROUTES)
|
|
5395
|
+
const sortedExports = Object.entries(localExports).sort((a, b) => b[0].length - a[0].length);
|
|
5396
|
+
// 1a: Exact element type annotation match (Stage → METHODOLOGY_DATA_ELEMENT_TYPE=Stage → METHODOLOGY_DATA[0])
|
|
5397
|
+
for (const [expName, expKind] of sortedExports) {
|
|
5398
|
+
if (expKind !== "array") continue;
|
|
5399
|
+
if (localExports[`${expName}_ELEMENT_TYPE`] === t) return `${expName}[0]`;
|
|
5400
|
+
}
|
|
5401
|
+
// 1b: Name prefix match (RouteDef → ROUTES[0])
|
|
5402
|
+
for (const [expName, expKind] of sortedExports) {
|
|
5403
|
+
if (expKind !== "array") continue;
|
|
5404
|
+
const expBase = expName.toLowerCase().replace(/_/g, "").replace(/s$/, "").replace(/data$/, "");
|
|
5405
|
+
if (expBase.slice(0, 4) === typeLower.slice(0, 4) || typeLower.slice(0, 4) === expBase.slice(0, 4)) {
|
|
5406
|
+
return `${expName}[0]`;
|
|
5407
|
+
}
|
|
5408
|
+
}
|
|
5409
|
+
}
|
|
5410
|
+
|
|
5411
|
+
// Strategy 2: Local type alias resolution — e.g. Level1Filter = 'A' | 'B' | null → null
|
|
5412
|
+
if (sourceContent) {
|
|
5413
|
+
const esc = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5414
|
+
// 2a: Non-object type alias: type Foo = ...
|
|
5415
|
+
const aliasRe = new RegExp(`type\\s+${esc}\\s*=\\s*([^{;\\n]+)`);
|
|
5416
|
+
const aliasMatch = sourceContent.match(aliasRe);
|
|
5417
|
+
if (aliasMatch) {
|
|
5418
|
+
const aliasValue = aliasMatch[1].trim().replace(/;$/, "");
|
|
5419
|
+
// Recursively resolve the alias value (pass sourceContent for chained aliases)
|
|
5420
|
+
return mockValueForType(aliasValue, localExports, sourceContent);
|
|
5421
|
+
}
|
|
5422
|
+
// 2b: Interface or object type alias → generate inline default object
|
|
5423
|
+
// e.g. FilterState → { level1: null, level2: null }
|
|
5424
|
+
const bodyRe = new RegExp(`(?:interface|type)\\s+${esc}\\s*(?:=\\s*)?\\{([^}]+)\\}`);
|
|
5425
|
+
const m = sourceContent.match(bodyRe);
|
|
5426
|
+
if (m) {
|
|
5427
|
+
const fields = m[1].split(";").map(s => s.trim()).filter(s => s.length > 0 && !s.startsWith("//"));
|
|
5428
|
+
const fieldPairs = fields.map(f => {
|
|
5429
|
+
const ci = f.indexOf(":");
|
|
5430
|
+
if (ci < 0) return null;
|
|
5431
|
+
const fname = f.slice(0, ci).trim().replace(/\?$/, "");
|
|
5432
|
+
const ftype = f.slice(ci + 1).trim();
|
|
5433
|
+
// Resolve field type with sourceContent for alias chaining
|
|
5434
|
+
const fmock = mockValueForType(ftype, localExports, sourceContent);
|
|
5435
|
+
return ` ${fname}: ${fmock}`;
|
|
5436
|
+
}).filter(Boolean);
|
|
5437
|
+
if (fieldPairs.length > 0) return `{\n${fieldPairs.join(",\n")}\n}`;
|
|
5386
5438
|
}
|
|
5387
5439
|
}
|
|
5440
|
+
|
|
5441
|
+
// Strategy 3: Nested array property lookup
|
|
5442
|
+
// SubStation → METHODOLOGY_DATA[0]?.subStations?.[0] (prefer longer export names)
|
|
5443
|
+
const camel = t[0].toLowerCase() + t.slice(1);
|
|
5444
|
+
const plural = camel + "s";
|
|
5445
|
+
const sortedForNested = Object.entries(localExports)
|
|
5446
|
+
.filter(([, k]) => k === "array")
|
|
5447
|
+
.sort((a, b) => b[0].length - a[0].length); // longest name first
|
|
5448
|
+
if (sortedForNested.length > 0) {
|
|
5449
|
+
const [expName] = sortedForNested[0];
|
|
5450
|
+
return `${expName}[0]?.${plural}?.[0]`;
|
|
5451
|
+
}
|
|
5388
5452
|
}
|
|
5453
|
+
|
|
5389
5454
|
return "undefined as any";
|
|
5390
5455
|
}
|
|
5391
5456
|
|
|
5392
5457
|
/**
|
|
5393
5458
|
* Build the story file content for a single internal component.
|
|
5459
|
+
* Automatically uses render() format when props require complex/nested mock data.
|
|
5394
5460
|
*/
|
|
5395
|
-
function buildInternalComponentStoryContent(compName, parentImportPath, dataImportPath, dataSymbols, localExports, props, parentComp) {
|
|
5461
|
+
function buildInternalComponentStoryContent(compName, parentImportPath, dataImportPath, dataSymbols, localExports, props, parentComp, sourceContent) {
|
|
5396
5462
|
const lines = [];
|
|
5397
5463
|
lines.push(`// @vds-regenerate — VDS auto-generated. Remove this line to prevent overwrite.`);
|
|
5398
5464
|
lines.push(`import React from "react";`);
|
|
@@ -5418,16 +5484,72 @@ function buildInternalComponentStoryContent(compName, parentImportPath, dataImpo
|
|
|
5418
5484
|
lines.push(`type Story = StoryObj<typeof meta>;`);
|
|
5419
5485
|
lines.push(``);
|
|
5420
5486
|
|
|
5421
|
-
if (props.length
|
|
5422
|
-
|
|
5487
|
+
if (props.length === 0) {
|
|
5488
|
+
lines.push(`export const Default: Story = {};`);
|
|
5489
|
+
return lines.join("\n") + "\n";
|
|
5490
|
+
}
|
|
5491
|
+
|
|
5492
|
+
const mockedProps = props.map(p => ({
|
|
5493
|
+
name: p.name,
|
|
5494
|
+
mock: mockValueForType(p.type, localExports, sourceContent),
|
|
5495
|
+
}));
|
|
5496
|
+
|
|
5497
|
+
// Use render() format when any mock is complex: nested path (?.),
|
|
5498
|
+
// multiline inline object (\n), or unresolved (undefined as any)
|
|
5499
|
+
const needsRender = mockedProps.some(p =>
|
|
5500
|
+
p.mock === "undefined as any" ||
|
|
5501
|
+
p.mock.includes("?.") ||
|
|
5502
|
+
p.mock.includes("\n")
|
|
5503
|
+
);
|
|
5504
|
+
|
|
5505
|
+
if (!needsRender) {
|
|
5506
|
+
// Simple args format — all values are primitives or direct data refs
|
|
5507
|
+
const argLines = mockedProps.map(p => ` ${p.name}: ${p.mock},`).join("\n");
|
|
5423
5508
|
lines.push(`export const Default: Story = {`);
|
|
5424
5509
|
lines.push(` args: {`);
|
|
5425
5510
|
lines.push(argLines);
|
|
5426
5511
|
lines.push(` },`);
|
|
5427
5512
|
lines.push(`};`);
|
|
5428
5513
|
} else {
|
|
5429
|
-
|
|
5514
|
+
// render() format — declare complex vars, guard against undefined, then render
|
|
5515
|
+
const varDecls = [];
|
|
5516
|
+
const propAttrs = [];
|
|
5517
|
+
const guardVarNames = [];
|
|
5518
|
+
|
|
5519
|
+
for (const { name, mock } of mockedProps) {
|
|
5520
|
+
if (mock.includes("?.") || mock === "undefined as any") {
|
|
5521
|
+
// Nested/unknown: declare variable + add to guard list
|
|
5522
|
+
const varName = `mock${name[0].toUpperCase() + name.slice(1)}`;
|
|
5523
|
+
varDecls.push(` const ${varName} = ${mock};`);
|
|
5524
|
+
guardVarNames.push(varName);
|
|
5525
|
+
propAttrs.push(` ${name}={${varName} as any}`);
|
|
5526
|
+
} else if (mock.includes("\n")) {
|
|
5527
|
+
// Multiline inline object (e.g. interface mock)
|
|
5528
|
+
const varName = `mock${name[0].toUpperCase() + name.slice(1)}`;
|
|
5529
|
+
const indented = mock.split("\n").map((l, i) => i === 0 ? l : " " + l).join("\n");
|
|
5530
|
+
varDecls.push(` const ${varName} = ${indented} as any;`);
|
|
5531
|
+
propAttrs.push(` ${name}={${varName}}`);
|
|
5532
|
+
} else {
|
|
5533
|
+
propAttrs.push(` ${name}={${mock}}`);
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
|
|
5537
|
+
lines.push(`export const Default: Story = {`);
|
|
5538
|
+
lines.push(` render: () => {`);
|
|
5539
|
+
for (const v of varDecls) lines.push(v);
|
|
5540
|
+
if (guardVarNames.length > 0) {
|
|
5541
|
+
const check = guardVarNames.map(v => `!${v}`).join(" || ");
|
|
5542
|
+
lines.push(` if (${check}) return <div style={{ color: "#888", padding: "1rem" }}>⚠️ Mock data unavailable for <strong>${compName}</strong></div>;`);
|
|
5543
|
+
}
|
|
5544
|
+
lines.push(` return (`);
|
|
5545
|
+
lines.push(` <${compName}`);
|
|
5546
|
+
for (const attr of propAttrs) lines.push(attr);
|
|
5547
|
+
lines.push(` />`);
|
|
5548
|
+
lines.push(` );`);
|
|
5549
|
+
lines.push(` },`);
|
|
5550
|
+
lines.push(`};`);
|
|
5430
5551
|
}
|
|
5552
|
+
|
|
5431
5553
|
return lines.join("\n") + "\n";
|
|
5432
5554
|
}
|
|
5433
5555
|
|
|
@@ -5495,10 +5617,14 @@ function generateInternalComponentStories(comp) {
|
|
|
5495
5617
|
// Step 4: Generate individual story files
|
|
5496
5618
|
for (const name of names) {
|
|
5497
5619
|
const storyFile = path.join(STORIES_DIR, `${name}.stories.tsx`);
|
|
5498
|
-
if (fs.existsSync(storyFile))
|
|
5620
|
+
if (fs.existsSync(storyFile)) {
|
|
5621
|
+
// Preserve user-modified stories; overwrite only VDS-generated ones
|
|
5622
|
+
const existing = fs.readFileSync(storyFile, "utf-8");
|
|
5623
|
+
if (!existing.startsWith("// @vds-regenerate")) continue;
|
|
5624
|
+
}
|
|
5499
5625
|
const props = extractComponentProps(sourceContent, name);
|
|
5500
5626
|
const content = buildInternalComponentStoryContent(
|
|
5501
|
-
name, parentImportPath, dataImportFromStories, bestSymbols, bestExports, props, comp
|
|
5627
|
+
name, parentImportPath, dataImportFromStories, bestSymbols, bestExports, props, comp, sourceContent
|
|
5502
5628
|
);
|
|
5503
5629
|
fs.writeFileSync(storyFile, content, "utf-8");
|
|
5504
5630
|
console.log(`[VDS] Wrote ${path.relative(PROJECT_ROOT, storyFile)} (sub-component of ${comp.name})`);
|