a11oy / web /sharedUiManifestPlugin.ts
betterwithage's picture
sync(space): complete build context — fix BUILD_ERROR (CTO)
518343a verified
Raw
History Blame
4.92 kB
import { readFileSync } from 'node:fs';
import path from 'node:path';
import type { Plugin } from 'vite';
const VIRTUAL_ID = 'virtual:shared-ui-manifest';
const RESOLVED_ID = `\0${VIRTUAL_ID}`;
export interface SharedUiExport {
name: string;
isComponent: boolean;
}
const ALL_CAPS_RE = /^[A-Z][A-Z0-9_]+$/;
const NON_COMPONENT_SUFFIXES = [
'Props',
'Config',
'Options',
'Type',
'Types',
'Colors',
'Color',
'Styles',
'Style',
'Labels',
'Label',
'Status',
'Statuses',
'Mode',
'Event',
'Events',
'Context',
'Schema',
'Entry',
'Item',
'Items',
'Args',
'Params',
'Payload',
'Result',
'Response',
'Request',
'Error',
'Action',
'Actions',
'Spec',
'Def',
'Info',
'Meta',
'Map',
'Signal',
'Level',
'Severity',
'Category',
];
export function classifyName(name: string): boolean {
if (!name || !/^[A-Z]/.test(name)) return false;
if (ALL_CAPS_RE.test(name)) return false;
for (const suffix of NON_COMPONENT_SUFFIXES) {
if (name.endsWith(suffix)) return false;
}
return true;
}
function processExportItem(raw: string, seen: Set<string>, out: SharedUiExport[]): void {
const trimmed = raw.trim();
if (!trimmed) return;
if (trimmed.startsWith('type ') || trimmed === 'type') return;
const aliasMatch = trimmed.match(/^(\w+)\s+as\s+(\w+)$/);
const finalName = aliasMatch ? aliasMatch[2] : trimmed.replace(/\s+as\s+\w+$/, '').trim();
if (!finalName || finalName === 'default' || seen.has(finalName)) return;
seen.add(finalName);
out.push({ name: finalName, isComponent: classifyName(finalName) });
}
export function parseExportsFromIndex(indexPath: string): SharedUiExport[] {
let content: string;
try {
content = readFileSync(indexPath, 'utf-8');
} catch {
return [];
}
const exports: SharedUiExport[] = [];
const seen = new Set<string>();
const lines = content.split('\n');
let blockBuffer = '';
let inBlock = false;
let isTypeBlock = false;
for (const rawLine of lines) {
const line = rawLine.trim();
if (!inBlock) {
if (/^export\s+type\s+\{/.test(line)) {
isTypeBlock = true;
inBlock = true;
if (line.includes('}')) {
inBlock = false;
isTypeBlock = false;
}
continue;
}
if (/^export\s+\{/.test(line)) {
if (line.includes('}')) {
const m = line.match(/^export\s+\{([^}]*)\}/);
const inner = m?.[1];
if (inner !== undefined) {
for (const item of inner.split(',')) {
processExportItem(item, seen, exports);
}
}
} else {
inBlock = true;
isTypeBlock = false;
blockBuffer = line.replace(/^export\s+\{/, '').trim();
}
continue;
}
const declMatch = line.match(
/^export\s+(?:default\s+)?(?:function|class|const|let|var|abstract\s+class)\s+(\w+)/,
);
if (declMatch && declMatch[1] !== undefined) {
processExportItem(declMatch[1], seen, exports);
}
} else {
if (line.includes('}')) {
inBlock = false;
if (!isTypeBlock) {
const inner = `${blockBuffer},${line.replace(/\}.*/, '')}`;
for (const item of inner.split(',')) {
processExportItem(item, seen, exports);
}
}
blockBuffer = '';
isTypeBlock = false;
} else {
if (!isTypeBlock) {
blockBuffer += `,${line}`;
}
}
}
}
return exports.sort((a, b) => a.name.localeCompare(b.name));
}
export function sharedUiManifestPlugin(options?: { indexPath?: string }): Plugin {
let resolvedIndexPath = '';
let manifestSource = '';
function buildManifest(indexPath: string): string {
const allExports = parseExportsFromIndex(indexPath);
const json = JSON.stringify(allExports, null, 2);
return [
'// Auto-generated by sharedUiManifestPlugin — do not edit.',
`export const sharedUiExports = ${json};`,
].join('\n');
}
return {
name: 'shared-ui-manifest',
enforce: 'pre',
configResolved(config) {
resolvedIndexPath =
options?.indexPath ??
path.resolve(config.root, '../../lib/shared-ui/src/index.ts');
manifestSource = buildManifest(resolvedIndexPath);
},
resolveId(id): string | undefined {
if (id === VIRTUAL_ID) return RESOLVED_ID;
return undefined;
},
load(id): string | undefined {
if (id === RESOLVED_ID) return manifestSource;
return undefined;
},
handleHotUpdate({ file, server }) {
if (file === resolvedIndexPath) {
manifestSource = buildManifest(resolvedIndexPath);
const mod = server.moduleGraph.getModuleById(RESOLVED_ID);
if (mod) {
server.moduleGraph.invalidateModule(mod);
server.ws.send({ type: 'full-reload' });
}
}
},
};
}